diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 4a32040a..4d6882bb 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -24,6 +24,8 @@ env: ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ + checker/fuzz/target + parser/fuzz/target jobs: validity: @@ -304,6 +306,9 @@ jobs: # find fuzz/artifacts -type f -print -exec xxd {} \; -exec cargo fuzz fmt -s none module_roundtrip_structured {} \;; false; # fi fi + + ls . + ls target working-directory: checker/fuzz clippy: diff --git a/checker/definitions/internal.ts.d.bin b/checker/definitions/internal.ts.d.bin index 69dd2cab..70374b16 100644 Binary files a/checker/definitions/internal.ts.d.bin and b/checker/definitions/internal.ts.d.bin differ diff --git a/checker/definitions/simple.d.ts b/checker/definitions/simple.d.ts index f2817f95..6534d8ec 100644 --- a/checker/definitions/simple.d.ts +++ b/checker/definitions/simple.d.ts @@ -32,12 +32,12 @@ declare function context_id(): void @Constant declare function context_id_chain(): void -// A function, as it should be! @Constant -declare function satisfies(t: T): T +declare function debug_state(): void +// A function, as it should be! @Constant -declare function compile_type_to_object(): any +declare function satisfies(t: T): T interface ImportEnv { [key: string]: string; @@ -76,7 +76,7 @@ declare class Array { const { length } = this, mapped: Array = []; let i: number = 0; while (i < length) { - const value = this?.[i]; + const value = this[i]; const newValue = cb(value, i++); mapped.push(newValue) } @@ -361,6 +361,38 @@ declare class Object { // Object.setProtoTypeOf(n, prototype); // return n // } + + static keys(on: { [s: string]: any }): Array { + const keys: Array = []; + for (const key in on) { + keys.push(key); + } + return keys + } + + static values(on: { [s: string]: any }): Array { + const values: Array = []; + for (const key in on) { + values.push(on[key]); + } + return values + } + + static entries(on: { [s: string]: any }): Array<[string, any]> { + const entries: Array<[string, any]> = []; + for (const key in on) { + entries.push([key, on[key]]); + } + return entries + } + + // TODO multiple arguments + static assign(target: object, source: object): object { + for (const key in source) { + target[key] = source[key] + } + return target + } } declare class RegExp { diff --git a/checker/examples/calculate_subtypes.rs b/checker/examples/calculate_subtypes.rs index 66270eef..72da1ee0 100644 --- a/checker/examples/calculate_subtypes.rs +++ b/checker/examples/calculate_subtypes.rs @@ -53,15 +53,11 @@ fn contributions(environment: &mut Environment, types: &mut TypeStore) { environment.new_explicit_type_parameter("T", Some(TypeId::NUMBER_TYPE), None, types); // create `{}` and add `inner: T` - let object = types.new_anonymous_interface_type(); - let inner = PropertyKey::String(std::borrow::Cow::Owned("inner".to_owned())); - environment.info.register_property( - object, + let object = types.new_anonymous_interface_type(vec![( Publicity::Public, - inner.clone(), + PropertyKey::String(std::borrow::Cow::Owned("inner".to_owned())), PropertyValue::Value(generic_parameter.type_id), - source_map::SpanWithSource::NULL, - ); + )]); let or = types.new_or_type(generic_parameter.type_id, object); let parameter = types.new_function_parameter(or); @@ -77,7 +73,7 @@ fn contributions(environment: &mut Environment, types: &mut TypeStore) { ); basis.append( Publicity::Public, - inner, + PropertyKey::String(std::borrow::Cow::Owned("inner".to_owned())), PropertyValue::Value(five), source_map::SpanWithSource::NULL, &mut environment.info, diff --git a/checker/examples/run_checker.rs b/checker/examples/run_checker.rs index eee0dc63..b06a5214 100644 --- a/checker/examples/run_checker.rs +++ b/checker/examples/run_checker.rs @@ -19,6 +19,7 @@ fn main() { let debug_types = args.iter().any(|item| item == "--debug-types"); let no_lib = args.iter().any(|item| item == "--no-lib"); let debug_dts = args.iter().any(|item| item == "--debug-dts"); + let extras = args.iter().any(|item| item == "--extras"); let now = Instant::now(); @@ -44,6 +45,7 @@ fn main() { record_all_assignments_and_reads: true, max_inline_count: 600, debug_dts, + extra_syntax: extras, ..Default::default() }; diff --git a/checker/fuzz/fuzz_targets/check_project_naive.rs b/checker/fuzz/fuzz_targets/check_project_naive.rs index b8666254..6a6039d9 100644 --- a/checker/fuzz/fuzz_targets/check_project_naive.rs +++ b/checker/fuzz/fuzz_targets/check_project_naive.rs @@ -2,7 +2,6 @@ use ezno_checker::{check_project, synthesis, TypeCheckOptions}; use libfuzzer_sys::{fuzz_target, Corpus}; -use std::collections::HashSet; use std::str; /// check_project_naive throws random strings into the ezno checker, validating that none of them make the checker panic. diff --git a/checker/specification/specification.md b/checker/specification/specification.md index 593f3e42..35251578 100644 --- a/checker/specification/specification.md +++ b/checker/specification/specification.md @@ -685,8 +685,8 @@ type MyObject = { foo: number; bar?: number }; const b: MyObject = { foo: 1, ...{ - bar: 2, - invalid: 3, + bar: 2, + invalid: 3, }, }; @@ -695,8 +695,8 @@ declare let condition: boolean; const c: MyObject = { foo: 1, ...(condition ? { - bar: 2, - non_existent: 3, + bar: 2, + non_existent: 3, } : {}), }; ``` @@ -953,7 +953,7 @@ function variadic(...r: string[]) { } ``` -- Expected boolean, found Array\ +- Expected boolean, found Array\ #### Destructuring parameter @@ -1048,7 +1048,7 @@ func satisfies string; > There are some issues around printing here, when to include the generic etc -- Expected string, found \(condition: T) => T ? 4 : 3 +- Expected string, found \(condition: T) => T ? 4 : 3 #### Early return @@ -1410,7 +1410,7 @@ function createNew(cb: { f(t: T): { a: T }}["f"]) { createNew satisfies string; ``` -- Expected string, found (cb: \(t: T) => { a: T }) => { a: 4 } +- Expected string, found (cb: \(t: T) => { a: T }) => { a: 4 } #### Builder pattern @@ -1421,12 +1421,12 @@ class StringBuilder { s: string = "" append(s: string) { - this.s += s; - return this + this.s += s; + return this } finish() { - return this.s + return this.s } } @@ -1975,7 +1975,7 @@ declare let string: string; function stringIsHi(s: string) { if (s === "hi") { - return true + return true } return false } @@ -1985,6 +1985,25 @@ stringIsHi(string) satisfies number; - Expected number, found boolean +#### Early return + +```ts +function func(param: boolean) { + let a = 2; + if (param) { + a = 3; + return a; + } else { + a = 7; + } + a satisfies string; +} +``` + +> Note not `3 | 7` + +- Expected string, found 7 + ### Iteration #### While loop unrolling @@ -2205,9 +2224,9 @@ console.log("Error caught!") ```ts function exceptionToResult(cb: () => number) { try { - return cb() + return cb() } catch (e) { - return e + return e } } @@ -2223,9 +2242,9 @@ console.log("Error caught!") ```ts function exceptionToResult(cb: () => number) { try { - cb() + cb() } catch (e: number) { - return e + return e } } @@ -2240,9 +2259,9 @@ console.log("Error caught!") ```ts function exceptionToResult(s: string) { try { - return JSON.parse(s) + return JSON.parse(s) } catch (e: number) { - return e + return e } } console.log("Error caught!") @@ -2258,9 +2277,9 @@ console.log("Error caught!") // no complex numbers :( function checkedLn(x: number) { if (x > 0) { - return Math.log(x) + return Math.log(x) } else { - throw new Error("Cannot log") + throw new Error("Cannot log") } } @@ -2664,8 +2683,6 @@ import.meta.env.production satisfies boolean; #### `instanceof` operator -> TODO dependent version - ```ts ([] instanceof Array) satisfies true; ({} instanceof Map) satisfies 4; @@ -2674,9 +2691,21 @@ class X {} (new X instanceof X) satisfies true; ([] instanceof X) satisfies false; + +function isArray(param: any): boolean { + return param instanceof Array; +} + +declare let myArray: Array; + +isArray([1, 2, 3]) satisfies true; +isArray(myArray) satisfies string; +isArray({ }) satisfies null; ``` - Expected 4, found false +- Expected string, found true +- Expected null, found false #### Tagged template literal @@ -2705,7 +2734,7 @@ const resp = await (fetch("/some-endpoint") satisfies string); resp.ok satisfies number; ``` -- Expected string, found Promise\ +- Expected string, found Promise\ - Expected number, found boolean ### Classes @@ -2732,7 +2761,7 @@ x.value satisfies string ```ts class X { method() { - return this; + return this; } } @@ -2889,7 +2918,7 @@ class X { static x = 2; static { - const property: 4 = ++this.x; + const property: 4 = ++this.x; } } @@ -2947,14 +2976,14 @@ class Class extends BaseClass { let b: number = 0; class Y { constructor(a) { - this.a = a; - b++; + this.a = a; + b++; } } class X extends Y { constructor(a) { - super(a); + super(a); } } @@ -3433,7 +3462,7 @@ const a: Uppercase<"something" |"hi"> = "HI"; const b: Uppercase = "hi" ``` -- Type \"hi\" is not assignable to type Uppercase\ +- Type \"hi\" is not assignable to type Uppercase\ #### `NoInfer` @@ -3750,6 +3779,7 @@ function x(p: { readonly a: string, b: string }) { function func1(p: { a: string, b: string }) { func2(p) } + function func2(p: readonly { a: string }) { } const obj = Object.freeze({ a: "hi" }); @@ -3773,7 +3803,7 @@ interface MyObject { const obj: MyObject = { a(b) { - b satisfies number; + b satisfies number; } } ``` @@ -3803,6 +3833,303 @@ x.map(a => (a satisfies string, 2)) - Expected string, found 1 | 2 | 3 +### Narrowing + +#### Equality + +```ts +function eqNarrow(a: string) { + if (a === "hi") { + a satisfies "hello" + } +} +``` + +- Expected "hello", found "hi" + +#### Condition outside of `if` + +```ts +function eqNarrow(a: string) { + const a_equals_hi = a === "hi"; + if (a_equals_hi) { + a satisfies "hello" + } +} +``` + +- Expected "hello", found "hi" + +#### Condition as a function + +```ts +function eqNarrow(a: string) { + function equalsHi(p: string): boolean { return p === "hi" } + + if (equalsHi(a)) { + a satisfies "hello" + } +} +``` + +- Expected "hello", found "hi" + +#### Reference passed around + +```ts +function eqNarrow(a: string) { + const b = a; + if (b === "hi") { + a satisfies "hello" + } +} +``` + +- Expected "hello", found "hi" + +#### `typeof` operator + +```ts +function typeOfNarrow(param: any) { + if (typeof param === "string") { + param satisfies number; + } +} +``` + +- Expected number, found string + +#### Boolean narrowing + +```ts +function booleanNarrow(param: boolean) { + if (param) { + param satisfies string + } + if (!param) { + param satisfies number + } +} +``` + +- Expected string, found true +- Expected number, found false + +#### Narrowing from operators + +```ts +function operatorNarrows(thing: string | null) { + (thing ?? "something") satisfies string; + (thing || "something") satisfies number; + + const result = thing === "hi" && (thing satisfies boolean); +} +``` + +- Expected number, found string | "something" +- Expected boolean, found "hi" + +#### Logic + +```ts +function logicNarrow(thing: any, other: any) { + if (typeof thing === "string" && other === 4) { + ({ thing, other }) satisfies string; + } + + if (typeof thing === "string" || typeof thing === "number") { + thing satisfies null; + } +} +``` + +- Expected string, found { thing: string, other: 4 } +- Expected null, found string | number + +#### Eating cases + +> TODO This tests two things. Context negation through final event and negating effect of typeof + +```ts +function func(param: boolean | string | number) { + if (typeof param === "boolean") { + return 5 + } + param satisfies null; +} +``` + +- Expected null, found string | number + +#### Prototype narrowed + +```ts +function func(param: Array | string) { + if (param instanceof Array) { + param satisfies null; + } +} +``` + +- Expected null, found Array\ + +#### Type generated from prototype + +```ts +function func(param: any) { + if (param instanceof Array) { + param satisfies null; + } +} +``` + +- Expected null, found Array\ + +#### Narrowing via property result + +```ts +function narrowPropertyEquals(param: { tag: "a", a: string } | { tag: "b", b: number }) { + if (param.tag === "a") { + param.a satisfies string; + param satisfies null; + } +} +``` + +- Expected null, found { tag: "a", a: string } + +#### Narrowing via `in` + +```ts +function narrowFromTag(param: { tag: "a", a: string } | { tag: "b", b: number }) { + if ("a" in param) { + param.a satisfies string; + param satisfies null; + } +} +``` + +- Expected null, found { tag: "a", a: string } + +#### Build object + +> TODO `.prop === 2` doesn't work because of get on `ANY_TYPE` + +```ts +function buildObject(param: any) { + if ("a" in param) { + param satisfies null; + } +} +``` + +- Expected null, found { a: any } + +#### Object equality + +```ts +function conditional(param: boolean) { + const obj1 = {}, obj2 = {}; + const sum = param ? obj1 : obj2; + if (sum === obj1) { + sum.a = 2; + } + [obj1, obj2] satisfies string; +} +``` + +- Expected string, found [{ a: 2 }, {}] + +#### From condition equality + +```ts +function conditional(param: boolean) { + const obj1 = { a: 1 }, obj2 = {}; + const sum = param ? obj1 : obj2; + if (param) { + sum satisfies string; + } +} +``` + +- Expected string, found { a: 1 } + +#### Across free variable + +```ts +function conditional(param: boolean) { + const constant = param; + if (constant) { + const b = () => constant; + b satisfies string; + } +} +``` + +- Expected string, found () => true + +#### Edge case + +> De-Morgans laws for and + +```ts +function func1(param: string | number) { + if (typeof param === "number" && param > 0) { + param satisfies number; + } else { + param satisfies null; + } +} + +function func2(param: string | number | boolean) { + if (typeof param === "string" || !(typeof param === "number")) { + param satisfies undefined; + } else { + param satisfies number; + } +} +``` + +- Expected null, found string | number +- Expected undefined, found string | boolean + +#### Mutation + +> TODO test more + +```ts +function func(param: boolean) { + let a = param; + const inner = (value: boolean) => a = value; + if (a) { + inner(false); + a satisfies null; + } +} +``` + +- Expected null, found false + +#### Assertions annotation + +```ts +function func1(param: any): asserts param is number { + if (typeof param !== "string") { + throw "bad" + } +} + +function func2(param: any): asserts param is boolean { + if (typeof param !== "boolean") { + throw "bad" + } +} +``` + +> TODO `any` should be parameter name + +- Cannot return asserts any is string because the function is expected to return asserts any is number + ### Object constraint > Any references to a annotated variable **must** be within its LHS type. These test that it carries down to objects. @@ -3959,13 +4286,13 @@ proxy1.a satisfies string; #### Proxy subtyping ```ts -const proxy1 = new Proxy({}, { get(_target, prop, _recivier) { return prop } }); +const proxy1 = new Proxy({}, { get(_target, prop, _receiver) { return prop } }); proxy1 satisfies { a: "a", b: "b" }; proxy1 satisfies { c: "d" }; ``` -- Expected { c: "d" }, found Proxy [ {}, { get: (_target: any, prop: any, _recivier: any) => any } ] +- Expected { c: "d" }, found Proxy [ {}, { get: (_target: any, prop: any, _receiver: any) => any } ] #### Proxy across functions @@ -4277,7 +4604,7 @@ const z: number = getString(2); - Cannot return number because the function is expected to return string - Type 5 is not assignable to type string - Could not find variable 'h' in scope -- Type (error) string is not assignable to type number +- Type string is not assignable to type number #### Errors carries @@ -4338,7 +4665,7 @@ register("something") register(document.title) ``` -- Argument of type string is not assignable to parameter of type Literal\ +- Argument of type string is not assignable to parameter of type Literal\ #### Number intrinsics @@ -4353,9 +4680,9 @@ register(document.title) -4 satisfies LessThan<2>; ``` -- Expected MultipleOf\<2\>, found 5 -- Expected GreaterThan\<2\>, found -4 -- Expected LessThan\<2\>, found 6 +- Expected MultipleOf\<2>, found 5 +- Expected GreaterThan\<2>, found -4 +- Expected LessThan\<2>, found 6 #### `Not` @@ -4375,7 +4702,7 @@ b satisfies 5; - Expected Not<4>, found 4 - Expected Not<8>, found number -- Expected Not\, found "hi" +- Expected Not\, found "hi" - Expected string, found Not<5> & number - Expected 5, found Not<5> & number @@ -4389,7 +4716,7 @@ x satisfies Exclusive; ({ a: 6 } satisfies Exclusive); ``` -- Expected Exclusive\, found { a: 1, b: 2 } +- Expected Exclusive\, found { a: 1, b: 2 } #### `CaseInsensitive` diff --git a/checker/src/context/environment.rs b/checker/src/context/environment.rs index 28c59fb2..ccdff6fd 100644 --- a/checker/src/context/environment.rs +++ b/checker/src/context/environment.rs @@ -884,52 +884,51 @@ impl<'a> Environment<'a> { if let (Some(_boundary), false) = (crossed_boundary, in_root) { let based_on = match og_var.get_mutability() { VariableMutability::Constant => { - let constraint = checking_data - .local_type_mappings - .variables_to_constraints - .0 - .get(&og_var.get_origin_variable_id()); - - // TODO temp - { - let current_value = get_value_of_variable( - self, - og_var.get_id(), - None::< - &crate::types::generics::substitution::SubstitutionArguments< - 'static, - >, - >, - ); - - if let Some(current_value) = current_value { - let ty = checking_data.types.get_type_by_id(current_value); - - // TODO temp - if let Type::SpecialObject(SpecialObject::Function(..)) = ty { - return Ok(VariableWithValue(og_var.clone(), current_value)); - } else if let Type::RootPolyType(PolyNature::Open(_)) = ty { - crate::utilities::notify!( - "Open poly type '{}' treated as immutable free variable", - name - ); - return Ok(VariableWithValue(og_var.clone(), current_value)); - } else if let Type::Constant(_) = ty { - return Ok(VariableWithValue(og_var.clone(), current_value)); - } - - crate::utilities::notify!("Free variable with value!"); - } else { - crate::utilities::notify!("Free variable with no current value"); + let current_value = self + .get_chain_of_info() + .find_map(|info| { + info.variable_current_value.get(&og_var.get_origin_variable_id()) + }) + .copied(); + let narrowed = current_value.and_then(|cv| self.get_narrowed(cv)); + + if let Some(precise) = narrowed.or(current_value) { + let ty = checking_data.types.get_type_by_id(precise); + + // TODO temp for function + if let Type::SpecialObject(SpecialObject::Function(..)) = ty { + return Ok(VariableWithValue(og_var.clone(), precise)); + } else if let Type::RootPolyType(PolyNature::Open(_)) = ty { + crate::utilities::notify!( + "Open poly type '{}' treated as immutable free variable", + name + ); + return Ok(VariableWithValue(og_var.clone(), precise)); + } else if let Type::Constant(_) = ty { + return Ok(VariableWithValue(og_var.clone(), precise)); } + + crate::utilities::notify!("Free variable with value!"); + } else { + crate::utilities::notify!("Free variable with no current value"); } - // TODO is primitive, then can just use type - if let Some(constraint) = constraint { - *constraint + if let Some(narrowed) = narrowed { + narrowed } else { - crate::utilities::notify!("TODO record that parent variable is `any` here"); - TypeId::ANY_TYPE + let constraint = checking_data + .local_type_mappings + .variables_to_constraints + .0 + .get(&og_var.get_origin_variable_id()); + if let Some(constraint) = constraint { + *constraint + } else { + crate::utilities::notify!( + "TODO record that free variable is `any` here" + ); + TypeId::ANY_TYPE + } } } VariableMutability::Mutable { reassignment_constraint } => { @@ -1124,13 +1123,15 @@ impl<'a> Environment<'a> { // Add the expected return type instead here // if it fell through to another then it could be bad - let expected_return = checking_data.types.new_error_type(expected); - let final_event = FinalEvent::Return { - returned: expected_return, - position: returned_position, - }; - self.info.events.push(final_event.into()); - return; + { + let expected_return = checking_data.types.new_error_type(expected); + let final_event = FinalEvent::Return { + returned: expected_return, + position: returned_position, + }; + self.info.events.push(final_event.into()); + return; + } } } } diff --git a/checker/src/context/information.rs b/checker/src/context/information.rs index 480eefbc..e94b6a7d 100644 --- a/checker/src/context/information.rs +++ b/checker/src/context/information.rs @@ -6,14 +6,12 @@ use crate::{ features::functions::ClosureId, types::{ calling::ThisValue, - properties::{PropertyKey, Publicity}, + properties::{Properties, PropertyKey, Publicity}, TypeStore, }, PropertyValue, Type, TypeId, VariableId, }; -pub type Properties = Vec<(Publicity, PropertyKey<'static>, PropertyValue)>; - /// Things that are currently true or have happened #[derive(Debug, Default, binary_serialize_derive::BinarySerializable)] pub struct LocalInformation { @@ -23,11 +21,15 @@ pub struct LocalInformation { /// This can be not have a value if not defined pub(crate) variable_current_value: HashMap, + + /// For objects (inc classes) and interfaces (because of hoisting). + /// However properties on [`crate::types::ObjectNature::AnonomousObjectLiteral`] are held on there pub(crate) current_properties: HashMap, /// Can be modified (unfortunately) so here pub(crate) prototypes: HashMap, + /// `ContextId` is a mini context pub(crate) closure_current_values: HashMap<(ClosureId, RootReference), TypeId>, /// Not writeable, `TypeError: Cannot add property t, object is not extensible`. TODO conditional ? @@ -38,6 +40,10 @@ pub struct LocalInformation { /// *not quite the best place, but used in [`InformationChain`]* pub(crate) object_constraints: HashMap, + /// WIP narrowing + /// TODO how will chaining but not cycles work + pub(crate) narrowed_values: crate::Map, + pub(crate) state: ReturnState, /// For super calls etc @@ -206,6 +212,8 @@ impl LocalInformation { self.prototypes.extend(other.prototypes); self.closure_current_values.extend(other.closure_current_values); self.frozen.extend(other.frozen); + self.narrowed_values.extend(other.narrowed_values); + self.state = other.state; } /// TODO explain when `ref` @@ -219,16 +227,22 @@ impl LocalInformation { self.closure_current_values .extend(other.closure_current_values.iter().map(|(l, r)| (l.clone(), *r))); self.frozen.extend(other.frozen.iter().clone()); + self.narrowed_values.extend(other.narrowed_values.iter().copied()); + self.state = other.state.clone(); } #[must_use] - pub fn is_halted(&self) -> bool { + pub fn is_finished(&self) -> bool { self.state.is_finished() } } pub trait InformationChain { fn get_chain_of_info(&self) -> impl Iterator; + + fn get_narrowed(&self, for_ty: TypeId) -> Option { + self.get_chain_of_info().find_map(|info| info.narrowed_values.get(&for_ty).copied()) + } } impl InformationChain for LocalInformation { @@ -266,6 +280,46 @@ pub fn merge_info( types: &mut TypeStore, position: SpanWithSource, ) { + // TODO I think these are okay + // Bias sets what happens after + let (new_state, carry_information_from) = match ( + truthy.state.clone(), + otherwise.as_ref().map_or(ReturnState::Continued, |o| o.state.clone()), + ) { + (ReturnState::Continued, ReturnState::Continued) => (ReturnState::Continued, None), + (ReturnState::Finished(returned), ReturnState::Continued) => { + (ReturnState::Rolling { under: condition, returned }, Some(false)) + } + (ReturnState::Continued, ReturnState::Finished(returned)) => ( + ReturnState::Rolling { under: types.new_logical_negation_type(condition), returned }, + Some(true), + ), + (ReturnState::Continued, rhs @ ReturnState::Rolling { .. }) => (rhs, None), + (ReturnState::Rolling { under, returned }, ReturnState::Continued) => ( + ReturnState::Rolling { under: types.new_logical_and_type(condition, under), returned }, + None, + ), + ( + ReturnState::Rolling { under: truthy_under, returned: truthy_returned }, + ReturnState::Rolling { under: otherwise_under, returned: otherwise_returned }, + ) => { + let under = types.new_logical_or_type(truthy_under, otherwise_under); + let returned = + types.new_conditional_type(condition, truthy_returned, otherwise_returned); + (ReturnState::Rolling { under, returned }, None) + } + (lhs @ ReturnState::Rolling { .. }, ReturnState::Finished(_)) => (lhs, Some(true)), + (ReturnState::Finished(_), rhs @ ReturnState::Rolling { .. }) => (rhs, Some(false)), + (ReturnState::Finished(truthy_return), ReturnState::Finished(otherwise_return)) => ( + ReturnState::Finished(types.new_conditional_type( + condition, + truthy_return, + otherwise_return, + )), + None, + ), + }; + let truthy_events = truthy.events.len() as u32; let otherwise_events = otherwise.as_ref().map_or(0, |f| f.events.len() as u32); @@ -286,70 +340,63 @@ pub fn merge_info( onto.events.push(Event::EndOfControlFlow(truthy_events + otherwise_events)); } - // TODO don't need to do above some scope - for (var, true_value) in truthy.variable_current_value { - crate::utilities::notify!("{:?} {:?}", var, true_value); - // TODO don't get value above certain scope... - let otherwise_value = otherwise - .as_mut() - // Remove is important here - .and_then(|otherwise| otherwise.variable_current_value.remove(&var)) - .or_else(|| onto.variable_current_value.get(&var).copied()) - .or_else(|| { - parents - .get_chain_of_info() - .find_map(|info| info.variable_current_value.get(&var)) - .copied() - }) - .unwrap_or(TypeId::ERROR_TYPE); - - let new = types.new_conditional_type(condition, true_value, otherwise_value); - - onto.variable_current_value.insert(var, new); - } - - // TODO temp fix for `... ? { ... } : { ... }`. Breaks for the fact that property - // properties might be targeting something above the current condition (e.g. `x ? (y.a = 2) : false`); - onto.current_properties.extend(truthy.current_properties.drain()); - if let Some(ref mut otherwise) = otherwise { - onto.current_properties.extend(otherwise.current_properties.drain()); + if new_state.is_finished() { + onto.state = new_state; + return; } - // TODO I think these are okay - onto.state = match ( - truthy.state, - otherwise.as_ref().map_or(ReturnState::Continued, |o| o.state.clone()), - ) { - (ReturnState::Continued, ReturnState::Continued) => ReturnState::Continued, - (ReturnState::Continued, ReturnState::Rolling { .. }) => todo!(), - (ReturnState::Continued, ReturnState::Finished(returned)) => { - ReturnState::Rolling { under: types.new_logical_negation_type(condition), returned } - } - (ReturnState::Rolling { under, returned }, ReturnState::Continued) => { - ReturnState::Rolling { under: types.new_logical_and_type(condition, under), returned } - } - ( - ReturnState::Rolling { under: truthy_under, returned: truthy_returned }, - ReturnState::Rolling { under: otherwise_under, returned: otherwise_returned }, - ) => { - let under = types.new_logical_or_type(truthy_under, otherwise_under); - let returned = - types.new_conditional_type(condition, truthy_returned, otherwise_returned); - ReturnState::Rolling { under, returned } + if let Some(carry_information_from) = carry_information_from { + #[allow(clippy::match_bool)] + match carry_information_from { + true => { + onto.extend(truthy, None); + } + false => { + if let Some(otherwise) = otherwise { + onto.extend(otherwise, None); + } else { + // Could negate existing, but starting again handles types better + let values = crate::features::narrowing::narrow_based_on_expression_into_vec( + condition, true, parents, types, + ); + + onto.narrowed_values = values; + onto.state = new_state; + } + } } - (ReturnState::Rolling { .. }, ReturnState::Finished(_)) => todo!(), - (ReturnState::Finished(returned), ReturnState::Continued) => { - ReturnState::Rolling { under: condition, returned } + } else { + onto.state = new_state; + + // TODO don't need to do above some scope + for (var, true_value) in truthy.variable_current_value { + crate::utilities::notify!("{:?} {:?}", var, true_value); + // TODO don't get value above certain scope... + let otherwise_value = otherwise + .as_mut() + // Remove is important here + .and_then(|otherwise| otherwise.variable_current_value.remove(&var)) + .or_else(|| onto.variable_current_value.get(&var).copied()) + .or_else(|| { + parents + .get_chain_of_info() + .find_map(|info| info.variable_current_value.get(&var)) + .copied() + }) + .unwrap_or(TypeId::ERROR_TYPE); + + let new = types.new_conditional_type(condition, true_value, otherwise_value); + + onto.variable_current_value.insert(var, new); } - (ReturnState::Finished(_), ReturnState::Rolling { .. }) => todo!(), - (ReturnState::Finished(truthy_return), ReturnState::Finished(otherwise_return)) => { - ReturnState::Finished(types.new_conditional_type( - condition, - truthy_return, - otherwise_return, - )) + + // TODO temp fix for `... ? { ... } : { ... }`. Breaks for the fact that property + // properties might be targeting something above the current condition (e.g. `x ? (y.a = 2) : false`); + onto.current_properties.extend(truthy.current_properties.drain()); + if let Some(ref mut otherwise) = otherwise { + onto.current_properties.extend(otherwise.current_properties.drain()); } - }; - // TODO set more information? an exit condition? + // TODO set more information? + } } diff --git a/checker/src/context/mod.rs b/checker/src/context/mod.rs index 718e97a0..55a5ddc6 100644 --- a/checker/src/context/mod.rs +++ b/checker/src/context/mod.rs @@ -29,7 +29,7 @@ use crate::{ use self::environment::{DynamicBoundaryKind, FunctionScope}; pub use environment::Scope; pub(crate) use environment::Syntax; -pub use information::{InformationChain, LocalInformation, Properties}; +pub use information::{InformationChain, LocalInformation}; use std::{ collections::{ @@ -390,7 +390,10 @@ impl Context { VariableOrImport::Variable { mutability, .. } => match mutability { // TODO get value + object constraint VariableMutability::Mutable { reassignment_constraint: None } - | VariableMutability::Constant => TypeId::ERROR_TYPE, + | VariableMutability::Constant => { + crate::utilities::notify!("TODO get value"); + TypeId::ERROR_TYPE + } VariableMutability::Mutable { reassignment_constraint: Some(value), } => *value, @@ -408,7 +411,10 @@ impl Context { } }) } - Reference::Property { .. } => todo!("keyof on?"), + Reference::Property { .. } => { + crate::utilities::notify!("TODO get object constraint on object"); + Some(TypeId::ERROR_TYPE) + } } } @@ -1023,11 +1029,10 @@ pub(crate) fn get_value_of_variable( let res = res.or_else(|| fact.variable_current_value.get(&on).copied()); - // TODO WIP narrowing - // TODO in remaining info, don't loop again if let Some(res) = res { - return Some(res); + let narrowed = info.get_narrowed(res); + return Some(narrowed.unwrap_or(res)); } } None diff --git a/checker/src/events/application.rs b/checker/src/events/application.rs index 880e849f..59a93b3d 100644 --- a/checker/src/events/application.rs +++ b/checker/src/events/application.rs @@ -1,11 +1,12 @@ use source_map::SpanWithSource; -use super::{CallingTiming, Event, FinalEvent, PrototypeArgument, RootReference}; +use super::{ + ApplicationResult, CallingTiming, Event, FinalEvent, PrototypeArgument, RootReference, +}; use crate::{ context::{get_value_of_variable, invocation::InvocationContext, CallCheckingBehavior}, diagnostics::{TypeStringRepresentation, TDZ}, - events::ApplicationResult, features::{ iteration::{self, IterationKind}, objects::SpecialObject, diff --git a/checker/src/features/conditional.rs b/checker/src/features/conditional.rs index c4eb8abc..caa7b332 100644 --- a/checker/src/features/conditional.rs +++ b/checker/src/features/conditional.rs @@ -40,6 +40,15 @@ where let mut truthy_environment = environment .new_lexical_environment(Scope::Conditional { antecedent: condition, is_switch: None }); + let values = super::narrowing::narrow_based_on_expression_into_vec( + condition, + false, + environment, + &mut checking_data.types, + ); + + truthy_environment.info.narrowed_values = values; + let result = then_evaluate(&mut truthy_environment, checking_data); let Context { @@ -65,6 +74,15 @@ where is_switch: None, }); + let values = super::narrowing::narrow_based_on_expression_into_vec( + condition, + true, + environment, + &mut checking_data.types, + ); + + falsy_environment.info.narrowed_values = values; + let result = else_evaluate(&mut falsy_environment, checking_data); let Context { diff --git a/checker/src/features/constant_functions.rs b/checker/src/features/constant_functions.rs index 4ba842ca..f8411ab9 100644 --- a/checker/src/features/constant_functions.rs +++ b/checker/src/features/constant_functions.rs @@ -595,6 +595,9 @@ pub(crate) fn call_constant_function( // Ok(ConstantOutput::Diagnostic(output)) // } // } + "debug_state" => { + Ok(ConstantOutput::Diagnostic(format!("State={:?}", environment.info.state))) + } "debug_context" => Ok(ConstantOutput::Diagnostic(environment.debug())), "context_id" => Ok(ConstantOutput::Diagnostic(format!("in {:?}", environment.context_id))), "context_id_chain" => Ok(ConstantOutput::Diagnostic({ diff --git a/checker/src/features/functions.rs b/checker/src/features/functions.rs index 8a732327..d04624e5 100644 --- a/checker/src/features/functions.rs +++ b/checker/src/features/functions.rs @@ -118,7 +118,7 @@ pub fn synthesise_hoisted_statement_function { - // crate::utilities::notify!( - // "expecting {}", - // types::printing::print_type( - // expecting, - // &checking_data.types, - // base_environment, - // false - // ) - // ); let (expected_parameters, expected_return) = get_expected_parameters_from_type( expecting, &mut checking_data.types, base_environment, ); - crate::utilities::notify!("expected {:?}", expecting); if let Some((or, _)) = expected_parameters.as_ref().and_then(|a| a.get_parameter_type_at_index(0)) @@ -849,7 +837,54 @@ where let variable_names = function_environment.variable_names; let returned = if function.has_body() { - info.state.get_returned(&mut checking_data.types) + let returned = info.state.get_returned(&mut checking_data.types); + + // TODO temp fix for predicates. This should be really be done in `environment.throw` ()? + if let Some(ReturnType(expected, _)) = return_type_annotation { + if crate::types::type_is_assert_is_type(expected, &checking_data.types).is_ok() { + let mut state = crate::subtyping::State { + already_checked: Default::default(), + mode: Default::default(), + contributions: Default::default(), + others: crate::subtyping::SubTypingOptions::satisfies(), + // TODO don't think there is much case in constraining it here + object_constraints: None, + }; + + let result = crate::subtyping::type_is_subtype( + expected, + returned, + &mut state, + base_environment, + &checking_data.types, + ); + + if let crate::subtyping::SubTypeResult::IsNotSubType(_) = result { + checking_data.diagnostics_container.add_error( + TypeCheckError::ReturnedTypeDoesNotMatch { + expected_return_type: TypeStringRepresentation::from_type_id( + expected, + base_environment, + &checking_data.types, + checking_data.options.debug_types, + ), + returned_type: TypeStringRepresentation::from_type_id( + returned, + base_environment, + &checking_data.types, + checking_data.options.debug_types, + ), + annotation_position: position, + returned_position: function + .get_position() + .with_source(base_environment.get_source()), + }, + ); + } + } + } + + returned } else if let Some(ReturnType(ty, _)) = return_type_annotation { ty } else { diff --git a/checker/src/features/iteration.rs b/checker/src/features/iteration.rs index 02d9476b..3f69aee0 100644 --- a/checker/src/features/iteration.rs +++ b/checker/src/features/iteration.rs @@ -79,10 +79,9 @@ pub fn synthesise_iteration( &mut environment.info.events, ); + // TODO narrowing loop_body(environment, checking_data); - // crate::utilities::notify!("Loop does {:#?}", environment.info.events); - condition }, ); @@ -748,6 +747,14 @@ impl LoopStructure { } pub fn calculate_iterations(self, types: &TypeStore) -> Result { + self.calculate_iterations_f64(types).map(|result| result as usize) + } + + pub fn known_to_never_exist(self, types: &TypeStore) -> bool { + self.calculate_iterations_f64(types).map_or(false, f64::is_infinite) + } + + fn calculate_iterations_f64(self, types: &TypeStore) -> Result { let values = ( types.get_type_by_id(self.start), types.get_type_by_id(self.increment_by), @@ -768,7 +775,7 @@ impl LoopStructure { // increment, // iterations // ); - Ok(iterations.ceil() as usize) + Ok(iterations.ceil()) } else { // crate::utilities::notify!("Iterations was {:?}", values); Err(self) diff --git a/checker/src/features/mod.rs b/checker/src/features/mod.rs index 712e2dd1..f998989f 100644 --- a/checker/src/features/mod.rs +++ b/checker/src/features/mod.rs @@ -14,6 +14,7 @@ pub mod exceptions; pub mod functions; pub mod iteration; pub mod modules; +pub mod narrowing; pub mod objects; pub mod operations; pub mod template_literal; @@ -25,7 +26,6 @@ use crate::{ context::{get_value_of_variable, ClosedOverReferencesInScope, InformationChain}, diagnostics::TypeStringRepresentation, events::RootReference, - features::functions::ClosedOverVariables, types::{ get_constraint, logical::{Logical, LogicalOrValid}, @@ -34,28 +34,48 @@ use crate::{ CheckingData, Environment, PropertyValue, Type, TypeId, }; -use self::objects::SpecialObject; +use self::{functions::ClosedOverVariables, objects::SpecialObject}; + +pub(crate) fn type_to_js_string_name(on: TypeId) -> Option<&'static str> { + match on { + TypeId::NUMBER_TYPE => Some("number"), + TypeId::STRING_TYPE => Some("string"), + TypeId::BOOLEAN_TYPE => Some("boolean"), + TypeId::SYMBOL_TYPE => Some("symbol"), + TypeId::UNDEFINED_TYPE => Some("undefined"), + TypeId::NULL_TYPE => Some("object"), + _ => None, + } +} + +pub(crate) fn string_name_to_type(name: &str) -> Option { + match name { + "number" => Some(TypeId::NUMBER_TYPE), + "string" => Some(TypeId::STRING_TYPE), + "boolean" => Some(TypeId::BOOLEAN_TYPE), + "function" => Some(TypeId::FUNCTION_TYPE), + "undefined" => Some(TypeId::UNDEFINED_TYPE), + "object" => Some(TypeId::OBJECT_TYPE), + _rhs => None, + } +} /// Returns result of `typeof *on*` pub fn type_of_operator(on: TypeId, types: &mut TypeStore) -> TypeId { if let Some(constraint) = get_constraint(on, types) { - let name = match constraint { - TypeId::NUMBER_TYPE => "number", - TypeId::STRING_TYPE => "string", - TypeId::BOOLEAN_TYPE => "boolean", - TypeId::SYMBOL_TYPE => "symbol", - _constraint => { - return types.register_type(crate::Type::Constructor( - crate::types::Constructor::TypeOperator(crate::types::TypeOperator::TypeOf(on)), - )) - } - }; - // TODO could Cow or something to not allocate? - types.new_constant_type(crate::Constant::String(name.to_owned())) + if let Some(str_name) = type_to_js_string_name(constraint) { + // TODO make them interal types under `TypeId::*` as to not recreate types + types.new_constant_type(crate::Constant::String(str_name.to_owned())) + } else { + // TODO if always object or function then could get more accurate tag + types.register_type(crate::Type::Constructor(crate::types::Constructor::TypeOperator( + crate::types::TypeOperator::TypeOf(on), + ))) + } } else if on == TypeId::UNDEFINED_TYPE { return types.new_constant_type(crate::Constant::String("undefined".to_owned())); } else if on == TypeId::NULL_TYPE { - return types.new_constant_type(crate::Constant::String("null".to_owned())); + return types.new_constant_type(crate::Constant::String("object".to_owned())); } else { let ty = types.get_type_by_id(on); if let crate::Type::Constant(cst) = ty { @@ -80,7 +100,7 @@ pub fn type_of_operator(on: TypeId, types: &mut TypeStore) -> TypeId { } // TODO think this is okay -fn extends_prototype(lhs: TypeId, rhs: TypeId, information: &impl InformationChain) -> bool { +pub fn extends_prototype(lhs: TypeId, rhs: TypeId, information: &impl InformationChain) -> bool { for info in information.get_chain_of_info() { if let Some(lhs_prototype) = info.prototypes.get(&lhs).copied() { let prototypes_equal = lhs_prototype == rhs; @@ -101,31 +121,52 @@ pub fn instance_of_operator( information: &impl InformationChain, types: &mut TypeStore, ) -> TypeId { - // TODO frozen prototypes - if let Some(_constraint) = get_constraint(lhs, types) { - todo!() - } else { - use crate::types::functions; - let rhs_prototype = if let Type::SpecialObject(SpecialObject::Function(func, _)) = - types.get_type_by_id(rhs) - { + let rhs_prototype = + if let Type::SpecialObject(SpecialObject::Function(func, _)) = types.get_type_by_id(rhs) { + use crate::types::functions::FunctionBehavior; + let func = types.get_function_from_id(*func); match &func.behavior { - functions::FunctionBehavior::ArrowFunction { .. } - | functions::FunctionBehavior::Method { .. } => TypeId::UNDEFINED_TYPE, - functions::FunctionBehavior::Function { prototype, .. } - | functions::FunctionBehavior::Constructor { prototype, .. } => *prototype, + FunctionBehavior::ArrowFunction { .. } | FunctionBehavior::Method { .. } => { + TypeId::UNDEFINED_TYPE + } + FunctionBehavior::Function { prototype, .. } + | FunctionBehavior::Constructor { prototype, .. } => *prototype, } } else { - // TODO err + crate::utilities::notify!("Instanceof RHS dependent or not constructor"); rhs }; - if extends_prototype(lhs, rhs_prototype, information) { - TypeId::TRUE - } else { - TypeId::FALSE + instance_of_operator_rhs_prototype(lhs, rhs_prototype, information, types) +} + +pub(crate) fn instance_of_operator_rhs_prototype( + lhs: TypeId, + rhs_prototype: TypeId, + information: &impl InformationChain, + types: &mut TypeStore, +) -> TypeId { + // TODO frozen prototypes + if let Some(constraint) = get_constraint(lhs, types) { + if let Type::PartiallyAppliedGenerics(crate::types::PartiallyAppliedGenerics { + on, + arguments: _, + }) = types.get_type_by_id(constraint) + { + if *on == rhs_prototype { + return TypeId::TRUE; + } + crate::utilities::notify!("Here?"); } + + types.register_type(Type::Constructor(crate::types::Constructor::TypeOperator( + crate::types::TypeOperator::IsPrototype { lhs, rhs_prototype }, + ))) + } else if extends_prototype(lhs, rhs_prototype, information) { + TypeId::TRUE + } else { + TypeId::FALSE } } @@ -362,8 +403,6 @@ pub fn in_operator( environment: &mut Environment, types: &mut TypeStore, ) -> TypeId { - let result = has_property((publicity, under), rhs, environment, types); - // TODO if any if get_constraint(rhs, types).is_some() { let dependency = @@ -371,11 +410,6 @@ pub fn in_operator( crate::types::TypeOperator::HasProperty(rhs, under.into_owned()), ))); - { - let ty = crate::types::printing::print_type(result, types, environment, true); - crate::utilities::notify!("ty={:?}, dependency={:?}", ty, dependency); - } - environment.info.events.push(crate::events::Event::Miscellaneous( crate::events::MiscellaneousEvents::Has { on: rhs, @@ -386,7 +420,70 @@ pub fn in_operator( )); dependency } else { - result + has_property((publicity, under), rhs, environment, types) + } +} + +/// WIP +pub(crate) fn has_property( + (publicity, key): (properties::Publicity, &properties::PropertyKey<'_>), + rhs: TypeId, + information: &impl InformationChain, + types: &mut TypeStore, +) -> TypeId { + match types.get_type_by_id(rhs) { + Type::Interface { .. } + | Type::Class { .. } + | Type::Constant(_) + | Type::FunctionReference(_) + | Type::Object(_) + | Type::PartiallyAppliedGenerics(_) + | Type::And(_, _) + | Type::SpecialObject(_) + | Type::Narrowed { .. } + | Type::AliasTo { .. } => { + let result = properties::get_property_unbound( + (rhs, None), + (publicity, key, None), + false, + information, + types, + ); + match result { + Ok(LogicalOrValid::Logical(result)) => match result { + Logical::Pure(_) => TypeId::TRUE, + Logical::Or { .. } => { + crate::utilities::notify!("or or implies `in`"); + TypeId::ERROR_TYPE + } + Logical::Implies { .. } => { + crate::utilities::notify!("or or implies `in`"); + TypeId::ERROR_TYPE + } + Logical::BasedOnKey { .. } => { + crate::utilities::notify!("mapped in"); + TypeId::ERROR_TYPE + } + }, + Ok(LogicalOrValid::NeedsCalculation(result)) => { + crate::utilities::notify!("TODO {:?}", result); + TypeId::ERROR_TYPE + } + Err(err) => { + crate::utilities::notify!("TODO {:?}", err); + TypeId::FALSE + } + } + } + Type::Or(_, _) => { + crate::utilities::notify!("Condtionally"); + TypeId::ERROR_TYPE + } + Type::RootPolyType(_) | Type::Constructor(_) => { + crate::utilities::notify!("Queue event / create dependent"); + let constraint = get_constraint(rhs, types).unwrap(); + has_property((publicity, key), constraint, information, types) + } } } @@ -395,7 +492,7 @@ pub mod tsc { use crate::{ diagnostics, - types::{subtyping, Constructor, PolyNature, TypeStore}, + types::{Constructor, TypeStore}, CheckingData, Environment, Type, TypeId, }; @@ -411,7 +508,7 @@ pub mod tsc { | Constructor::UnaryOperator { .. } | Constructor::BinaryOperator { .. } => false, Constructor::TypeOperator(_) => todo!(), - Constructor::TypeRelationOperator(_) => todo!(), + Constructor::TypeExtends(_) => todo!(), Constructor::Awaited { .. } | Constructor::KeyOf(..) | Constructor::ConditionalResult { .. } @@ -428,54 +525,27 @@ pub mod tsc { // TSC compat around `any` let cast_to = if cast_to == TypeId::ANY_TYPE { TypeId::ERROR_TYPE } else { cast_to }; - // TODO Type::Narrowed - Ok(types.register_type(Type::RootPolyType(PolyNature::Open(cast_to)))) + Ok(types.register_type(Type::Narrowed { narrowed_to: cast_to, from: on })) } else { Err(()) } } - pub fn non_null_assertion(on: TypeId, types: &mut TypeStore) -> Result { - /// TODO undefined? what about null - fn get_non_null_type(on: TypeId, types: &TypeStore) -> TypeId { - match types.get_type_by_id(on) { - // Type::Constructor(Constructor::ConditionalResult { - // condition: _, - // truthy_result, - // otherwise_result, - // result_union: _, - // }) => { - // if *truthy_result == TypeId::UNDEFINED_TYPE { - // *otherwise_result - // } else if *otherwise_result == TypeId::UNDEFINED_TYPE { - // *truthy_result - // } else { - // on - // } - // } - Type::Or(left, right) => { - if *left == TypeId::UNDEFINED_TYPE { - *right - } else if *right == TypeId::UNDEFINED_TYPE { - *left - } else { - on - } - } - ty => { - crate::utilities::notify!("{:?}", ty); - if let Some(constraint) = crate::types::get_constraint(on, types) { - get_non_null_type(constraint, types) - } else { - on - } - } - } - } - let cast_to = get_non_null_type(on, types); - - // TODO Type::Narrowed - Ok(types.register_type(Type::RootPolyType(PolyNature::Open(cast_to)))) + pub fn non_null_assertion( + on: TypeId, + environment: &Environment, + types: &mut TypeStore, + ) -> Result { + let mut result = Vec::new(); + super::narrowing::build_union_from_filter( + on, + super::narrowing::NOT_NULL_OR_UNDEFINED, + &mut result, + environment, + types, + ); + let cast_to = types.new_or_type_from_iterator(result); + Ok(types.register_type(Type::Narrowed { narrowed_to: cast_to, from: on })) } pub fn check_satisfies( @@ -485,31 +555,12 @@ pub mod tsc { environment: &mut Environment, checking_data: &mut CheckingData, ) { - pub(crate) fn check_satisfies( - expr_ty: TypeId, - to_satisfy: TypeId, - types: &TypeStore, - environment: &mut Environment, - ) -> bool { - // TODO `behavior.allow_error = true` would be better - if expr_ty == TypeId::ERROR_TYPE { - false - } else { - let mut state = subtyping::State { - already_checked: Default::default(), - mode: Default::default(), - contributions: Default::default(), - others: subtyping::SubTypingOptions { allow_errors: false }, - object_constraints: None, - }; - let result = - subtyping::type_is_subtype(to_satisfy, expr_ty, &mut state, environment, types); - - matches!(result, subtyping::SubTypeResult::IsSubType) - } - } - - if !check_satisfies(expr_ty, to_satisfy, &checking_data.types, environment) { + if !crate::types::helpers::simple_subtype( + expr_ty, + to_satisfy, + environment, + &checking_data.types, + ) { let expected = diagnostics::TypeStringRepresentation::from_type_id( to_satisfy, environment, @@ -528,65 +579,3 @@ pub mod tsc { } } } - -/// WIP -pub(crate) fn has_property( - (publicity, key): (properties::Publicity, &properties::PropertyKey<'_>), - rhs: TypeId, - information: &impl InformationChain, - types: &mut TypeStore, -) -> TypeId { - match types.get_type_by_id(rhs) { - Type::Interface { .. } - | Type::Class { .. } - | Type::Constant(_) - | Type::FunctionReference(_) - | Type::Object(_) - | Type::PartiallyAppliedGenerics(_) - | Type::And(_, _) - | Type::SpecialObject(_) - | Type::AliasTo { .. } => { - let result = properties::get_property_unbound( - (rhs, None), - (publicity, key, None), - false, - information, - types, - ); - match result { - Ok(LogicalOrValid::Logical(result)) => match result { - Logical::Pure(_) => TypeId::TRUE, - Logical::Or { .. } => { - crate::utilities::notify!("or or implies `in`"); - TypeId::ERROR_TYPE - } - Logical::Implies { .. } => { - crate::utilities::notify!("or or implies `in`"); - TypeId::ERROR_TYPE - } - Logical::BasedOnKey { .. } => { - crate::utilities::notify!("mapped in"); - TypeId::ERROR_TYPE - } - }, - Ok(LogicalOrValid::NeedsCalculation(result)) => { - crate::utilities::notify!("TODO {:?}", result); - TypeId::ERROR_TYPE - } - Err(err) => { - crate::utilities::notify!("TODO {:?}", err); - TypeId::FALSE - } - } - } - Type::Or(_, _) => { - crate::utilities::notify!("Condtionally"); - TypeId::ERROR_TYPE - } - Type::RootPolyType(_) | Type::Constructor(_) => { - crate::utilities::notify!("Queue event / create dependent"); - let constraint = get_constraint(rhs, types).unwrap(); - has_property((publicity, key), constraint, information, types) - } - } -} diff --git a/checker/src/features/narrowing.rs b/checker/src/features/narrowing.rs new file mode 100644 index 00000000..312c4291 --- /dev/null +++ b/checker/src/features/narrowing.rs @@ -0,0 +1,470 @@ +use crate::{ + context::InformationChain, + types::{ + self, as_logical_and, as_logical_or, get_conditional, helpers::get_origin, properties, + Constant, Constructor, PolyNature, TypeOperator, TypeStore, + }, + Map, Type, TypeId, +}; + +use super::operations::{CanonicalEqualityAndInequality, MathematicalAndBitwise, PureUnary}; + +pub fn narrow_based_on_expression_into_vec( + condition: TypeId, + negate: bool, + information: &impl InformationChain, + types: &mut TypeStore, +) -> Map { + let mut into = Default::default(); + narrow_based_on_expression(condition, negate, &mut into, information, types); + into +} + +pub fn narrow_based_on_expression( + condition: TypeId, + negate: bool, + into: &mut Map, + information: &impl InformationChain, + types: &mut TypeStore, +) { + let r#type = types.get_type_by_id(condition); + if let Type::Constructor(constructor) = r#type { + match constructor { + Constructor::CanonicalRelationOperator { + lhs, + operator: CanonicalEqualityAndInequality::StrictEqual, + rhs, + } => { + if let Type::Constructor(Constructor::TypeOperator(TypeOperator::TypeOf(on))) = + types.get_type_by_id(*lhs) + { + let from = get_origin(*on, types); + if let Type::Constant(Constant::String(c)) = types.get_type_by_id(*rhs) { + let narrowed_to = crate::features::string_name_to_type(c); + if let Some(narrowed_to) = narrowed_to { + if negate { + let mut result = Vec::new(); + build_union_from_filter( + from, + Filter::Not(&Filter::IsType(narrowed_to)), + &mut result, + information, + types, + ); + let narrowed_to = types.new_or_type_from_iterator(result); + let narrowed = types.new_narrowed(from, narrowed_to); + into.insert(from, narrowed); + } else { + let narrowed = types.new_narrowed(from, narrowed_to); + into.insert(from, narrowed); + } + } else { + crate::utilities::notify!("Type name was (shouldn't be here)"); + } + } else { + crate::utilities::notify!("Here?"); + } + } else if let Type::Constructor(Constructor::BinaryOperator { + lhs: operand, + operator: MathematicalAndBitwise::Modulo, + rhs: modulo, + }) = types.get_type_by_id(*lhs) + { + if *rhs == TypeId::ZERO { + crate::utilities::notify!("TODO only if sensible"); + + let (from, modulo) = (*operand, *modulo); + if negate { + crate::utilities::notify!("TODO do we not divisable by?"); + } else { + let narrowed_to = types.new_intrinsic( + &crate::types::intrinsics::Intrinsic::MultipleOf, + modulo, + ); + let narrowed = types.new_narrowed(from, narrowed_to); + into.insert(from, narrowed); + } + } else { + crate::utilities::notify!("maybe subtract LHS"); + } + } else { + if let Type::RootPolyType(PolyNature::Parameter { .. }) = + types.get_type_by_id(*lhs) + { + crate::utilities::notify!( + "lhs is {:?} with {:?}", + lhs, + types.get_type_by_id(*rhs) + ); + } + + let lhs = get_origin(*lhs, types); + + let result = if negate { + // TODO wip + let narrowed_to = if get_conditional(lhs, types).is_some() { + let mut result = Vec::new(); + build_union_from_filter( + lhs, + Filter::Not(&Filter::IsType(*rhs)), + &mut result, + information, + types, + ); + // crate::utilities::notify!("Here {:?} {:?}", (filter, lhs), result); + + let narrowed_to = types.new_or_type_from_iterator(result); + types.new_narrowed(lhs, narrowed_to) + } else { + types.new_intrinsic(&crate::types::intrinsics::Intrinsic::Not, *rhs) + }; + types.new_narrowed(lhs, narrowed_to) + } else { + *rhs + }; + + into.insert(lhs, result); + + // PROPERTY HERE + if let Type::Constructor(Constructor::Property { + on, + under, + result: _, + mode: _, + }) = types.get_type_by_id(lhs) + { + let on = *on; + let narrowed_to = if !negate + && crate::types::get_constraint(on, types) + .is_some_and(|c| c == TypeId::ANY_TYPE) + { + generate_new_type_with_property(under.into_owned(), result, types) + } else { + let mut items = Vec::new(); + build_union_from_filter( + on, + Filter::HasProperty { + property: under, + filter: &Filter::IsType(result), + }, + &mut items, + information, + types, + ); + types.new_or_type_from_iterator(items) + }; + // crate::utilities::notify!( + // "Here {:?} to {:?}", + // on, + // types.get_type_by_id(narrowed_to) + // ); + into.insert(on, types.new_narrowed(on, narrowed_to)); + } + } + } + Constructor::CanonicalRelationOperator { + lhs, + operator: CanonicalEqualityAndInequality::LessThan, + rhs, + } => { + let (lhs, rhs) = (*lhs, *rhs); + if negate { + return; + } + if types.get_type_by_id(lhs).is_dependent() { + let narrowed_to = + types.new_intrinsic(&crate::types::intrinsics::Intrinsic::LessThan, rhs); + let narrowed = types.new_narrowed(lhs, narrowed_to); + into.insert(lhs, narrowed); + } else if types.get_type_by_id(rhs).is_dependent() { + let narrowed_to = + types.new_intrinsic(&crate::types::intrinsics::Intrinsic::GreaterThan, lhs); + let narrowed = types.new_narrowed(rhs, narrowed_to); + into.insert(rhs, narrowed); + } + } + Constructor::UnaryOperator { operator: PureUnary::LogicalNot, operand } => { + narrow_based_on_expression(*operand, !negate, into, information, types); + } + Constructor::TypeOperator(TypeOperator::IsPrototype { lhs, rhs_prototype }) => { + let (lhs, rhs_prototype) = (*lhs, *rhs_prototype); + let constraint = crate::types::get_constraint(lhs, types).unwrap_or(lhs); + // TODO want a mix of two + let narrowed_to = if !negate && constraint == TypeId::ANY_TYPE { + generate_new_type_with_prototype(rhs_prototype, types) + } else { + let mut result = Vec::new(); + let filter = Filter::HasPrototype(rhs_prototype); + let filter = if negate { Filter::Not(&filter) } else { filter }; + build_union_from_filter(constraint, filter, &mut result, information, types); + types.new_or_type_from_iterator(result) + }; + let narrowed = types.new_narrowed(lhs, narrowed_to); + into.insert(lhs, narrowed); + } + Constructor::TypeExtends(crate::types::TypeExtends { item, extends }) => { + let (item, extends) = (*item, *extends); + // experimental + // TODO want a mix of two + let constraint = crate::types::get_constraint(item, types).unwrap_or(item); + let narrowed_to = if !negate && constraint == TypeId::ANY_TYPE { + extends + } else { + let mut result = Vec::new(); + let filter = Filter::IsType(extends); + let filter = if negate { Filter::Not(&filter) } else { filter }; + build_union_from_filter(constraint, filter, &mut result, information, types); + types.new_or_type_from_iterator(result) + }; + let narrowed = types.new_narrowed(item, narrowed_to); + into.insert(item, narrowed); + } + Constructor::TypeOperator(TypeOperator::HasProperty(on, under)) => { + let on = *on; + let constraint = types::get_constraint(on, types).unwrap_or(on); + let narrowed_to = if !negate && constraint == TypeId::ANY_TYPE { + generate_new_type_with_property(under.into_owned(), TypeId::ANY_TYPE, types) + } else { + let mut items = Vec::new(); + build_union_from_filter( + constraint, + Filter::HasProperty { + property: under, + filter: &Filter::IsType(TypeId::ANY_TYPE), + }, + &mut items, + information, + types, + ); + types.new_or_type_from_iterator(items) + }; + into.insert(on, types.new_narrowed(on, narrowed_to)); + } + constructor => { + if let Some((lhs, rhs)) = as_logical_and(constructor, types) { + // De Morgan's laws + if negate { + // OR: Pull assertions from left and right, merge if both branches assert something + let lhs_requests = + narrow_based_on_expression_into_vec(lhs, negate, information, types); + let rhs_requests = + narrow_based_on_expression_into_vec(rhs, negate, information, types); + + for (on, lhs_request) in lhs_requests { + if let Some(rhs_request) = rhs_requests.get(&on) { + let rhs_request = *rhs_request; + let (lhs_request, rhs_request) = ( + crate::types::get_constraint(lhs_request, types) + .unwrap_or(lhs_request), + crate::types::get_constraint(rhs_request, types) + .unwrap_or(rhs_request), + ); + // TODO + // let narrowed = types.new_narrowed(rhs, narrowed_to); + into.insert(on, types.new_or_type(lhs_request, rhs_request)); + } else { + // Only when we have two results is it useful + } + } + } else { + // AND: Pull assertions from left and right + narrow_based_on_expression(lhs, negate, into, information, types); + narrow_based_on_expression(rhs, negate, into, information, types); + } + } else if let Some((lhs, rhs)) = as_logical_or(constructor, types) { + // De Morgan's laws + if negate { + // AND: Pull assertions from left and right + narrow_based_on_expression(lhs, negate, into, information, types); + narrow_based_on_expression(rhs, negate, into, information, types); + } else { + // OR: Pull assertions from left and right, merge if both branches assert something + let lhs_requests = + narrow_based_on_expression_into_vec(lhs, negate, information, types); + let rhs_requests = + narrow_based_on_expression_into_vec(rhs, negate, information, types); + + for (on, lhs_request) in lhs_requests { + if let Some(rhs_request) = rhs_requests.get(&on) { + let rhs_request = *rhs_request; + let (lhs_request, rhs_request) = ( + crate::types::get_constraint(lhs_request, types) + .unwrap_or(lhs_request), + crate::types::get_constraint(rhs_request, types) + .unwrap_or(rhs_request), + ); + // TODO + // let narrowed = types.new_narrowed(rhs, narrowed_to); + into.insert(on, types.new_or_type(lhs_request, rhs_request)); + } else { + // Only when we have two results is it useful + } + } + } + } else { + crate::utilities::notify!("Here?, {:?}", constructor); + } + } + } + } else if let Type::RootPolyType(rpt) = r#type { + if rpt.get_constraint() == TypeId::BOOLEAN_TYPE { + let result = if negate { TypeId::FALSE } else { TypeId::TRUE }; + into.insert(condition, result); + } else { + crate::utilities::notify!("Set, {:?} as truthy", r#type); + } + } +} + +/// Pseudo type for keeping or removing types +#[derive(Debug, Clone, Copy)] +pub(crate) enum Filter<'a> { + Not(&'a Self), + IsType(TypeId), + HasPrototype(TypeId), + HasProperty { + property: &'a properties::PropertyKey<'a>, + filter: &'a Filter<'a>, + }, + /// For non null assertions and + NullOrUndefined, + Falsy, +} + +static NULL_OR_UNDEFINED: Filter<'static> = Filter::NullOrUndefined; +pub(crate) static NOT_NULL_OR_UNDEFINED: Filter<'static> = Filter::Not(&NULL_OR_UNDEFINED); + +static FASLY: Filter<'static> = Filter::Falsy; +pub(crate) static NOT_FASLY: Filter<'static> = Filter::Not(&FASLY); + +impl<'a> Filter<'a> { + pub(crate) fn type_matches_filter( + &self, + value: TypeId, + information: &impl InformationChain, + types: &TypeStore, + negate: bool, + ) -> bool { + match self { + Filter::Not(filter) => filter.type_matches_filter(value, information, types, !negate), + Filter::IsType(ty) => { + if negate { + types::disjoint::types_are_disjoint( + *ty, + value, + &mut Vec::new(), + information, + types, + ) + } else { + // value has to satisfies ty + types::helpers::simple_subtype(value, *ty, information, types) + } + } + Filter::HasPrototype(prototype) => { + if let Type::PartiallyAppliedGenerics(types::PartiallyAppliedGenerics { + on: gen_on, + arguments: _, + }) = types.get_type_by_id(value) + { + let is_equal = prototype == gen_on; + let allowed_match = !negate; + (allowed_match && is_equal) || (!allowed_match && !is_equal) + } else if let Type::Object(types::ObjectNature::RealDeal) = + types.get_type_by_id(value) + { + // This branch can be triggered by conditionals + let extends = + crate::features::extends_prototype(value, *prototype, information); + let allowed_match = !negate; + (allowed_match && extends) || (!allowed_match && !extends) + } else { + let is_equal = value == *prototype; + let allowed_match = !negate; + (allowed_match && is_equal) || (!allowed_match && !is_equal) + } + } + Filter::HasProperty { property, filter } => { + let value = + types::properties::get_simple_value(information, value, property, types); + if let Some(value) = value { + let matches = filter.type_matches_filter(value, information, types, negate); + crate::utilities::notify!("Value {:?}", (value, negate, matches)); + matches + } else { + negate + } + } + Filter::NullOrUndefined => { + let is_null_or_undefined = + [TypeId::NULL_TYPE, TypeId::UNDEFINED_TYPE].contains(&value); + let allowed_match = !negate; + (allowed_match && is_null_or_undefined) || (!allowed_match && !is_null_or_undefined) + } + Filter::Falsy => { + let is_falsy = [ + TypeId::NULL_TYPE, + TypeId::UNDEFINED_TYPE, + TypeId::FALSE, + TypeId::EMPTY_STRING, + TypeId::ZERO, + ] + .contains(&value); + let allowed_match = !negate; + (allowed_match && is_falsy) || (!allowed_match && !is_falsy) + } + } + } +} + +#[allow(clippy::used_underscore_binding)] +pub(crate) fn build_union_from_filter( + on: TypeId, + filter: Filter, + found: &mut Vec, + information: &impl InformationChain, + types: &TypeStore, +) { + if let Some((_condition, lhs, rhs)) = get_conditional(on, types) { + build_union_from_filter(lhs, filter, found, information, types); + build_union_from_filter(rhs, filter, found, information, types); + } else if let Some(constraint) = crate::types::get_constraint(on, types) { + build_union_from_filter(constraint, filter, found, information, types); + } else { + let not_already_added = !found.contains(&on); + if not_already_added && filter.type_matches_filter(on, information, types, false) { + found.push(on); + } + } +} + +/// Takes `Array` constructor and generates `Array` +pub fn generate_new_type_with_prototype(constructor: TypeId, types: &mut TypeStore) -> TypeId { + use source_map::{Nullable, SpanWithSource}; + + if let Some(parameters) = types.get_type_by_id(constructor).get_parameters() { + let arguments = crate::types::GenericArguments::ExplicitRestrictions( + parameters + .into_iter() + .map(|key| (key, (TypeId::ANY_TYPE, SpanWithSource::NULL))) + .collect(), + ); + types.register_type(Type::PartiallyAppliedGenerics( + crate::types::PartiallyAppliedGenerics { on: constructor, arguments }, + )) + } else { + constructor + } +} + +pub fn generate_new_type_with_property( + key: properties::PropertyKey<'static>, + value: TypeId, + types: &mut TypeStore, +) -> TypeId { + let property = (properties::Publicity::Public, key, properties::PropertyValue::Value(value)); + + types.register_type(Type::Object(crate::types::ObjectNature::AnonymousTypeAnnotation(vec![ + property, + ]))) +} diff --git a/checker/src/features/operations.rs b/checker/src/features/operations.rs index 774c5385..f3443a66 100644 --- a/checker/src/features/operations.rs +++ b/checker/src/features/operations.rs @@ -7,7 +7,7 @@ use crate::{ diagnostics::{TypeCheckError, TypeStringRepresentation}, features::conditional::new_conditional_context, types::{ - cast_as_number, cast_as_string, is_type_truthy_falsy, new_logical_or_type, Constructor, + cast_as_number, cast_as_string, is_type_truthy_falsy, Constructor, PartiallyAppliedGenerics, TypeStore, }, CheckingData, Constant, Decidable, Environment, Type, TypeId, @@ -233,11 +233,13 @@ pub fn evaluate_equality_inequality_operation( EqualityAndInequality::StrictEqual => { // crate::utilities::notify!("{:?} === {:?}", lhs, rhs); - let is_dependent = types.get_type_by_id(lhs).is_dependent() - || types.get_type_by_id(rhs).is_dependent(); + let left_dependent = types.get_type_by_id(lhs).is_dependent(); + let is_dependent = left_dependent || types.get_type_by_id(rhs).is_dependent(); // TODO check lhs and rhs type to see if they overlap if is_dependent { + // Sort if `*constant* == ...`. Ideally want constant type on the RHS + let (lhs, rhs) = if left_dependent { (lhs, rhs) } else { (rhs, rhs) }; let constructor = crate::types::Constructor::CanonicalRelationOperator { lhs, operator: CanonicalEqualityAndInequality::StrictEqual, @@ -316,7 +318,16 @@ pub fn evaluate_equality_inequality_operation( }; types.register_type(crate::Type::Constructor(constructor)) } else { - attempt_less_than(lhs, rhs, types, strict_casts).unwrap() + let less_than_result = attempt_less_than(lhs, rhs, types, strict_casts); + if let Ok(result) = less_than_result { + result + } else { + crate::utilities::notify!( + "Less than unreachable {:?}", + (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) + ); + TypeId::ERROR_TYPE + } } } // equal OR less than @@ -347,7 +358,7 @@ pub fn evaluate_equality_inequality_operation( types, strict_casts, ); - new_logical_or_type(equality_result, less_than_result, types) + types.new_logical_or_type(equality_result, less_than_result) } } EqualityAndInequality::StrictNotEqual => { @@ -403,6 +414,7 @@ pub fn evaluate_equality_inequality_operation( } } +#[allow(clippy::let_and_return)] pub fn is_null_or_undefined(ty: TypeId, types: &mut TypeStore) -> TypeId { let is_null = evaluate_equality_inequality_operation( ty, @@ -411,14 +423,17 @@ pub fn is_null_or_undefined(ty: TypeId, types: &mut TypeStore) -> TypeId { types, false, ); - let is_undefined = evaluate_equality_inequality_operation( - ty, - &EqualityAndInequality::StrictEqual, - TypeId::UNDEFINED_TYPE, - types, - false, - ); - types.new_logical_or_type(is_null, is_undefined) + // TODO temp to fix narrowing + // let is_undefined = evaluate_equality_inequality_operation( + // ty, + // &EqualityAndInequality::StrictEqual, + // TypeId::UNDEFINED_TYPE, + // types, + // false, + // ); + + // types.new_logical_or_type(is_null, is_undefined) + is_null } #[derive(Copy, Clone, Debug)] @@ -455,18 +470,54 @@ pub fn evaluate_logical_operation_with_expression< LogicalOperator::Or => Ok(new_conditional_context( environment, lhs, - |_env: &mut Environment, _data: &mut CheckingData| lhs.0, + |env: &mut Environment, checking_data: &mut CheckingData| { + if let Some(constraint) = crate::types::get_constraint(lhs.0, &checking_data.types) + { + let mut result = Vec::new(); + super::narrowing::build_union_from_filter( + constraint, + super::narrowing::NOT_FASLY, + &mut result, + env, + &checking_data.types, + ); + let narrowed_to = checking_data.types.new_or_type_from_iterator(result); + checking_data.types.register_type(Type::Narrowed { from: lhs.0, narrowed_to }) + } else { + lhs.0 + } + }, Some(|env: &mut Environment, data: &mut CheckingData| { A::synthesise_expression(rhs, expecting, env, data) }), checking_data, )), LogicalOperator::NullCoalescing => { - let null_or_undefined = is_null_or_undefined(lhs.0, &mut checking_data.types); + let is_lhs_null_or_undefined = is_null_or_undefined(lhs.0, &mut checking_data.types); + // Equivalent to: `(lhs is null or undefined) ? lhs : rhs` Ok(new_conditional_context( environment, - (null_or_undefined, lhs.1), - |_env: &mut Environment, _data: &mut CheckingData| lhs.0, + (is_lhs_null_or_undefined, lhs.1), + |env: &mut Environment, checking_data: &mut CheckingData| { + if let Some(constraint) = + crate::types::get_constraint(lhs.0, &checking_data.types) + { + let mut result = Vec::new(); + super::narrowing::build_union_from_filter( + constraint, + super::narrowing::NOT_NULL_OR_UNDEFINED, + &mut result, + env, + &checking_data.types, + ); + let narrowed_to = checking_data.types.new_or_type_from_iterator(result); + checking_data + .types + .register_type(Type::Narrowed { from: lhs.0, narrowed_to }) + } else { + lhs.0 + } + }, Some(|env: &mut Environment, data: &mut CheckingData| { A::synthesise_expression(rhs, expecting, env, data) }), diff --git a/checker/src/lib.rs b/checker/src/lib.rs index ab04dd07..f0faebba 100644 --- a/checker/src/lib.rs +++ b/checker/src/lib.rs @@ -172,7 +172,13 @@ pub trait ASTImplementation: Sized { fn parameter_constrained<'a>(parameter: &'a Self::TypeParameter<'a>) -> bool; - fn parse_options(is_js: bool, parse_comments: bool, lsp_mode: bool) -> Self::ParseOptions; + #[allow(clippy::fn_params_excessive_bools)] + fn parse_options( + is_js: bool, + extra_syntax: bool, + parse_comments: bool, + lsp_mode: bool, + ) -> Self::ParseOptions; fn owned_module_from_module(m: Self::Module<'static>) -> Self::OwnedModule; @@ -317,7 +323,6 @@ pub struct CheckingData<'a, FSResolver, ModuleAST: ASTImplementation> { pub(crate) modules: ModuleData<'a, FSResolver, ModuleAST>, /// Options for checking pub(crate) options: TypeCheckOptions, - // pub(crate) parse_options: parser::ParseOptions, // pub(crate) events: EventsStore, pub types: TypeStore, @@ -577,6 +582,7 @@ fn parse_source( let parse_options = A::parse_options( is_js, + checking_data.options.extra_syntax, checking_data.options.parse_comments, checking_data.options.lsp_mode, ); diff --git a/checker/src/options.rs b/checker/src/options.rs index fdf792db..00a39872 100644 --- a/checker/src/options.rs +++ b/checker/src/options.rs @@ -29,6 +29,9 @@ pub struct TypeCheckOptions { /// For post type check optimisations and LSP. Stores both expressions and type annotations pub store_type_mappings: bool, + /// ? + pub extra_syntax: bool, + /// TODO WIP pub parse_comments: bool, @@ -74,6 +77,7 @@ impl Default for TypeCheckOptions { max_inline_count: 300, measure_time: false, debug_dts: false, + extra_syntax: true, } } } diff --git a/checker/src/synthesis/block.rs b/checker/src/synthesis/block.rs index e1f36766..9aba8f14 100644 --- a/checker/src/synthesis/block.rs +++ b/checker/src/synthesis/block.rs @@ -33,7 +33,7 @@ pub(super) fn synthesise_block( } // TODO conditionals and more etc - if environment.info.is_halted() { + if environment.info.is_finished() { break; } } diff --git a/checker/src/synthesis/expressions.rs b/checker/src/synthesis/expressions.rs index 9b5ec7b4..8268c85b 100644 --- a/checker/src/synthesis/expressions.rs +++ b/checker/src/synthesis/expressions.rs @@ -965,16 +965,19 @@ pub(super) fn synthesise_expression( SpecialOperators::NonNullAssertion(on) => { let lhs = synthesise_expression(on, environment, checking_data, expecting); Instance::RValue( - features::tsc::non_null_assertion(lhs, &mut checking_data.types).unwrap(), + features::tsc::non_null_assertion(lhs, environment, &mut checking_data.types) + .unwrap(), ) } - SpecialOperators::Is { value: _, type_annotation: _ } => { - // Special non-standard - checking_data.raise_unimplemented_error( - "is expression", - position.with_source(environment.get_source()), - ); - return TypeId::ERROR_TYPE; + SpecialOperators::Is { value, type_annotation } => { + let item = + synthesise_expression(value, environment, checking_data, TypeId::ANY_TYPE); + Instance::RValue(super::extensions::is_expression::new_is_type( + item, + type_annotation, + environment, + checking_data, + )) } }, Expression::ImportMeta(_) => { @@ -1262,17 +1265,8 @@ pub(super) fn synthesise_object_literal( } } } - crate::Type::AliasTo { .. } - | crate::Type::And { .. } - | crate::Type::Or { .. } - | crate::Type::RootPolyType { .. } - | crate::Type::Constructor { .. } - | crate::Type::PartiallyAppliedGenerics { .. } - | crate::Type::Interface { .. } - | crate::Type::Class { .. } - | crate::Type::Constant { .. } - | crate::Type::FunctionReference { .. } - | crate::Type::SpecialObject(_) => { + r#type => { + crate::utilities::notify!("more than binary spread {:?}", r#type); checking_data.raise_unimplemented_error( "more than binary spread", pos.with_source(environment.get_source()), diff --git a/checker/src/synthesis/extensions/is_expression.rs b/checker/src/synthesis/extensions/is_expression.rs index 0b68d611..23eff9f0 100644 --- a/checker/src/synthesis/extensions/is_expression.rs +++ b/checker/src/synthesis/extensions/is_expression.rs @@ -1,5 +1,5 @@ use crate::{ - context::Environment, + context::{Environment, Scope}, synthesis::{ expressions::synthesise_multiple_expression, functions::SynthesisableFunctionBody, type_annotations::synthesise_type_annotation, @@ -12,36 +12,61 @@ pub(crate) fn synthesise_is_expression( environment: &mut Environment, checking_data: &mut CheckingData, ) -> TypeId { - // TODO expecting - let _matcher = synthesise_multiple_expression( + let matcher = synthesise_multiple_expression( &is_expression.matcher, environment, checking_data, TypeId::ANY_TYPE, ); - let returned = TypeId::UNDEFINED_TYPE; - for (condition, code) in &is_expression.branches { - let _requirement = synthesise_type_annotation(condition, environment, checking_data); + let mut returned = None; + // TODO expecting + for (condition, code) in &is_expression.branches { // TODO need to test subtyping and subtype here // TODO move proofs here - code.synthesise_function_body(environment, checking_data); + environment.new_lexical_environment_fold_into_parent( + Scope::Block {}, + checking_data, + |environment, checking_data| { + let requirement = synthesise_type_annotation(condition, environment, checking_data); + // todo!("disjoint"); + // TODO extract named members as variables - // let code_returns = code.synthesise_function_body(environment, checking_data); + let narrowed = checking_data.types.new_narrowed(matcher, requirement); + environment.info.narrowed_values.insert(matcher, narrowed); - // let on = todo!("Need to turn Type into binary operation?"); + code.synthesise_function_body(environment, checking_data); - // let ty = Type::Constructor(crate::types::Constructor::ConditionalTernary { - // on, - // true_res: code_returns, - // false_res: returned, - // result_union: todo!(), - // }); + // TODO this should be done outside + let result = environment.info.state.clone().get_returned(&mut checking_data.types); - // returned = checking_data.types.register_type(ty); + returned = if let Some(existing) = returned { + // TODO new conditional + Some(checking_data.types.new_or_type(existing, result)) + } else { + Some(result) + }; + }, + ); } - returned + // TODO check every case covered + + returned.unwrap_or(TypeId::UNDEFINED_TYPE) +} + +pub(crate) fn new_is_type( + item: TypeId, + rhs: &parser::TypeAnnotation, + environment: &mut Environment, + checking_data: &mut CheckingData, +) -> TypeId { + use crate::types::{Constructor, Type, TypeExtends}; + + crate::utilities::notify!("{:?}", checking_data.types.get_type_by_id(item)); + let extends = synthesise_type_annotation(rhs, environment, checking_data); + let ty = Type::Constructor(Constructor::TypeExtends(TypeExtends { item, extends })); + checking_data.types.register_type(ty) } diff --git a/checker/src/synthesis/interfaces.rs b/checker/src/synthesis/interfaces.rs index 60817370..7d019f71 100644 --- a/checker/src/synthesis/interfaces.rs +++ b/checker/src/synthesis/interfaces.rs @@ -63,6 +63,68 @@ pub(crate) enum InterfaceValue { pub(crate) struct OnToType(pub(crate) TypeId); +fn register( + key: &InterfaceKey, + (value, always_defined, writable): (InterfaceValue, IsDefined, Writable), + checking_data: &mut CheckingData, + environment: &mut Environment, + _position: SpanWithSource, +) -> (Publicity, PropertyKey<'static>, PropertyValue) { + let (publicity, under) = match key { + InterfaceKey::ClassProperty(key) => { + // TODO + let perform_side_effect_computed = true; + ( + if key.is_private() { Publicity::Private } else { Publicity::Public }, + parser_property_key_to_checker_property_key( + key, + environment, + checking_data, + perform_side_effect_computed, + ), + ) + } + InterfaceKey::Type(ty) => (Publicity::Public, PropertyKey::Type(*ty)), + }; + let value = match value { + InterfaceValue::Function(function, getter_setter) => match getter_setter { + Some(GetterSetter::Getter) => PropertyValue::Getter(Callable::new_from_function( + function, + &mut checking_data.types, + )), + Some(GetterSetter::Setter) => PropertyValue::Setter(Callable::new_from_function( + function, + &mut checking_data.types, + )), + None => { + let function_id = function.id; + checking_data.types.functions.insert(function.id, function); + let ty = Type::FunctionReference(function_id); + PropertyValue::Value(checking_data.types.register_type(ty)) + } + }, + InterfaceValue::Value(value) => PropertyValue::Value(value), + }; + let value = if let Writable(TypeId::TRUE) = writable { + value + } else { + let descriptor = Descriptor { + writable: writable.0, + enumerable: TypeId::TRUE, + configurable: TypeId::TRUE, + }; + PropertyValue::Configured { on: Box::new(value), descriptor } + }; + // optional properties (`?:`) is implemented here: + let value = if let IsDefined(TypeId::TRUE) = always_defined { + value + } else { + // crate::utilities::notify!("always_defined.0 {:?}", always_defined.0); + PropertyValue::ConditionallyExists { condition: always_defined.0, truthy: Box::new(value) } + }; + (publicity, under, value) +} + impl SynthesiseInterfaceBehavior for OnToType { fn register( &mut self, @@ -70,63 +132,10 @@ impl SynthesiseInterfaceBehavior for OnToType { (value, always_defined, writable): (InterfaceValue, IsDefined, Writable), checking_data: &mut CheckingData, environment: &mut Environment, - _position: SpanWithSource, + position: SpanWithSource, ) { - let (publicity, under) = match key { - InterfaceKey::ClassProperty(key) => { - // TODO - let perform_side_effect_computed = true; - ( - if key.is_private() { Publicity::Private } else { Publicity::Public }, - parser_property_key_to_checker_property_key( - key, - environment, - checking_data, - perform_side_effect_computed, - ), - ) - } - InterfaceKey::Type(ty) => (Publicity::Public, PropertyKey::Type(ty)), - }; - let value = - match value { - InterfaceValue::Function(function, getter_setter) => match getter_setter { - Some(GetterSetter::Getter) => PropertyValue::Getter( - Callable::new_from_function(function, &mut checking_data.types), - ), - Some(GetterSetter::Setter) => PropertyValue::Setter( - Callable::new_from_function(function, &mut checking_data.types), - ), - None => { - let function_id = function.id; - checking_data.types.functions.insert(function.id, function); - let ty = Type::FunctionReference(function_id); - PropertyValue::Value(checking_data.types.register_type(ty)) - } - }, - InterfaceValue::Value(value) => PropertyValue::Value(value), - }; - let value = if let Writable(TypeId::TRUE) = writable { - value - } else { - let descriptor = Descriptor { - writable: writable.0, - enumerable: TypeId::TRUE, - configurable: TypeId::TRUE, - }; - PropertyValue::Configured { on: Box::new(value), descriptor } - }; - // optional properties (`?:`) is implemented here: - let value = if let IsDefined(TypeId::TRUE) = always_defined { - value - } else { - // crate::utilities::notify!("always_defined.0 {:?}", always_defined.0); - PropertyValue::ConditionallyExists { - condition: always_defined.0, - truthy: Box::new(value), - } - }; - + let (publicity, under, value) = + register(&key, (value, always_defined, writable), checking_data, environment, position); environment.info.register_property_on_type(self.0, publicity, under, value); } @@ -135,6 +144,27 @@ impl SynthesiseInterfaceBehavior for OnToType { } } +pub struct PropertiesList(pub crate::types::properties::Properties); + +impl SynthesiseInterfaceBehavior for PropertiesList { + fn register( + &mut self, + key: InterfaceKey, + (value, always_defined, writable): (InterfaceValue, IsDefined, Writable), + checking_data: &mut CheckingData, + environment: &mut Environment, + position: SpanWithSource, + ) { + let (publicity, under, value) = + register(&key, (value, always_defined, writable), checking_data, environment, position); + self.0.push((publicity, under, value)); + } + + fn interface_type(&self) -> Option { + None + } +} + pub(super) fn synthesise_signatures( type_parameters: Option<&[parser::TypeParameter]>, extends: Option<&[parser::TypeAnnotation]>, diff --git a/checker/src/synthesis/mod.rs b/checker/src/synthesis/mod.rs index 1106eca4..1475a8b5 100644 --- a/checker/src/synthesis/mod.rs +++ b/checker/src/synthesis/mod.rs @@ -149,7 +149,12 @@ impl crate::ASTImplementation for EznoParser { synthesise_type_annotation(annotation, environment, checking_data) } - fn parse_options(is_js: bool, parse_comments: bool, lsp_mode: bool) -> Self::ParseOptions { + fn parse_options( + is_js: bool, + extra_syntax: bool, + parse_comments: bool, + lsp_mode: bool, + ) -> Self::ParseOptions { parser::ParseOptions { comments: if parse_comments { parser::Comments::JustDocumentation @@ -158,6 +163,7 @@ impl crate::ASTImplementation for EznoParser { }, type_annotations: !is_js, partial_syntax: lsp_mode, + is_expressions: extra_syntax, ..Default::default() } } diff --git a/checker/src/synthesis/type_annotations.rs b/checker/src/synthesis/type_annotations.rs index ac47458e..a683302c 100644 --- a/checker/src/synthesis/type_annotations.rs +++ b/checker/src/synthesis/type_annotations.rs @@ -405,20 +405,16 @@ pub fn synthesise_type_annotation( // Object literals are first turned into types as if they were interface declarations and then // returns reference to object literal TypeAnnotation::ObjectLiteral(members, _) => { - // TODO rather than onto, generate a new type... - let onto = checking_data - .types - .register_type(Type::Object(crate::types::ObjectNature::AnonymousTypeAnnotation)); - - super::interfaces::synthesise_signatures( + let properties = super::interfaces::synthesise_signatures( None, None, members, - super::interfaces::OnToType(onto), + super::interfaces::PropertiesList(Vec::new()), environment, checking_data, - ) - .0 + ); + + checking_data.types.new_anonymous_interface_type(properties.0) } TypeAnnotation::TupleLiteral(members, position) => { let mut items = Vec::<(SpanWithSource, ArrayItem)>::with_capacity(members.len()); @@ -619,9 +615,10 @@ pub fn synthesise_type_annotation( synthesise_type_annotation(resolve_false, environment, checking_data); // TODO WIP - if let Type::Constructor(Constructor::TypeRelationOperator( - crate::types::TypeRelationOperator::Extends { item, extends }, - )) = checking_data.types.get_type_by_id(condition) + if let Type::Constructor(Constructor::TypeExtends(crate::types::TypeExtends { + item, + extends, + })) = checking_data.types.get_type_by_id(condition) { if let Type::Constant(_) = checking_data.types.get_type_by_id(*item) { use crate::types::generics::substitution::{ @@ -735,28 +732,40 @@ pub fn synthesise_type_annotation( TypeAnnotation::Extends { item, extends, position: _ } => { let item = synthesise_type_annotation(item, environment, checking_data); let extends = synthesise_type_annotation(extends, environment, checking_data); - let ty = Type::Constructor(Constructor::TypeRelationOperator( - crate::types::TypeRelationOperator::Extends { item, extends }, - )); + let ty = Type::Constructor(Constructor::TypeExtends(crate::types::TypeExtends { + item, + extends, + })); checking_data.types.register_type(ty) } - TypeAnnotation::Is { reference: _, is: _, position } => { - // let _item = environment.get_reference_constraint( - // crate::features::assignments::Reference::Variable( - // reference.clone(), - // position.with_source(environment.get_source()), - // ), - // ); - // let _is = synthesise_type_annotation(is, environment, checking_data); - checking_data.raise_unimplemented_error( - "is type annotation", - position.with_source(environment.get_source()), - ); - TypeId::ERROR_TYPE - // let ty = Type::Constructor(Constructor::TypeRelationOperator( - // crate::types::TypeRelationOperator::E { ty: item, is }, - // )); - // checking_data.types.register_type(ty) + TypeAnnotation::Is { reference, is, position: _ } => { + let item_type = match reference { + parser::type_annotations::IsItem::Reference(name) => environment + .variables + .get(name) + .and_then(|variable| { + environment + .info + .variable_current_value + .get(&variable.get_origin_variable_id()) + }) + .copied(), + parser::type_annotations::IsItem::This => { + // TODO + let based_on = TypeId::ERROR_TYPE; + let ty = Type::RootPolyType(crate::types::PolyNature::FreeVariable { + reference: crate::events::RootReference::This, + based_on, + }); + Some(checking_data.types.register_type(ty)) + } + }; + if let Some(item) = item_type { + super::extensions::is_expression::new_is_type(item, is, environment, checking_data) + } else { + crate::utilities::notify!("Here, it is fine for hoisting?"); + TypeId::ERROR_TYPE + } } TypeAnnotation::Symbol { name, unique: _unique, position: _ } => { // TODO what does unique do? @@ -772,13 +781,18 @@ pub fn synthesise_type_annotation( TypeId::SYMBOL_TYPE } } - TypeAnnotation::Asserts(_, position) => { - // TODO construct condition for never - checking_data.raise_unimplemented_error( - "asserts annotation", - position.with_source(environment.get_source()), - ); - TypeId::ERROR_TYPE + TypeAnnotation::Asserts(condition, _position) => { + let condition = synthesise_type_annotation(condition, environment, checking_data); + let ty = Type::Constructor(Constructor::ConditionalResult { + condition, + truthy_result: TypeId::ANY_TYPE, + otherwise_result: TypeId::NEVER_TYPE, + // TODO not needed + result_union: TypeId::ERROR_TYPE, + }); + checking_data.types.register_type(ty) + // crate::types::TypeExtends { item, extends }, + // )); } TypeAnnotation::This(position) => { checking_data.raise_unimplemented_error( diff --git a/checker/src/synthesis/variables.rs b/checker/src/synthesis/variables.rs index 477b956c..236d890f 100644 --- a/checker/src/synthesis/variables.rs +++ b/checker/src/synthesis/variables.rs @@ -168,7 +168,7 @@ pub(crate) fn register_variable( let initial_value = argument.initial_value; let space = if let Some(space) = argument.space { - let rest = checking_data.types.new_anonymous_interface_type(); + let mut rest = Vec::new(); for (publicity, key, property) in get_properties_on_single_type( space, &checking_data.types, @@ -181,10 +181,14 @@ pub(crate) fn register_variable( continue; } } - - environment.info.register_property_on_type(rest, publicity, key, property); + rest.push((publicity, key, property)); + } + if rest.is_empty() { + None + } else { + let rest = checking_data.types.new_anonymous_interface_type(rest); + Some(rest) } - Some(rest) } else { None }; diff --git a/checker/src/types/calling.rs b/checker/src/types/calling.rs index 262e9498..e3b93222 100644 --- a/checker/src/types/calling.rs +++ b/checker/src/types/calling.rs @@ -280,12 +280,12 @@ pub fn call_type_handle_errors { - crate::utilities::notify!("calling proxy"); + crate::utilities::notify!("TODO calling proxy"); (TypeId::ERROR_TYPE, None) } }, @@ -493,6 +493,9 @@ fn get_logical_callable_from_type( } .into()) } + Type::Narrowed { narrowed_to, from } => { + get_logical_callable_from_type(*narrowed_to, on, Some(*from), types) + } Type::AliasTo { to, name: _, parameters } => { if parameters.is_some() { todo!() @@ -945,14 +948,14 @@ fn mark_possible_mutation( | Type::Class { .. } | Type::AliasTo { .. } | Type::And(_, _) - | Type::Object(ObjectNature::AnonymousTypeAnnotation) + | Type::Object(ObjectNature::AnonymousTypeAnnotation(_)) | Type::FunctionReference(_) | Type::PartiallyAppliedGenerics(_) | Type::Or(_, _) => { crate::utilities::notify!("Unreachable"); } Type::Constant(_) => {} - Type::RootPolyType(_) | Type::Constructor(_) => { + Type::Narrowed { .. } | Type::RootPolyType(_) | Type::Constructor(_) => { // All dependent anyway crate::utilities::notify!("TODO if any properties set etc"); } @@ -2020,18 +2023,18 @@ fn synthesise_argument_expressions_wrt_parameters { + Type::FunctionReference(..) | Type::Object(ObjectNature::AnonymousTypeAnnotation(_)) => { // crate::utilities::notify!("{:?}", arguments.arguments); // Apply curring if arguments.arguments.is_empty() { @@ -206,6 +206,7 @@ pub(crate) fn substitute( let rhs = substitute(rhs, arguments, environment, types); types.new_or_type(lhs, rhs) } + Type::Narrowed { from, .. } => substitute(*from, arguments, environment, types), Type::RootPolyType(nature) => { if let PolyNature::Open(_) | PolyNature::Error(_) = nature { id @@ -263,9 +264,10 @@ pub(crate) fn substitute( result_union: _, } => { // TSC behavior, use `compute_extends_rule` which does distribution - if let Type::Constructor(Constructor::TypeRelationOperator( - crate::types::TypeRelationOperator::Extends { item, extends }, - )) = types.get_type_by_id(condition) + if let Type::Constructor(Constructor::TypeExtends(crate::types::TypeExtends { + item, + extends, + })) = types.get_type_by_id(condition) { // crate::utilities::notify!("Here!"); @@ -372,7 +374,7 @@ pub(crate) fn substitute( })) } } else if let Type::Interface { .. } - | Type::Object(ObjectNature::AnonymousTypeAnnotation) + | Type::Object(ObjectNature::AnonymousTypeAnnotation(_)) | Type::AliasTo { .. } = on_type { // TODO union ors etc @@ -421,11 +423,12 @@ pub(crate) fn substitute( } } } else { - todo!( + crate::utilities::notify!( "Constructor::Property ({:?}[{:?}]) should be covered by events", on_type, under ); + TypeId::ERROR_TYPE } } Constructor::Image { .. } => { @@ -479,80 +482,86 @@ pub(crate) fn substitute( let ty = substitute(ty, arguments, environment, types); crate::features::type_of_operator(ty, types) } - crate::types::TypeOperator::PrototypeOf(_) => { - unreachable!("'PrototypeOf' should be specialised by events") - } crate::types::TypeOperator::HasProperty(_, _) => { unreachable!("'HasProperty' should be specialised by events") } - }, - Constructor::TypeRelationOperator(op) => match op { - crate::types::TypeRelationOperator::Extends { item, extends } => { - let before = item; - let item = substitute(item, arguments, environment, types); - let extends = substitute(extends, arguments, environment, types); - - { - use crate::types::printing::print_type; - - crate::utilities::notify!( - "Subtyping {} (prev {}) :>= {} ({:?})", - print_type(item, types, environment, true), - print_type(before, types, environment, true), - print_type(extends, types, environment, true), - &arguments.arguments - ); - } - - let mut state = crate::subtyping::State { - already_checked: Default::default(), - mode: Default::default(), - contributions: None, - object_constraints: None, - others: Default::default(), - }; - let result = crate::subtyping::type_is_subtype( - extends, - item, - &mut state, + crate::types::TypeOperator::IsPrototype { lhs, rhs_prototype } => { + let lhs = substitute(lhs, arguments, environment, types); + crate::features::instance_of_operator_rhs_prototype( + lhs, + rhs_prototype, environment, types, + ) + // unreachable!("'HasProperty' should be specialised by events") + } + }, + Constructor::TypeExtends(op) => { + let crate::types::TypeExtends { item, extends } = op; + let before = item; + let item = substitute(item, arguments, environment, types); + let extends = substitute(extends, arguments, environment, types); + + { + use crate::types::printing::print_type; + + crate::utilities::notify!( + "Subtyping {} (prev {}) :>= {} ({:?})", + print_type(item, types, environment, true), + print_type(before, types, environment, true), + print_type(extends, types, environment, true), + &arguments.arguments ); + } - // let base = crate::types::get_larger_type(item, types); - // let does_extend = base == extends; - // crate::utilities::notify!( - // "Extends result {:?} base={:?} extends={:?}", - // does_extend, - // crate::types::printing::print_type(base, types, environment, true), - // crate::types::printing::print_type(extends, types, environment, true) - // ); - if result.is_subtype() { - TypeId::TRUE - } else { - TypeId::FALSE - } + let mut state = crate::subtyping::State { + already_checked: Default::default(), + mode: Default::default(), + contributions: None, + object_constraints: None, + others: Default::default(), + }; + let result = crate::subtyping::type_is_subtype( + extends, + item, + &mut state, + environment, + types, + ); - // let does_extend = get_larger_type(ty, types) == extends; - // crate::utilities::notify!("Extends result {:?}", does_extend); - // if does_extend { - // TypeId::TRUE - // } else { - // TypeId::FALSE - // } + // let base = crate::types::get_larger_type(item, types); + // let does_extend = base == extends; + // crate::utilities::notify!( + // "Extends result {:?} base={:?} extends={:?}", + // does_extend, + // crate::types::printing::print_type(base, types, environment, true), + // crate::types::printing::print_type(extends, types, environment, true) + // ); + if result.is_subtype() { + TypeId::TRUE + } else { + TypeId::FALSE + } - // TODO special behavior that doesn't need to collect errors... - // let result = type_is_subtype(extends, ty, environment, types); + // let does_extend = get_larger_type(ty, types) == extends; + // crate::utilities::notify!("Extends result {:?}", does_extend); + // if does_extend { + // TypeId::TRUE + // } else { + // TypeId::FALSE + // } - // let does_extend = matches!(result, crate::subtyping::SubTypeResult::IsSubType); + // TODO special behavior that doesn't need to collect errors... + // let result = type_is_subtype(extends, ty, environment, types); - // if does_extend { - // TypeId::TRUE - // } else { - // TypeId::FALSE - // } - } - }, + // let does_extend = matches!(result, crate::subtyping::SubTypeResult::IsSubType); + + // if does_extend { + // TypeId::TRUE + // } else { + // TypeId::FALSE + // } + } Constructor::Awaited { .. } => todo!("should have effect result"), Constructor::KeyOf(on) => { let on = substitute(on, arguments, environment, types); diff --git a/checker/src/types/mod.rs b/checker/src/types/mod.rs index 02eeaccf..8d3dab29 100644 --- a/checker/src/types/mod.rs +++ b/checker/src/types/mod.rs @@ -12,33 +12,35 @@ pub mod store; pub mod subtyping; mod terms; -use derive_debug_extras::DebugExtras; +pub(crate) use self::{ + casts::*, + generics::contributions::CovariantContribution, + generics::{ + chain::{GenericChain, GenericChainLink}, + substitution::*, + }, + properties::AccessMode, + properties::*, +}; -use derive_enum_from_into::EnumFrom; -pub(crate) use generics::{ - chain::{GenericChain, GenericChainLink}, - substitution::*, +pub use self::{ + functions::*, generics::generic_type_arguments::GenericArguments, store::TypeStore, + terms::Constant, }; pub use crate::features::objects::SpecialObject; -pub(crate) use casts::*; -pub use logical::*; -use source_map::SpanWithSource; -pub use store::TypeStore; -pub use terms::Constant; use crate::{ context::InformationChain, events::RootReference, features::operations::{CanonicalEqualityAndInequality, MathematicalAndBitwise, PureUnary}, subtyping::SliceArguments, - types::{generics::contributions::CovariantContribution, properties::AccessMode}, Decidable, FunctionId, }; -pub use self::functions::*; - -use self::{generics::generic_type_arguments::GenericArguments, properties::PropertyKey}; +use derive_debug_extras::DebugExtras; +use derive_enum_from_into::EnumFrom; +use source_map::SpanWithSource; pub type ExplicitTypeArgument = (TypeId, SpanWithSource); @@ -158,6 +160,21 @@ impl TypeId { #[derive(Debug, binary_serialize_derive::BinarySerializable)] pub enum Type { + /// *Dependent equality types* + Constant(crate::Constant), + /// Technically could be just a function but... + Object(ObjectNature), + SpecialObject(SpecialObject), + + /// From a parameter, introduces a set of types > 1 + RootPolyType(PolyNature), + /// Also a "Substituted constructor type" + Constructor(Constructor), + Narrowed { + /// Either `RootPolyType` or `Constructor` + from: TypeId, + narrowed_to: TypeId, + }, /// For /// - `extends` interface part (for multiple it to is a `Type::And`) /// - Can be used for subtypes (aka N aliases number then more types on top) @@ -167,15 +184,6 @@ pub enum Type { name: String, parameters: Option>, }, - And(TypeId, TypeId), - Or(TypeId, TypeId), - RootPolyType(PolyNature), - /// Also a "Substituted constructor type" - Constructor(Constructor), - - /// **e.g `Array` - PartiallyAppliedGenerics(PartiallyAppliedGenerics), - /// For number and other rooted types /// /// Although they all alias Object @@ -189,15 +197,16 @@ pub enum Type { name: String, type_parameters: Option>, }, - /// *Dependent equality types* - Constant(crate::Constant), - /// From a type annotation or .d.ts WITHOUT body. e.g. don't know effects TODO... FunctionReference(FunctionId), - /// Technically could be just a function but... - Object(ObjectNature), - SpecialObject(SpecialObject), + /// **e.g `Array` + PartiallyAppliedGenerics(PartiallyAppliedGenerics), + + /// Has properties of both sides + And(TypeId, TypeId), + /// Has properties of either of sides + Or(TypeId, TypeId), } /// TODO difference between untyped and typed parameters and what about parameter based for any @@ -281,12 +290,12 @@ impl PolyNature { } /// TODO split -#[derive(Copy, Clone, Debug, binary_serialize_derive::BinarySerializable)] +#[derive(Clone, Debug, binary_serialize_derive::BinarySerializable)] pub enum ObjectNature { /// Actual allocated object RealDeal, /// From `x: { a: number }` etc - AnonymousTypeAnnotation, + AnonymousTypeAnnotation(Properties), } impl Type { @@ -310,7 +319,7 @@ impl Type { // TODO Type::PartiallyAppliedGenerics(..) => false, // Fine - Type::Constructor(_) | Type::RootPolyType(_) => true, + Type::Narrowed { .. } | Type::Constructor(_) | Type::RootPolyType(_) => true, // TODO what about if left or right Type::And(_, _) | Type::Or(_, _) => false, // TODO what about if it aliases dependent? @@ -364,8 +373,10 @@ pub enum Constructor { operator: PureUnary, operand: TypeId, }, + /// JS type based operations TypeOperator(TypeOperator), - TypeRelationOperator(TypeRelationOperator), + /// TS operation + TypeExtends(TypeExtends), /// TODO constraint is res ConditionalResult { /// TODO this can only be poly types @@ -374,7 +385,7 @@ pub enum Constructor { otherwise_result: TypeId, result_union: TypeId, }, - /// output of a function where on is dependent (or sometimes for const functions one of `with` is dependent) + /// Output of a function where on is dependent (or sometimes for const functions one of `with` is dependent) Image { on: TypeId, // TODO I don't think this is necessary, maybe for debugging. In such case should be an Rc to share with events @@ -384,7 +395,7 @@ pub enum Constructor { /// Access Property { on: TypeId, - under: PropertyKey<'static>, + under: properties::PropertyKey<'static>, result: TypeId, mode: AccessMode, }, @@ -407,7 +418,7 @@ impl Constructor { Constructor::BinaryOperator { .. } | Constructor::CanonicalRelationOperator { .. } | Constructor::UnaryOperator { .. } - | Constructor::TypeRelationOperator(_) + | Constructor::TypeExtends(_) | Constructor::TypeOperator(_) => None, // TODO or symbol Constructor::KeyOf(_) => Some(TypeId::STRING_TYPE), @@ -415,6 +426,47 @@ impl Constructor { } } +#[must_use] +pub fn as_logical_and(constructor: &Constructor, types: &TypeStore) -> Option<(TypeId, TypeId)> { + if let Constructor::ConditionalResult { + condition, + truthy_result, + otherwise_result, + result_union: _, + } = constructor + { + let (condition, truthy_result, otherwise_result) = ( + helpers::get_origin(*condition, types), + helpers::get_origin(*truthy_result, types), + helpers::get_origin(*otherwise_result, types), + ); + (condition == otherwise_result).then_some((truthy_result, otherwise_result)) + } else { + None + } +} + +#[must_use] +pub fn as_logical_or(constructor: &Constructor, types: &TypeStore) -> Option<(TypeId, TypeId)> { + if let Constructor::ConditionalResult { + condition, + truthy_result, + otherwise_result, + result_union: _, + } = constructor + { + let (condition, truthy_result, otherwise_result) = ( + helpers::get_origin(*condition, types), + helpers::get_origin(*truthy_result, types), + helpers::get_origin(*otherwise_result, types), + ); + (condition == truthy_result || truthy_result == TypeId::TRUE) + .then_some((condition, otherwise_result)) + } else { + None + } +} + /// Closed over arguments #[derive(Clone, Debug, binary_serialize_derive::BinarySerializable)] pub struct PartiallyAppliedGenerics { @@ -424,21 +476,89 @@ pub struct PartiallyAppliedGenerics { #[derive(Clone, Debug, binary_serialize_derive::BinarySerializable)] pub enum TypeOperator { - /// Gets the prototype - PrototypeOf(TypeId), + IsPrototype { + lhs: TypeId, + rhs_prototype: TypeId, + }, /// The `typeof` unary operator TypeOf(TypeId), HasProperty(TypeId, properties::PropertyKey<'static>), } -/// TODO instance of? -#[derive(Clone, Debug, binary_serialize_derive::BinarySerializable)] -pub enum TypeRelationOperator { - Extends { item: TypeId, extends: TypeId }, +/// This if from the type annotation `is` +#[derive(Clone, Copy, Debug, binary_serialize_derive::BinarySerializable)] +pub struct TypeExtends { + pub item: TypeId, + pub extends: TypeId, } -pub(crate) fn new_logical_or_type(lhs: TypeId, rhs: TypeId, types: &mut TypeStore) -> TypeId { - types.new_conditional_type(lhs, lhs, rhs) +impl TypeExtends { + /// This does `typeof`, `===` and `instanceof` + /// + /// Because of type representation and the fact this cannot generate types the following are not covered + /// - negation. Cannot create `Not` + /// - number intrinsincs. Cannot create `LessThan`, `GreaterThan`, etc + /// - has property. Cannot create new object + pub fn from_type(rhs: TypeId, types: &TypeStore) -> Result { + let rhs_ty = types.get_type_by_id(rhs); + if let Type::Constructor(Constructor::CanonicalRelationOperator { + lhs, + operator: CanonicalEqualityAndInequality::StrictEqual, + rhs, + }) = rhs_ty + { + if let ( + Type::Constructor(Constructor::TypeOperator(TypeOperator::TypeOf(on))), + Type::Constant(Constant::String(s)), + ) = (types.get_type_by_id(*lhs), types.get_type_by_id(*rhs)) + { + if let Some(extends_ty_name) = crate::features::string_name_to_type(s) { + Ok(Self { item: *on, extends: extends_ty_name }) + } else { + Err(()) + } + } else { + // === rhs + Ok(Self { item: *lhs, extends: *rhs }) + } + } else if let Type::Constructor(Constructor::TypeOperator(TypeOperator::IsPrototype { + lhs, + rhs_prototype, + })) = rhs_ty + { + // TODO this loses generics etc + Ok(Self { item: *lhs, extends: *rhs_prototype }) + } else if let Type::Constructor(Constructor::TypeExtends(extends)) = rhs_ty { + Ok(*extends) + } else { + Err(()) + } + } + + pub fn equal_to_rhs(&self, rhs: TypeId, types: &TypeStore) -> bool { + if let Ok(TypeExtends { item, extends }) = Self::from_type(rhs, types) { + // TODO get origin on item + item == self.item && extends == self.extends + } else { + // crate::utilities::notify!("Here {:?}", rhs_ty); + false + } + } +} + +/// If `assert x is y` annotation +pub fn type_is_assert_is_type(ty: TypeId, types: &TypeStore) -> Result { + if let Type::Constructor(Constructor::ConditionalResult { + condition, + truthy_result: _, + otherwise_result: TypeId::NEVER_TYPE, + result_union: _, + }) = types.get_type_by_id(ty) + { + TypeExtends::from_type(*condition, types) + } else { + Err(()) + } } #[must_use] @@ -458,6 +578,7 @@ pub fn is_type_truthy_falsy(id: TypeId, types: &TypeStore) -> Decidable { | Type::Constructor(_) | Type::Interface { .. } | Type::PartiallyAppliedGenerics(..) + | Type::Narrowed { .. } | Type::Class { .. } => { // TODO some of these case are known Decidable::Unknown(id) @@ -485,7 +606,7 @@ pub enum ArgumentOrLookup { pub enum NonEqualityReason { Mismatch, PropertiesInvalid { - errors: Vec<(PropertyKey<'static>, PropertyError)>, + errors: Vec<(properties::PropertyKey<'static>, PropertyError)>, }, TooStrict, /// TODO more information @@ -511,7 +632,7 @@ pub(crate) fn is_explicit_generic(on: TypeId, types: &TypeStore) -> bool { types.get_type_by_id(on) { is_explicit_generic(*on, types) - || matches!(under, PropertyKey::Type(under) if is_explicit_generic(*under, types)) + || matches!(under, properties::PropertyKey::Type(under) if is_explicit_generic(*under, types)) } else { false } @@ -572,15 +693,21 @@ pub(crate) fn get_constraint(on: TypeId, types: &TypeStore) -> Option { // TODO dynamic and open poly Some(result_union) } - Constructor::TypeOperator(_) | Constructor::CanonicalRelationOperator { .. } => { - // TODO open poly + Constructor::TypeOperator(op) => match op { + // TODO union of names + TypeOperator::TypeOf(_) => Some(TypeId::STRING_TYPE), + TypeOperator::IsPrototype { .. } | TypeOperator::HasProperty(..) => { + Some(TypeId::BOOLEAN_TYPE) + } + }, + Constructor::CanonicalRelationOperator { .. } => Some(TypeId::BOOLEAN_TYPE), + Constructor::TypeExtends(op) => { + let crate::types::TypeExtends { .. } = op; Some(TypeId::BOOLEAN_TYPE) } - Constructor::TypeRelationOperator(op) => match op { - crate::types::TypeRelationOperator::Extends { .. } => Some(TypeId::BOOLEAN_TYPE), - }, Constructor::KeyOf(_) => Some(TypeId::STRING_TYPE), }, + Type::Narrowed { from: _, narrowed_to } => Some(*narrowed_to), Type::Object(ObjectNature::RealDeal) => { // crate::utilities::notify!("Might be missing some mutations that are possible here"); None @@ -622,7 +749,7 @@ impl LookUpGeneric { .flatten() .filter_map(|(_publicity, key, value)| { // TODO filter more - if matches!(key, PropertyKey::String(s) if s == "length") { + if matches!(key, properties::PropertyKey::String(s) if s == "length") { None } else { Some(value.as_get_type(types)) @@ -725,43 +852,12 @@ pub(crate) fn _assign_to_tuple(_ty: TypeId) -> TypeId { // if let PropertyKey::Type(slice) = } -/// For getting `length` and stuff -fn get_simple_value( - ctx: &impl InformationChain, - on: TypeId, - property: &PropertyKey, - types: &TypeStore, -) -> Option { - fn get_logical(v: Logical) -> Option { - match v { - Logical::Pure(crate::PropertyValue::Value(t)) => Some(t), - Logical::Implies { on, antecedent: _ } => get_logical(*on), - _ => None, - } - } - - let value = properties::get_property_unbound( - (on, None), - (properties::Publicity::Public, property, None), - false, - ctx, - types, - ) - .ok()?; - - if let LogicalOrValid::Logical(value) = value { - get_logical(value) - } else { - None - } -} - fn get_array_length( ctx: &impl InformationChain, on: TypeId, types: &TypeStore, ) -> Result, Option> { - let length_property = PropertyKey::String(std::borrow::Cow::Borrowed("length")); + let length_property = properties::PropertyKey::String(std::borrow::Cow::Borrowed("length")); let id = get_simple_value(ctx, on, &length_property, types).ok_or(None)?; if let Type::Constant(Constant::Number(n)) = types.get_type_by_id(id) { Ok(*n) @@ -850,10 +946,10 @@ impl Counter { *self = Counter::AddTo(new); } - pub(crate) fn into_property_key(self) -> PropertyKey<'static> { + pub(crate) fn into_property_key(self) -> properties::PropertyKey<'static> { match self { - Counter::On(value) => PropertyKey::from_usize(value), - Counter::AddTo(ty) => PropertyKey::Type(ty), + Counter::On(value) => properties::PropertyKey::from_usize(value), + Counter::AddTo(ty) => properties::PropertyKey::Type(ty), } } @@ -896,6 +992,7 @@ pub fn references_key_of(id: TypeId, types: &TypeStore) -> bool { Type::Interface { .. } | Type::Class { .. } | Type::Constant(_) + | Type::Narrowed { .. } | Type::FunctionReference(_) | Type::Object(_) | Type::SpecialObject(_) => false, @@ -964,3 +1061,34 @@ pub fn is_pseudo_continous((ty, generics): (TypeId, GenericChain), types: &TypeS pub fn is_inferrable_type(ty: TypeId) -> bool { matches!(ty, TypeId::ANY_TO_INFER_TYPE | TypeId::OBJECT_TYPE) } + +pub(crate) mod helpers { + use super::{subtyping, InformationChain, Type, TypeId, TypeStore}; + + pub fn simple_subtype( + expr_ty: TypeId, + to_satisfy: TypeId, + information: &impl InformationChain, + types: &TypeStore, + ) -> bool { + let mut state = subtyping::State { + already_checked: Default::default(), + mode: Default::default(), + contributions: Default::default(), + others: subtyping::SubTypingOptions { allow_errors: false }, + object_constraints: None, + }; + + subtyping::type_is_subtype(to_satisfy, expr_ty, &mut state, information, types).is_subtype() + } + + // unfolds narrowing + pub fn get_origin(ty: TypeId, types: &TypeStore) -> TypeId { + if let Type::Narrowed { from, .. } = types.get_type_by_id(ty) { + // Hopefully don't have a nested from + *from + } else { + ty + } + } +} diff --git a/checker/src/types/printing.rs b/checker/src/types/printing.rs index 695491d1..91307e3d 100644 --- a/checker/src/types/printing.rs +++ b/checker/src/types/printing.rs @@ -11,8 +11,7 @@ use crate::{ generics::generic_type_arguments::GenericArguments, get_array_length, get_constraint, get_simple_value, properties::{get_properties_on_single_type, AccessMode, PropertyKey, Publicity}, - Constructor, GenericChainLink, ObjectNature, PartiallyAppliedGenerics, - TypeRelationOperator, + Constructor, GenericChainLink, ObjectNature, PartiallyAppliedGenerics, TypeExtends, }, PropertyValue, }; @@ -88,6 +87,12 @@ pub fn print_type_into_buf( buf.push_str(" | "); print_type_into_buf(*b, buf, cycles, args, types, info, debug); } + Type::Narrowed { narrowed_to, .. } => { + if debug { + buf.push_str("(narrowed) "); + } + print_type_into_buf(*narrowed_to, buf, cycles, args, types, info, debug); + } Type::RootPolyType(nature) => match nature { PolyNature::MappedGeneric { name, extends } => { if debug { @@ -169,9 +174,8 @@ pub fn print_type_into_buf( } PolyNature::Error(to) => { if debug { - write!(buf, "[error {}] ", ty.0).unwrap(); + buf.push_str("(error) "); } - buf.push_str("(error) "); print_type_into_buf(*to, buf, cycles, args, types, info, debug); } PolyNature::RecursiveFunction(..) => { @@ -257,6 +261,16 @@ pub fn print_type_into_buf( otherwise_result, result_union: _, } => { + if let (TypeId::NEVER_TYPE, Ok(crate::types::TypeExtends { item, extends })) = + (TypeId::NEVER_TYPE, crate::types::TypeExtends::from_type(*condition, types)) + { + buf.push_str("asserts "); + print_type_into_buf(item, buf, cycles, args, types, info, debug); + buf.push_str(" is "); + print_type_into_buf(extends, buf, cycles, args, types, info, debug); + return; + } + // TODO nested on constructor let is_standard_generic = matches!( types.get_type_by_id(*condition), @@ -268,6 +282,19 @@ pub fn print_type_into_buf( ) ); + // WIP! + { + if let Some(narrowed_value) = info.get_narrowed(*condition) { + if let crate::Decidable::Known(condition) = + crate::types::is_type_truthy_falsy(narrowed_value, types) + { + let value = if condition { truthy_result } else { otherwise_result }; + print_type_into_buf(*value, buf, cycles, args, types, info, debug); + return; + } + } + } + if debug || is_standard_generic { if debug { write!(buf, "?#{} ", ty.0).unwrap(); @@ -383,10 +410,7 @@ pub fn print_type_into_buf( Constructor::TypeOperator(to) => { write!(buf, "TypeOperator.{to:?}").unwrap(); } - Constructor::TypeRelationOperator(TypeRelationOperator::Extends { - item, - extends, - }) => { + Constructor::TypeExtends(TypeExtends { item, extends }) => { print_type_into_buf(*item, buf, cycles, args, types, info, debug); buf.push_str(" extends "); print_type_into_buf(*extends, buf, cycles, args, types, info, debug); diff --git a/checker/src/types/properties/access.rs b/checker/src/types/properties/access.rs index 0effa81a..d5bf2db7 100644 --- a/checker/src/types/properties/access.rs +++ b/checker/src/types/properties/access.rs @@ -14,93 +14,109 @@ use crate::{ BasedOnKey, Invalid, Logical, LogicalOrValid, NeedsCalculation, PossibleLogical, PropertyOn, }, - Constructor, GenericChain, GenericChainLink, PartiallyAppliedGenerics, SliceArguments, - SpecialObject, TypeStore, + Constructor, GenericChain, GenericChainLink, ObjectNature, PartiallyAppliedGenerics, + SliceArguments, SpecialObject, TypeStore, }, Constant, Type, TypeId, }; use super::{PropertyKey, PropertyKind, PropertyValue, Publicity}; -/// Has to return `Logical` for mapped types -/// -/// Resolver runs information lookup on **ONE** `TypeId` #[allow(clippy::similar_names)] -pub(crate) fn resolver( - (on, on_type_arguments): (TypeId, GenericChain), +fn get_property_from_list( + (on_properties, on_type_arguments): (&super::Properties, GenericChain), (publicity, under, under_type_arguments): (Publicity, &PropertyKey, GenericChain), info_chain: &impl InformationChain, types: &TypeStore, ) -> Option<(PropertyValue, Option, bool)> { - // TODO if on == constant string and property == length. Need to be able to create types here - info_chain.get_chain_of_info().find_map(|info: &LocalInformation| { - info.current_properties.get(&on).and_then(|on_properties| { - { - let (required_publicity, want_key, want_type_arguments) = - (publicity, under, under_type_arguments); - - // crate::utilities::notify!("{:?} {:?}", on, on_properties); + let (required_publicity, want_key, want_type_arguments) = + (publicity, under, under_type_arguments); - // TODO trailing for conditional? + // This is fine on the same type! + let mut trailing_getter = None::; + let mut trailing_setter = None::; - // This is fine on the same type! - let mut trailing_getter = None::; - let mut trailing_setter = None::; - - // 'rev' is important - for (publicity, key, value) in on_properties.iter().rev() { - if *publicity != required_publicity { - continue; - } + // 'rev' is important + for (publicity, key, value) in on_properties.iter().rev() { + if *publicity != required_publicity { + continue; + } - if let PropertyValue::ConditionallyExists { .. } = value { - crate::utilities::notify!("TODO trailing"); - // continue; - } + if let PropertyValue::ConditionallyExists { .. } = value { + crate::utilities::notify!("TODO trailing"); + // continue; + } - let (matched, key_arguments) = super::key_matches( - (key, on_type_arguments), - (want_key, want_type_arguments), - info_chain, - types, - ); + let (matched, key_arguments) = super::key_matches( + (key, on_type_arguments), + (want_key, want_type_arguments), + info_chain, + types, + ); - if matched { - // crate::utilities::notify!("{:?} {:?}", (key, want_key), value); - // TODO if conditional then continue to find then logical or + if matched { + // crate::utilities::notify!("{:?} {:?}", (key, want_key), value); + // TODO if conditional then continue to find then logical or - // TODO should come from key_matches - let is_key_continous = matches!(key, PropertyKey::Type(ty) if is_pseudo_continous((*ty, on_type_arguments), types)); + // TODO should come from key_matches + let is_key_continous = matches!(key, PropertyKey::Type(ty) if is_pseudo_continous((*ty, on_type_arguments), types)); - match value.inner_simple() { - PropertyValue::Getter(getter) => { - if let Some(setter) = trailing_setter { - return Some((PropertyValue::GetterAndSetter { getter: *getter, setter }, key_arguments.into_some(), is_key_continous)); - } - trailing_getter = Some(*getter); - } - PropertyValue::Setter(setter) => { - if let Some(getter) = trailing_getter { - return Some((PropertyValue::GetterAndSetter { getter, setter: *setter }, key_arguments.into_some(), is_key_continous)); - } - trailing_setter = Some(*setter); - } - _ => { - return Some((value.clone(), key_arguments.into_some(), is_key_continous)); - } - } + match value.inner_simple() { + PropertyValue::Getter(getter) => { + if let Some(setter) = trailing_setter { + return Some(( + PropertyValue::GetterAndSetter { getter: *getter, setter }, + key_arguments.into_some(), + is_key_continous, + )); } + trailing_getter = Some(*getter); } - - #[allow(clippy::manual_map)] - if let Some(setter) = trailing_setter { - Some((PropertyValue::Setter(setter), None, false)) - } else if let Some(getter) = trailing_getter { - Some((PropertyValue::Getter(getter), None, false)) - } else { - None + PropertyValue::Setter(setter) => { + if let Some(getter) = trailing_getter { + return Some(( + PropertyValue::GetterAndSetter { getter, setter: *setter }, + key_arguments.into_some(), + is_key_continous, + )); + } + trailing_setter = Some(*setter); + } + _ => { + return Some((value.clone(), key_arguments.into_some(), is_key_continous)); } } + } + } + + #[allow(clippy::manual_map)] + if let Some(setter) = trailing_setter { + Some((PropertyValue::Setter(setter), None, false)) + } else if let Some(getter) = trailing_getter { + Some((PropertyValue::Getter(getter), None, false)) + } else { + None + } +} + +/// Has to return `Logical` for mapped types +/// +/// Resolver runs information lookup on **ONE** `TypeId` +pub(crate) fn resolver( + (on, on_type_arguments): (TypeId, GenericChain), + (publicity, under, under_type_arguments): (Publicity, &PropertyKey, GenericChain), + info_chain: &impl InformationChain, + types: &TypeStore, +) -> Option<(PropertyValue, Option, bool)> { + // TODO if on == constant string and property == length. Need to be able to create types here + info_chain.get_chain_of_info().find_map(|info: &LocalInformation| { + info.current_properties.get(&on).and_then(|on_properties| { + get_property_from_list( + (on_properties, on_type_arguments), + (publicity, under, under_type_arguments), + info_chain, + types, + ) }) }) } @@ -193,6 +209,16 @@ pub(crate) fn get_property_unbound( .map(wrap) .map(LogicalOrValid::Logical) .ok_or(Invalid(on)), + Type::Narrowed { narrowed_to, .. } => { + // TODO wrap value to mark that this could be unsafe if mutated somehow + get_property_on_type_unbound( + (*narrowed_to, on_type_arguments), + (publicity, under, under_type_arguments), + require_both_logical, + info_chain, + types, + ) + } Type::AliasTo { to, .. } => { get_property_on_type_unbound( (*to, on_type_arguments), @@ -381,7 +407,18 @@ pub(crate) fn get_property_unbound( ) }) } - Type::Object(..) => { + Type::Object(ObjectNature::AnonymousTypeAnnotation(properties)) => { + get_property_from_list( + (properties, on_type_arguments), + (publicity, under, under_type_arguments), + info_chain, + types, + ) + .map(wrap) + .map(LogicalOrValid::Logical) + .ok_or(Invalid(on)) + } + Type::Object(ObjectNature::RealDeal) => { let object_constraint_structure_generics = crate::types::get_structure_arguments_based_on_object_constraint( on, info_chain, types, @@ -807,7 +844,9 @@ fn resolve_property_on_logical( Some((PropertyKind::Direct, ty)) } - Type::Constructor(_) | Type::RootPolyType { .. } => { + Type::Narrowed { .. } + | Type::Constructor(_) + | Type::RootPolyType { .. } => { if let Some(generics) = generics { let arguments = generics.build_substitutable(types); let value = @@ -958,7 +997,31 @@ fn resolve_property_on_logical( ), } } - Logical::Or { left, right, condition } => { + Logical::Or { condition, left, right } => { + // WIP! + { + // TODO get_chain_of_info with behavior?? + if let Some(narrowed_value) = environment.get_narrowed(condition) { + if let crate::Decidable::Known(condition) = + crate::types::is_type_truthy_falsy(narrowed_value, types) + { + let to_evaluate = if condition { left } else { right }; + return if let LogicalOrValid::Logical(to_evaluate) = *to_evaluate { + resolve_property_on_logical( + (to_evaluate, generics), + (on, under), + environment, + types, + (behavior, diagnostics), + mode, + ) + } else { + todo!() + }; + } + } + } + // if let (Ok(lhs), Ok(rhs)) = (*left, *right) { let (_, left) = if let LogicalOrValid::Logical(left) = *left { resolve_property_on_logical( diff --git a/checker/src/types/properties/assignment.rs b/checker/src/types/properties/assignment.rs index 1038f3a1..c28315a1 100644 --- a/checker/src/types/properties/assignment.rs +++ b/checker/src/types/properties/assignment.rs @@ -9,9 +9,8 @@ use crate::{ types::{ calling::{CallingDiagnostics, CallingOutput, SynthesisedArgument}, get_constraint, - logical::{BasedOnKey, Logical, LogicalOrValid}, - tuple_like, Constructor, GenericChain, NeedsCalculation, PartiallyAppliedGenerics, - TypeStore, + logical::{BasedOnKey, Logical, LogicalOrValid, NeedsCalculation}, + tuple_like, Constructor, GenericChain, PartiallyAppliedGenerics, TypeStore, }, Environment, Type, TypeId, }; diff --git a/checker/src/types/properties/list.rs b/checker/src/types/properties/list.rs index be681d58..29fe0b12 100644 --- a/checker/src/types/properties/list.rs +++ b/checker/src/types/properties/list.rs @@ -1,7 +1,7 @@ -use super::{PropertyKey, PropertyValue}; +use super::{Properties, PropertyKey, PropertyValue}; use crate::{ - context::{InformationChain, Properties}, - types::{GenericChain, SliceArguments}, + context::InformationChain, + types::{GenericChain, ObjectNature, SliceArguments}, Type, TypeId, TypeStore, }; use std::collections::{BTreeMap, HashMap}; @@ -21,7 +21,8 @@ pub fn get_properties_on_single_type( filter_type: TypeId, ) -> Properties { match types.get_type_by_id(base) { - Type::Interface { .. } | Type::Class { .. } | Type::Object(_) => { + Type::Object(ObjectNature::AnonymousTypeAnnotation(properties)) => properties.clone(), + Type::Interface { .. } | Type::Class { .. } | Type::Object(ObjectNature::RealDeal) => { // Reversed needed for deleted let flattened_properties = info .get_chain_of_info() @@ -104,7 +105,7 @@ pub fn get_properties_on_single_type( numerical_properties.into_values().chain(properties).collect() } } - Type::AliasTo { to, .. } => { + Type::Narrowed { narrowed_to: to, .. } | Type::AliasTo { to, .. } => { get_properties_on_single_type(*to, types, info, filter_enumerable, filter_type) } Type::Constant(c) => get_properties_on_single_type( @@ -146,6 +147,47 @@ pub fn get_properties_on_single_type2( filter_type: TypeId, ) -> Vec<(PropertyKey<'static>, PropertyValue, SliceArguments)> { match types.get_type_by_id(base) { + Type::Object(ObjectNature::AnonymousTypeAnnotation(on_properties)) => { + let mut existing_properties = HashMap::::new(); + let mut properties = Vec::new(); + + for (_publicity, key, value) in on_properties { + let existing = existing_properties.insert(key.clone(), properties.len()); + if let PropertyValue::Deleted = value { + if let Some(existing) = existing { + properties.remove(existing); + } + // TODO only covers constant keys :( + continue; + } + + // if !matches!(filter_type, TypeId::ANY_TYPE) { + let on_type_arguments = None; // TODO + let (key_matches, key_arguments) = super::key_matches( + (&PropertyKey::Type(filter_type), on_type_arguments), + (key, None), + info, + types, + ); + + // crate::utilities::notify!("key_arguments={:?}", key_arguments); + + if !key_matches { + continue; + } + // } + + if let Some(idx) = existing { + let value = (key.to_owned(), value.clone(), key_arguments); + properties[idx] = value; + } else { + let value = (key.to_owned(), value.clone(), key_arguments); + properties.push(value); + } + } + + properties + } Type::Interface { .. } | Type::Class { .. } | Type::Object(_) => { // Reversed needed for deleted let flattened_properties = info @@ -235,7 +277,7 @@ pub fn get_properties_on_single_type2( Default::default() } } - Type::AliasTo { to, .. } => { + Type::Narrowed { narrowed_to: to, .. } | Type::AliasTo { to, .. } => { get_properties_on_single_type2((*to, base_arguments), types, info, filter_type) } Type::Constant(c) => get_properties_on_single_type2( diff --git a/checker/src/types/properties/mod.rs b/checker/src/types/properties/mod.rs index 8d3edfe2..c1a0b75c 100644 --- a/checker/src/types/properties/mod.rs +++ b/checker/src/types/properties/mod.rs @@ -10,11 +10,16 @@ use super::{Type, TypeStore}; use crate::{ context::InformationChain, subtyping::{slice_matches_type, SliceArguments, SubTypingOptions}, - types::{calling::Callable, generics::contributions::Contributions, GenericChain, PolyNature}, + types::{ + calling::Callable, generics::contributions::Contributions, logical, GenericChain, + PolyNature, + }, Constant, Environment, TypeId, }; use std::borrow::Cow; +pub type Properties = Vec<(Publicity, PropertyKey<'static>, PropertyValue)>; + #[derive(PartialEq)] pub enum PropertyKind { Direct, @@ -173,6 +178,32 @@ impl<'a> PropertyKey<'a> { } } +/// For getting `length` and stuff +pub(crate) fn get_simple_value( + ctx: &impl InformationChain, + on: TypeId, + property: &PropertyKey, + types: &TypeStore, +) -> Option { + fn get_logical(v: logical::Logical) -> Option { + match v { + logical::Logical::Pure(PropertyValue::Value(t)) => Some(t), + logical::Logical::Implies { on, antecedent: _ } => get_logical(*on), + _ => None, + } + } + + let value = + get_property_unbound((on, None), (Publicity::Public, property, None), false, ctx, types) + .ok()?; + + if let logical::LogicalOrValid::Logical(value) = value { + get_logical(value) + } else { + None + } +} + // WIP quick hack for static property keys under < 10 static NUMBERS: &str = "0123456789"; diff --git a/checker/src/types/store.rs b/checker/src/types/store.rs index 465e3acf..07662858 100644 --- a/checker/src/types/store.rs +++ b/checker/src/types/store.rs @@ -15,7 +15,7 @@ use crate::{ use super::{ generics::generic_type_arguments::GenericArguments, get_constraint, properties::PropertyKey, - Constructor, LookUpGeneric, LookUpGenericMap, PartiallyAppliedGenerics, TypeRelationOperator, + Constructor, LookUpGeneric, LookUpGenericMap, PartiallyAppliedGenerics, TypeExtends, }; /// Holds all the types. Eventually may be split across modules @@ -261,11 +261,31 @@ impl TypeStore { if let TypeId::NEVER_TYPE = rhs { return lhs; } + // Normalise to ltr + if let Type::Or(lhs_lhs, lhs_rhs) = self.get_type_by_id(lhs) { + let new_lhs = *lhs_lhs; + let rhs = self.new_or_type(*lhs_rhs, rhs); + return self.new_or_type(new_lhs, rhs); + } + + // TODO recursive contains + if let Type::Or(rhs_lhs, rhs_rhs) = self.get_type_by_id(rhs) { + if lhs == *rhs_lhs { + return self.new_or_type(lhs, *rhs_rhs); + } else if lhs == *rhs_rhs { + return self.new_or_type(lhs, *rhs_lhs); + } + } let ty = Type::Or(lhs, rhs); self.register_type(ty) } + #[must_use] + pub fn new_or_type_from_iterator(&mut self, iter: impl IntoIterator) -> TypeId { + iter.into_iter().reduce(|acc, n| self.new_or_type(acc, n)).unwrap_or(TypeId::NEVER_TYPE) + } + pub fn new_and_type(&mut self, lhs: TypeId, rhs: TypeId) -> Result { if lhs == rhs { return Ok(lhs); @@ -337,9 +357,11 @@ impl TypeStore { true_result: TypeId, false_result: TypeId, ) -> TypeId { - let on = self.register_type(Type::Constructor(super::Constructor::TypeRelationOperator( - TypeRelationOperator::Extends { item: check_type, extends }, - ))); + let on = + self.register_type(Type::Constructor(super::Constructor::TypeExtends(TypeExtends { + item: check_type, + extends, + }))); self.new_conditional_type(on, true_result, false_result) } @@ -359,20 +381,29 @@ impl TypeStore { otherwise_result } else if truthy_result == TypeId::TRUE && otherwise_result == TypeId::FALSE { condition + } else if let Type::Constructor(Constructor::UnaryOperator { + operator: crate::features::operations::PureUnary::LogicalNot, + operand: reversed_condition, + }) = self.get_type_by_id(condition) + { + self.new_conditional_type(*reversed_condition, otherwise_result, truthy_result) } else { - // TODO on is negation then swap operands let ty = Type::Constructor(super::Constructor::ConditionalResult { condition, truthy_result, otherwise_result, + // TODO remove result_union: self.new_or_type(truthy_result, otherwise_result), }); self.register_type(ty) } } - pub fn new_anonymous_interface_type(&mut self) -> TypeId { - let ty = Type::Object(super::ObjectNature::AnonymousTypeAnnotation); + pub fn new_anonymous_interface_type( + &mut self, + properties: super::properties::Properties, + ) -> TypeId { + let ty = Type::Object(super::ObjectNature::AnonymousTypeAnnotation(properties)); self.register_type(ty) } @@ -490,6 +521,10 @@ impl TypeStore { self.register_type(Type::RootPolyType(PolyNature::Open(base))) } + pub fn new_narrowed(&mut self, from: TypeId, narrowed_to: TypeId) -> TypeId { + self.register_type(Type::Narrowed { from, narrowed_to }) + } + /// For any synthesis errors to keep the program going a type is needed. /// Most of the time use [`TypeId:ERROR_TYPE`] which is generic any like type. /// However sometimes we can use some type annotation instead to still leave some information. @@ -578,7 +613,18 @@ impl TypeStore { Intrinsic::GreaterThan => (TypeId::GREATER_THAN, TypeId::NUMBER_GENERIC), Intrinsic::MultipleOf => (TypeId::MULTIPLE_OF, TypeId::NUMBER_GENERIC), Intrinsic::Exclusive => (TypeId::EXCLUSIVE_RESTRICTION, TypeId::T_TYPE), - Intrinsic::Not => (TypeId::NOT_RESTRICTION, TypeId::T_TYPE), + Intrinsic::Not => { + // Double negation + if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::NOT_RESTRICTION, + arguments: GenericArguments::ExplicitRestrictions(args), + }) = self.get_type_by_id(argument) + { + return args.get(&TypeId::T_TYPE).unwrap().0; + } + + (TypeId::NOT_RESTRICTION, TypeId::T_TYPE) + } Intrinsic::CaseInsensitive => (TypeId::CASE_INSENSITIVE, TypeId::STRING_GENERIC), }; let arguments = GenericArguments::ExplicitRestrictions(crate::Map::from_iter([( diff --git a/checker/src/types/subtyping.rs b/checker/src/types/subtyping.rs index adcabe37..3fa83b11 100644 --- a/checker/src/types/subtyping.rs +++ b/checker/src/types/subtyping.rs @@ -259,7 +259,7 @@ pub(crate) fn type_is_subtype_with_generics( // Eager things match right_ty { - Type::AliasTo { to: right, .. } => { + Type::Narrowed { narrowed_to: right, .. } | Type::AliasTo { to: right, .. } => { return type_is_subtype_with_generics( (base_type, base_type_arguments), (*right, ty_structure_arguments), @@ -493,32 +493,26 @@ pub(crate) fn type_is_subtype_with_generics( SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) } } - Type::Object(nature) => { - if let ObjectNature::RealDeal = nature { - crate::utilities::notify!( - "what {:?} (subtyping where LHS = ObjectNature::RealDeal)", - ty - ); - } - // assert!(matches!(nature, ObjectNature::AnonymousTypeAnnotation)); + Type::Object(ObjectNature::AnonymousTypeAnnotation(properties)) => subtype_properties( + (base_type, properties.iter(), base_type_arguments), + (ty, ty_structure_arguments), + state, + information, + types, + ), + Type::Object(ObjectNature::RealDeal) => { + crate::utilities::notify!( + "what {:?} (subtyping where LHS = ObjectNature::RealDeal)", + ty + ); - subtype_properties( + subtype_floating_properties( (base_type, base_type_arguments), (ty, ty_structure_arguments), state, information, types, ) - - // let _left = print_type(base_type, types, information, true); - - // crate::utilities::notify!("Left object {}", left); - - // if let SubTypeResult::IsNotSubType(..) = result { - // result - // } else { - // SubTypeResult::IsSubType - // } } Type::And(left, right) => { let right = *right; @@ -722,6 +716,16 @@ pub(crate) fn type_is_subtype_with_generics( ) } } + Type::Narrowed { narrowed_to, .. } => { + crate::utilities::notify!("Narrowed on Left?"); + type_is_subtype_with_generics( + (*narrowed_to, base_type_arguments), + (ty, ty_structure_arguments), + state, + information, + types, + ) + } Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on, arguments }) => { match *on { TypeId::READONLY_RESTRICTION => { @@ -1101,27 +1105,69 @@ pub(crate) fn type_is_subtype_with_generics( } } else { crate::utilities::notify!("RHS not string"); - SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + type_is_subtype_with_generics( + (TypeId::NUMBER_TYPE, base_type_arguments), + (ty, ty_structure_arguments), + state, + information, + types, + ) } } Constructor::BinaryOperator { .. } | Constructor::CanonicalRelationOperator { .. } | Constructor::UnaryOperator { .. } => unreachable!("invalid constructor on LHS"), Constructor::TypeOperator(_) => todo!(), - Constructor::TypeRelationOperator(_) => todo!(), - Constructor::Image { on: _, with: _, result } - | Constructor::ConditionalResult { - condition: _, + Constructor::TypeExtends(_) => todo!(), + Constructor::Image { on: _, with: _, result } => { + crate::utilities::notify!("Here"); + type_is_subtype_with_generics( + (*result, base_type_arguments), + (ty, ty_structure_arguments), + state, + information, + types, + ) + } + Constructor::ConditionalResult { + condition, truthy_result: _, - otherwise_result: _, + otherwise_result, result_union: result, - } => type_is_subtype_with_generics( - (*result, base_type_arguments), - (ty, ty_structure_arguments), - state, - information, - types, - ), + } => { + // implements `assert is condition annotation` + if let ( + Type::Constructor(Constructor::TypeExtends(extends)), + TypeId::NEVER_TYPE, + Type::Constructor(Constructor::ConditionalResult { + condition: rhs_condition, + truthy_result: _, + otherwise_result: TypeId::NEVER_TYPE, + result_union: _, + }), + ) = (types.get_type_by_id(*condition), *otherwise_result, right_ty) + { + if extends.equal_to_rhs(*rhs_condition, types) { + SubTypeResult::IsSubType + } else { + crate::utilities::notify!( + "Here {:?}", + types.get_type_by_id(*rhs_condition) + ); + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + } + } else { + crate::utilities::notify!("Here {:?}", right_ty); + + type_is_subtype_with_generics( + (*result, base_type_arguments), + (ty, ty_structure_arguments), + state, + information, + types, + ) + } + } Constructor::Property { on, under, result: _, mode: _ } => { // Ezno custom state // TODO might be based of T @@ -1395,7 +1441,7 @@ pub(crate) fn type_is_subtype_with_generics( SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) } } - Type::Object(..) => subtype_properties( + Type::Object(..) => subtype_floating_properties( (base_type, base_type_arguments), (ty, ty_structure_arguments), state, @@ -1435,7 +1481,7 @@ pub(crate) fn type_is_subtype_with_generics( // TODO SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) } - Type::Constructor(..) | Type::RootPolyType(..) => { + Type::Narrowed { .. } | Type::Constructor(..) | Type::RootPolyType(..) => { let arg = base_type_arguments.and_then(|args| args.get_argument_covariant(base_type)); @@ -1493,8 +1539,6 @@ fn subtype_function( information: &impl InformationChain, types: &TypeStore, ) -> SubTypeResult { - crate::utilities::notify!("Subtyping a function"); - let right_func = if let Type::FunctionReference(right_func) | Type::SpecialObject(SpecialObject::Function(right_func, _)) = right_ty { @@ -1580,34 +1624,43 @@ fn subtype_function( } } -fn subtype_properties( +fn subtype_floating_properties( (base_type, base_type_arguments): (TypeId, GenericChain), (ty, right_type_arguments): (TypeId, GenericChain), state: &mut State, information: &impl InformationChain, types: &TypeStore, ) -> SubTypeResult { - // TODO this will cause problems - state.mode = state.mode.one_deeper(); - - // TODO (#128): This is a compromise where only boolean and number types are treated as nominal - // match base_type { - // TypeId::BOOLEAN_TYPE | TypeId::NUMBER_TYPE if base_type != ty => { - // crate::utilities::notify!("Here"); - // state.mode = state.mode.one_shallower(); - // return SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch); - // } - // _ => {} - // } - - let mut property_errors = Vec::new(); let reversed_flattened_properties_on_base = information .get_chain_of_info() .filter_map(|info| info.current_properties.get(&base_type).map(|v| v.iter().rev())) .flatten(); + subtype_properties( + (base_type, reversed_flattened_properties_on_base, base_type_arguments), + (ty, right_type_arguments), + state, + information, + types, + ) +} + +fn subtype_properties<'a, T>( + (base_type, base_properties, base_type_arguments): (TypeId, T, GenericChain), + (ty, right_type_arguments): (TypeId, GenericChain), + state: &mut State, + information: &impl InformationChain, + types: &TypeStore, +) -> SubTypeResult +where + T: Iterator, PropertyValue)> + 'a, +{ + // TODO this will cause problems if not reversed at end + state.mode = state.mode.one_deeper(); + let mut property_errors = Vec::new(); + // Note this won't check for conditional stuff being true etc or things being deleted - for (publicity, key, lhs_property) in reversed_flattened_properties_on_base { + for (publicity, key, lhs_property) in base_properties { // crate::utilities::notify!( // "key {:?} with base_type_arguments={:?}", // key, @@ -2178,16 +2231,15 @@ pub fn type_is_subtype_of_property_mapped_key( } CovariantContribution::TypeId(key_ty) => { match types.get_type_by_id(key_ty) { - Type::AliasTo { to, name: _, parameters: _ } => { - type_is_subtype_of_property_mapped_key( - MappedKey { value: (*to).into(), key: mapped_key.key }, - (base, property_generics, optional), - (ty, right_type_arguments), - state, - information, - types, - ) - } + Type::Narrowed { narrowed_to: to, .. } + | Type::AliasTo { to, name: _, parameters: _ } => type_is_subtype_of_property_mapped_key( + MappedKey { value: (*to).into(), key: mapped_key.key }, + (base, property_generics, optional), + (ty, right_type_arguments), + state, + information, + types, + ), Type::And(left, right) => { let left = type_is_subtype_of_property_mapped_key( MappedKey { value: (*left).into(), key: mapped_key.key }, @@ -2526,7 +2578,7 @@ pub(crate) fn slice_matches_type( false } } - Type::AliasTo { to, .. } => slice_matches_type( + Type::Narrowed { narrowed_to: to, .. } | Type::AliasTo { to, .. } => slice_matches_type( (*to, base_type_arguments), slice, contributions, diff --git a/checker/tests/suggestions.rs b/checker/tests/suggestions.rs index 0621d149..4a9f3e41 100644 --- a/checker/tests/suggestions.rs +++ b/checker/tests/suggestions.rs @@ -1,4 +1,5 @@ -const SIMPLE_DTS: Option<&str> = Some(include_str!("../definitions/simple.d.ts")); +// const SIMPLE_DTS: Option<&str> = Some(include_str!("../definitions/simple.d.ts")); +const SIMPLE_DTS: Option<&str> = None; #[cfg(feature = "ezno-parser")] #[test]