diff --git a/.github/workflows/clippy-rustfmt-fix.yml b/.github/workflows/clippy-rustfmt-fix.yml index fc2cfd76..16aa4d05 100644 --- a/.github/workflows/clippy-rustfmt-fix.yml +++ b/.github/workflows/clippy-rustfmt-fix.yml @@ -16,6 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable - uses: actions/cache@v4 with: path: ${{ env.CACHE_PATHS }} diff --git a/.github/workflows/performance-and-size.yml b/.github/workflows/performance-and-size.yml index 6d5dcdb6..e248bd27 100644 --- a/.github/workflows/performance-and-size.yml +++ b/.github/workflows/performance-and-size.yml @@ -37,13 +37,15 @@ jobs: env: CARGO_PROFILE_RELEASE_DEBUG: true - - name: Get base ezno - if: github.ref_name != 'main' - uses: actions/download-artifact@v4 - continue-on-error: true - with: - name: latest-checker - path: previous-ezno + # TODO need to lookup existing workflow on main + # even if this worked, it might have issues with newer features added in this run + # - name: Get base ezno + # if: github.ref_name != 'main' + # uses: actions/download-artifact@v4 + # continue-on-error: true + # with: + # name: latest-checker + # path: previous-ezno - name: Set compilers id: compilers @@ -197,16 +199,16 @@ jobs: curl -sS $url > input.js echo "::group::Comparison" - hyperfine \ + hyperfine -i \ -L compiler ${{ steps.compilers.outputs.BINARIES }} \ '{compiler} ast-explorer full input.js --timings' echo "::endgroup::" done - - name: Upload checker - if: github.ref == 'main' - uses: actions/upload-artifact@v4 - with: - name: latest-checker - path: target/release/ezno - retention-days: 90 + # - name: Upload checker + # if: github.ref == 'main' + # uses: actions/upload-artifact@v4 + # with: + # name: latest-checker + # path: target/release/ezno + # retention-days: 90 diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index e45c83c5..c6e1b695 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -95,7 +95,7 @@ jobs: - name: Run checker specification if: (steps.changes.outputs.checker == 'true' && github.event_name != 'pull_request') || github.ref_name == 'main' - run: cargo test + run: cargo test -p ezno-checker-specification - name: Run checker specification (w/ staging) if: steps.changes.outputs.checker == 'true' && github.event_name == 'pull_request' diff --git a/README.md b/README.md index 27dfc88b..f0c1165e 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ What Ezno is: What Ezno is not - **eNZo, the Z is in front of the N** (pronounce as 'Fresno' without the 'fr') 😀 -- Be on parity with TSC or 1:1, it has some different behaviors **but** should work in existing projects using TSC +- Be on parity with TSC or 1:1, it has some different behaviors **but** should work in existing projects using TSC. [You can see a full comparison of emitted errors and warnings compared with TSC here](https://kaleidawave.github.io/ezno/comparison) - Faster as a means to serve large codebases. Cut out bloat and complex code first! - Smarter as a means to allow more *dynamic patterns*. Keep things simple! - A binary executable compiler. It takes in JavaScript (or a TypeScript or Ezno superset) and does similar processes to traditional compilers, but at the end emits JavaScript. However, in the future, it *could* generate a lower level format using its event (side-effect) representation. diff --git a/checker/Cargo.toml b/checker/Cargo.toml index 2461cae6..2b7614b9 100644 --- a/checker/Cargo.toml +++ b/checker/Cargo.toml @@ -39,7 +39,7 @@ path-absolutize = { version = "3.0", features = ["use_unix_paths_on_wasm"] } either = "1.6" levenshtein = "1" ordered-float = "4.2" -regress = { version = "0.10.0", features = [] } +regress = { version = "0.10", features = [] } serde = { version = "1.0", features = ["derive"], optional = true } simple-json-parser = "0.0.2" diff --git a/checker/definitions/internal.ts.d.bin b/checker/definitions/internal.ts.d.bin index c4f51fa0..c2573ea2 100644 Binary files a/checker/definitions/internal.ts.d.bin and b/checker/definitions/internal.ts.d.bin differ diff --git a/checker/definitions/overrides.d.ts b/checker/definitions/overrides.d.ts index ad67900c..b5c0e66f 100644 --- a/checker/definitions/overrides.d.ts +++ b/checker/definitions/overrides.d.ts @@ -37,7 +37,7 @@ declare class Array<T> { // TODO this argument map<U>(cb: (t: T, i?: number) => U): Array<U> { - const { length } = this, mapped: Array<U> = []; + const { length } = this, mapped: U[] = []; let i: number = 0; while (i < length) { const value = this[i]; @@ -48,7 +48,7 @@ declare class Array<T> { // // TODO any is debatable filter(cb: (t: T, i?: number) => any): Array<T> { - const { length } = this, filtered: Array<T> = []; + const { length } = this, filtered: T[] = []; let i: number = 0; while (i < length) { const value = this[i]; @@ -127,8 +127,9 @@ declare class Array<T> { type Record<K extends string, T> = { [P in K]: T } -type LessThan<T extends number> = ExclusiveRange<NegativeInfinity, T>; -type GreaterThan<T extends number> = ExclusiveRange<T, Infinity>; +type ExclusiveRange<F extends number, C extends number> = GreaterThan<F> & LessThan<C>; +type InclusiveRange<F extends number, C extends number> = (GreaterThan<F> & LessThan<C>) | (F | C); + type Integer = MultipleOf<1>; declare class Map<T, U> { @@ -202,7 +203,7 @@ declare class Promise<T> { } declare class RegExp { @Constant("regexp:constructor") - constructor(pattern: string, flags?: string); + constructor(pattern: string, flags?: string); @Constant("regexp:exec") exec(input: string): RegExpExecArray | null; @@ -222,10 +223,10 @@ interface RegExpExecArray extends Array<string> { * The first match. This will always be present because `null` will be returned if there are no matches. */ 0: string; -} + // } -// es2018 -interface RegExpExecArray { + // // es2018 + // interface RegExpExecArray { groups?: { [key: string]: string; }; diff --git a/checker/definitions/simple.d.ts b/checker/definitions/simple.d.ts index aba85ea2..01aed46e 100644 --- a/checker/definitions/simple.d.ts +++ b/checker/definitions/simple.d.ts @@ -211,8 +211,9 @@ declare class Map<K, V> { type Record<K extends string, T> = { [P in K]: T } -type LessThan<T extends number> = ExclusiveRange<NegativeInfinity, T>; -type GreaterThan<T extends number> = ExclusiveRange<T, Infinity>; +type ExclusiveRange<F extends number, C extends Number> = GreaterThan<F> & LessThan<C>; +type InclusiveRange<F extends number, C extends Number> = (GreaterThan<F> & LessThan<C>) | (F | C); + type Integer = MultipleOf<1>; /** diff --git a/checker/examples/run_checker.rs b/checker/examples/run_checker.rs index 124c86e8..8c54fb7e 100644 --- a/checker/examples/run_checker.rs +++ b/checker/examples/run_checker.rs @@ -20,6 +20,7 @@ fn main() { 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 advanced_numbers = args.iter().any(|item| item == "--advanced-numbers"); let now = Instant::now(); @@ -46,6 +47,7 @@ fn main() { max_inline_count: 600, debug_dts, extra_syntax: extras, + advanced_numbers, ..Default::default() }; @@ -60,8 +62,7 @@ fn main() { if args.iter().any(|arg| arg == "--types") { eprintln!("Types:"); - let types = result.types.into_vec_temp(); - for (type_id, item) in &types[types.len().saturating_sub(60)..] { + for (type_id, item) in result.types.user_types() { eprintln!("\t{type_id:?}: {item:?}"); } } @@ -74,6 +75,10 @@ fn main() { } } + if args.iter().any(|arg| arg == "--called-functions") { + eprintln!("Called function: {:?}", result.types.called_functions); + } + if args.iter().any(|arg| arg == "--time") { let end = now.elapsed(); let count = result.diagnostics.into_iter().len(); diff --git a/checker/specification/Cargo.toml b/checker/specification/Cargo.toml index 51591c22..81dbbd2b 100644 --- a/checker/specification/Cargo.toml +++ b/checker/specification/Cargo.toml @@ -7,9 +7,11 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -just-staging = [] +default = ["base"] +base = [] staging = [] -all = ["staging"] +to_implement = [] +all = ["base", "staging", "to_implement"] [[test]] name = "specification_test" diff --git a/checker/specification/build.rs b/checker/specification/build.rs index 1b20193d..1c597410 100644 --- a/checker/specification/build.rs +++ b/checker/specification/build.rs @@ -12,7 +12,7 @@ fn main() -> Result<(), Box<dyn Error>> { let out_path = Path::new(&std::env::var("OUT_DIR")?).join("specification.rs"); let mut out = File::create(out_path)?; - if cfg!(not(feature = "just-staging")) { + if cfg!(feature = "base") { let specification = read_to_string("./specification.md")?; markdown_lines_append_test_to_rust(specification.lines().enumerate(), &mut out)?; } @@ -26,7 +26,7 @@ fn main() -> Result<(), Box<dyn Error>> { writeln!(&mut out, "}}").unwrap(); } - if cfg!(feature = "all") { + if cfg!(feature = "to_implement") { let to_implement = read_to_string("./to_implement.md")?; writeln!(&mut out, "mod to_implement {{ ").unwrap(); writeln!(&mut out, "use super::{{check_expected_diagnostics, TypeCheckOptions}}; ") diff --git a/checker/specification/specification.md b/checker/specification/specification.md index 88a305b0..7e2841af 100644 --- a/checker/specification/specification.md +++ b/checker/specification/specification.md @@ -306,16 +306,6 @@ const b = x.b; - No property 'b' on { a: 2 } -#### `Object.keys`, `Object.values`, `Object.entries` - -```ts -Object.keys({ a: 1, b: 2 }) satisfies ["a", "b"]; -Object.values({ a: 1, b: 2 }) satisfies [1, 2]; -Object.entries({ a: 1, b: 2 }) satisfies boolean; -``` - -- Expected boolean, found [["a", 1], ["b", 2]] - #### Spread condition ```ts @@ -813,7 +803,7 @@ const z: false = true || 4 ```ts (4 === 2) satisfies true; -(4 !== 2) satisfies string; +(4 !== 5) satisfies string; ``` - Expected true, found false @@ -1330,7 +1320,7 @@ new MyClass("hi").value satisfies "hello" - Expected "hello", found "hi" -#### `new` on function prototype +#### `new` on function with assigned prototype ```ts function MyClass(value) { @@ -1841,13 +1831,13 @@ kestrel(3)(2) satisfies 4 ```ts function kestrel2(a) { - return _b => _c => a + return b => c => (a * b) + c } -kestrel2(3)(2)(6) satisfies 4 +kestrel2(3)(2)(1) satisfies 4 ``` -- Expected 4, found 3 +- Expected 4, found 7 #### Carry across objects @@ -1952,6 +1942,7 @@ function conditional(v: string) { a++ } } + conditional("x") a satisfies 2 conditional("value") @@ -2055,13 +2046,13 @@ stringIsHi(string) satisfies number; ```ts function func(param: boolean) { let a = 2; - if (param) { + if (param) { a = 3; - return a; - } else { + return a; + } else { a = 7; } - a satisfies string; + a satisfies string; } ``` @@ -2356,25 +2347,6 @@ try { checkedLn(-5) } catch {} - Conditional '[Error] { message: \"Cannot log\" }' was thrown in function -#### Throw through internal callback - -```ts -try { - [1, 2, 3].map((x: number) => { - if (x === 2) { - throw "error" - } - }); - console.log("unreachable") -} catch (e) { - e satisfies number; -} -``` - -- Conditional '"error"' was thrown in function -- Unreachable statement -- Expected number, found "error" - ### Collections > Some of these are built of exiting features. @@ -2411,16 +2383,6 @@ x.push("hi"); - Argument of type \"hi\" is not assignable to parameter of type number -#### Array map - -> TODO other arguments (index and `this`) - -```ts -[6, 8, 10].map(x => x + 1) satisfies [7, 8, 11]; -``` - -- Expected [7, 8, 11], found [7, 9, 11] - #### Mutation > This is part of [assignment mismatch](https://github.com/kaleidawave/ezno/issues/18) @@ -2542,8 +2504,8 @@ getNumberBetweenFive() === 2.2; getNumberBetweenFive() === 7; ``` -- This equality is always false as InclusiveRange<0, 5> & Integer and 2.2 have no overlap -- This equality is always false as InclusiveRange<0, 5> & Integer and 7 have no overlap +- This equality is always false as GreaterThan<0> & LessThan<5> & Integer | 0 | 5 and 2.2 have no overlap +- This equality is always false as GreaterThan<0> & LessThan<5> & Integer | 0 | 5 and 7 have no overlap #### Identity equality @@ -2552,14 +2514,14 @@ getNumberBetweenFive() === 7; ```ts function func(a: string, b: number) { (a === a) satisfies string; - (b === b) satisfies null; + (b === b) satisfies null; } ``` - Expected string, found true - Expected null, found boolean -#### Ranges for interal types +#### Ranges for internal types ```ts function func(a: number) { @@ -2579,20 +2541,87 @@ function func(a: number) { } ``` -- Expected null, found InclusiveRange\<-5, 5> -- Expected string, found InclusiveRange\<18, 22> +With advanced_numbers + +- Expected null, found GreaterThan<-5> & LessThan<5> | -5 | 5 +- Expected string, found GreaterThan<18> & LessThan<22> | 18 | 22 -#### Not disjoint +#### Disjoint multiple of with range + +> TODO need to redo range to use interesection of less than and greater than ```ts -function func(param: number) { - if (param !== 2) { - return param === 2 - } +function func1(a: number, b: number) { + if (a % 8 === 0 && 31 < b && b < 37) { + const x = a === b; + } + if (a % 10 === 0 && 31 < b && b < 37) { + const x = a === b; + } + if (a % 10 === 0 && 31 < b && b < 41) { + const x = a === b; + } } ``` -- This equality is always false as Not<2> and 2 have no overlap +With advanced_numbers + +- This equality is always false as MultipleOf<10> and GreaterThan<31> & LessThan<37> have no overlap + +#### Modulo range + +```ts +function func(x: number) { + return x % 5 === 6; +} +``` + +- This equality is always false as ExclusiveRange<-5, 5> and 6 have no overlap + +#### Transistivity + +```ts +function func(a: number, b: number, c: number) { + if (a < b && b < c) { + const cond = (a < c) satisfies 5; + } +} +``` + +- Expected 5, found true + +### Operators across conditions + +```ts +function func(param: boolean) { + const value = param ? 1 : 2; + return value + 1; +} + +func satisfies string; +``` + +With advanced_numbers + +- Expected string, found (param: boolean) => 2 | 3 + +#### Disjoint not + +```ts +function func1(param: Not<string>) { + return "hi" === param; +} + +function func2(param: Not<string>) { + return 4 === param; +} + +function func3(p1: Not<string>, p2: Not<number>) { + return p1 === p2; +} +``` + +- This equality is always false as "hi" and Not\<string> have no overlap ### Statements, declarations and expressions @@ -2709,6 +2738,9 @@ declare var x: number; declare var x: number; (x < 4) satisfies string; (x === 4) satisfies Math; +(x !== 4) satisfies boolean; +(x > 4) satisfies boolean; +(x >= 4) satisfies boolean; ``` - Expected string, found boolean @@ -2961,8 +2993,8 @@ let x: BoxString<string, number>; ```ts interface X { - a: string - b: string + a: string + b: string } function func(x: X | null) { @@ -3330,11 +3362,11 @@ doThingWithX(new Y()) ```ts class Box<T> { - value: T; + value: T; - constructor(value: T) { - this.value = value; - } + constructor(value: T) { + this.value = value; + } } const myBox = new Box<number>("hi"); @@ -3747,10 +3779,29 @@ type Introduction = `Hello ${string}`; const first: Introduction = "Hello Ben"; const second: Introduction = "Hi Ben"; const third: `Hiya ${string}` = "Hello Ben"; + +// Edge cases +const invalidNum1: `${1}` = 1; +const invalidNum2: `${1}` = "1"; +const invalidNum3: `${1}` = "2"; ``` - Type "Hi Ben" is not assignable to type Introduction - Type "Hello Ben" is not assignable to type `Hiya ${string}` +- Type 1 is not assignable to type "1" +- Type \"2\" is not assignable to type "1" + +#### Disjoint template literals + +```ts +function func(a: `a${string}`, b: `b${string}`, c: string) { + const res1 = a === b; + const res2 = (b === c) satisfies string; +} +``` + +- This equality is always false as `a${string}` and `b${string}` have no overlap +- Expected string, found boolean #### Assigning to types as keys @@ -4071,6 +4122,8 @@ x.a = "hi"; x.a = 4; ``` +<!-- TODO this is incorrect!!!!, should be string --> + - Type 4 does not meet property constraint "hi" #### `as` rewrite @@ -4097,7 +4150,7 @@ x.property_b ```ts function x(p: { readonly a: string, b: string }) { - p.a = "hi"; + p.a = "hi"; p.b = "hi"; } ``` @@ -4283,10 +4336,10 @@ function logicNarrow(thing: any, other: any) { ```ts function func(param: boolean | string | number) { - if (typeof param === "boolean") { - return 5 - } - param satisfies null; + if (typeof param === "boolean") { + return 5 + } + param satisfies null; } ``` @@ -4296,9 +4349,9 @@ function func(param: boolean | string | number) { ```ts function func(param: Array<string> | string) { - if (param instanceof Array) { + if (param instanceof Array) { param satisfies null; - } + } } ``` @@ -4308,9 +4361,9 @@ function func(param: Array<string> | string) { ```ts function func(param: any) { - if (param instanceof Array) { + if (param instanceof Array) { param satisfies null; - } + } } ``` @@ -4320,10 +4373,10 @@ function func(param: any) { ```ts function narrowPropertyEquals(param: { tag: "a", a: string } | { tag: "b", b: number }) { - if (param.tag === "a") { - param.a satisfies string; - param satisfies null; - } + if (param.tag === "a") { + param.a satisfies string; + param satisfies null; + } } ``` @@ -4333,10 +4386,10 @@ function narrowPropertyEquals(param: { tag: "a", a: string } | { tag: "b", b: nu ```ts function narrowFromTag(param: { tag: "a", a: string } | { tag: "b", b: number }) { - if ("a" in param) { - param.a satisfies string; - param satisfies null; - } + if ("a" in param) { + param.a satisfies string; + param satisfies null; + } } ``` @@ -4348,9 +4401,9 @@ function narrowFromTag(param: { tag: "a", a: string } | { tag: "b", b: number }) ```ts function buildObject(param: any) { - if ("a" in param) { - param satisfies null; - } + if ("a" in param) { + param satisfies null; + } } ``` @@ -4360,22 +4413,22 @@ function buildObject(param: any) { ```ts function conditional(param: boolean) { - const obj1 = {}, obj2 = {}; + const obj1 = { b: 2 }, obj2 = { c: 6 }; const sum = param ? obj1 : obj2; if (sum === obj1) { - sum.a = 2; + sum.a = 3; } [obj1, obj2] satisfies string; } ``` -- Expected string, found [{ a: 2 }, {}] +- Expected string, found [{ b: 2, a: 3 }, { c: 6 }] #### From condition equality ```ts function conditional(param: boolean) { - const obj1 = { a: 1 }, obj2 = {}; + const obj1 = { a: 1 }, obj2 = { b: 2}; const sum = param ? obj1 : obj2; if (param) { sum satisfies string; @@ -4405,19 +4458,19 @@ function conditional(param: boolean) { ```ts function func1(param: string | number) { - if (typeof param === "number" && param > 0) { - param satisfies number; - } else { - param satisfies null; + 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; - } + if (typeof param === "string" || !(typeof param === "number")) { + param satisfies undefined; + } else { + param satisfies number; + } } ``` @@ -4432,10 +4485,10 @@ function func2(param: string | number | boolean) { function func(param: boolean) { let a = param; const inner = (value: boolean) => a = value; - if (a) { + if (a) { inner(false); a satisfies null; - } + } } ``` @@ -4445,15 +4498,15 @@ function func(param: boolean) { ```ts function func1(param: any): asserts param is number { - if (typeof param !== "string") { - throw "bad" - } + if (typeof param !== "string") { + throw "bad" + } } function func2(param: any): asserts param is boolean { - if (typeof param !== "boolean") { - throw "bad" - } + if (typeof param !== "boolean") { + throw "bad" + } } ``` @@ -4506,6 +4559,31 @@ function getName(name?: string) { - Expected undefined, found string +#### Implication from equality + +```ts +function func(a: boolean) { + const x = a ? 1 : 2; + if (x === 1) { + a satisfies "hi" + } +} +``` + +- Expected "hi", found true + +#### Narrowing in for loop + +> Can't do modulo because of post mutation + +```ts +for (let i = 0; i < 3; i++) { + const x = i === 50; +} +``` + +- This equality is always false as LessThan<3> and 50 have no overlap + ### Object constraint > Any references to a annotated variable **must** be within its LHS type. These test that it carries down to objects. @@ -4597,9 +4675,18 @@ const x = { a: 3 }; Object.setPrototypeOf(x, { a: 5, b: 2 }); x.a satisfies 3; x.b satisfies string; + +const obj = Object.setPrototypeOf( + {}, + Math.random() ? { a: 2 } : { get a() { return 0 } } +); + +const result = 'a' in obj; +result satisfies string; ``` - Expected string, found 2 +- Expected string, found true #### Get prototype @@ -5038,9 +5125,16 @@ function register(a: Literal<string>) { register("something") // `document.title` is an unknown string, non-literal -register(document.title) +register(document.title); + +function func(param: object) { + const obj = { a: 2 }; + const obj1: Literal<object> = obj; + const obj2: Literal<object> = param; +} ``` +- Type object is not assignable to type Literal\<object> - Argument of type string is not assignable to parameter of type Literal\<string> #### Number intrinsics diff --git a/checker/specification/to_implement.md b/checker/specification/to_implement.md index 11cce36d..5856c16b 100644 --- a/checker/specification/to_implement.md +++ b/checker/specification/to_implement.md @@ -770,6 +770,47 @@ a satisfies 0; b satisfies string; - Expected string, found 1 +#### Array map + +> TODO other arguments (index and `this`) + +```ts +[6, 8, 10].map(x => x + 1) satisfies [7, 8, 11]; +``` + +- Expected [7, 8, 11], found [7, 9, 11] + +#### `Object.keys`, `Object.values`, `Object.entries` + +```ts +Object.keys({ a: 1, b: 2 }) satisfies ["a", "b"]; +Object.values({ a: 1, b: 2 }) satisfies [1, 2]; +Object.entries({ a: 1, b: 2 }) satisfies boolean; +``` + +- Expected boolean, found [["a", 1], ["b", 2]] + +#### Throw through internal callback + +> This test heavily relies on `.map` working + +```ts +try { + [1, 2, 3].map((x: number) => { + if (x === 2) { + throw "error" + } + }); + console.log("unreachable") +} catch (e) { + e satisfies number; +} +``` + +- Conditional '"error"' was thrown in function +- Unreachable statement +- Expected number, found "error" + ### Control flow #### Conditional break @@ -948,3 +989,16 @@ new RegExp("<string>x").group.string ``` - ? + +### Properties + +#### Issue [#208](https://github.com/kaleidawave/ezno/issues/208) + +```ts +let d = {}; +let e = 0; +if (Math.random() > 0.2) { d.a = 0; } +print_type('a' in d); +``` + +- Hmmm diff --git a/checker/src/context/environment.rs b/checker/src/context/environment.rs index a4a2dece..2c5decc7 100644 --- a/checker/src/context/environment.rs +++ b/checker/src/context/environment.rs @@ -14,11 +14,7 @@ use crate::{ AssignmentKind, AssignmentReturnStatus, IncrementOrDecrement, Reference, }, modules::Exported, - objects::SpecialObject, - operations::{ - evaluate_logical_operation_with_expression, - evaluate_pure_binary_operation_handle_errors, MathematicalAndBitwise, - }, + operations::{evaluate_logical_operation_with_expression, MathematicalOrBitwiseOperation}, variables::{VariableMutability, VariableOrImport, VariableWithValue}, }, subtyping::{type_is_subtype, type_is_subtype_object, State, SubTypeResult, SubTypingOptions}, @@ -278,7 +274,7 @@ impl<'a> Environment<'a> { } AssignmentKind::PureUpdate(operator) => { // Order matters here - let reference_position = reference.get_position(); + // let reference_position = reference.get_position(); let existing = self.get_reference( reference.clone(), checking_data, @@ -286,8 +282,9 @@ impl<'a> Environment<'a> { ); let expression = expression.unwrap(); - let expression_pos = - A::expression_position(expression).with_source(self.get_source()); + // let expression_pos = + // A::expression_position(expression).with_source(self.get_source()); + let rhs = A::synthesise_expression( expression, TypeId::ANY_TYPE, @@ -295,22 +292,48 @@ impl<'a> Environment<'a> { checking_data, ); - let new = evaluate_pure_binary_operation_handle_errors( - (existing, reference_position), - operator.into(), - (rhs, expression_pos), - checking_data, + let result = crate::features::operations::evaluate_mathematical_operation( + existing, + operator, + rhs, self, + &mut checking_data.types, + checking_data.options.strict_casts, + checking_data.options.advanced_numbers, ); - let assignment_position = - assignment_position.with_source(self.get_source()); - self.set_reference_handle_errors( - reference, - new, - assignment_position, - checking_data, - ); - new + if let Ok(new) = result { + let assignment_position = + assignment_position.with_source(self.get_source()); + + self.set_reference_handle_errors( + reference, + new, + assignment_position, + checking_data, + ); + + new + } else { + checking_data.diagnostics_container.add_error( + crate::TypeCheckError::InvalidMathematicalOrBitwiseOperation { + operator, + lhs: crate::diagnostics::TypeStringRepresentation::from_type_id( + existing, + self, + &checking_data.types, + false, + ), + rhs: crate::diagnostics::TypeStringRepresentation::from_type_id( + rhs, + self, + &checking_data.types, + false, + ), + position: assignment_position.with_source(self.get_source()), + }, + ); + TypeId::ERROR_TYPE + } } AssignmentKind::IncrementOrDecrement(direction, return_kind) => { // let value = @@ -323,31 +346,57 @@ impl<'a> Environment<'a> { ); // TODO existing needs to be cast to number!! - - let new = evaluate_pure_binary_operation_handle_errors( - (existing, position), - match direction { - IncrementOrDecrement::Increment => MathematicalAndBitwise::Add, - IncrementOrDecrement::Decrement => MathematicalAndBitwise::Subtract, + let operator = match direction { + IncrementOrDecrement::Increment => MathematicalOrBitwiseOperation::Add, + IncrementOrDecrement::Decrement => { + MathematicalOrBitwiseOperation::Subtract } - .into(), - (TypeId::ONE, source_map::Nullable::NULL), - checking_data, - self, - ); + }; - let assignment_position = - assignment_position.with_source(self.get_source()); - self.set_reference_handle_errors( - reference, - new, - assignment_position, - checking_data, + let result = crate::features::operations::evaluate_mathematical_operation( + existing, + operator, + TypeId::ONE, + self, + &mut checking_data.types, + checking_data.options.strict_casts, + checking_data.options.advanced_numbers, ); + if let Ok(new) = result { + let assignment_position = + assignment_position.with_source(self.get_source()); + + self.set_reference_handle_errors( + reference, + new, + assignment_position, + checking_data, + ); - match return_kind { - AssignmentReturnStatus::Previous => existing, - AssignmentReturnStatus::New => new, + match return_kind { + AssignmentReturnStatus::Previous => existing, + AssignmentReturnStatus::New => new, + } + } else { + checking_data.diagnostics_container.add_error( + crate::TypeCheckError::InvalidMathematicalOrBitwiseOperation { + operator, + lhs: crate::diagnostics::TypeStringRepresentation::from_type_id( + existing, + self, + &checking_data.types, + false, + ), + rhs: crate::diagnostics::TypeStringRepresentation::from_type_id( + TypeId::ONE, + self, + &checking_data.types, + false, + ), + position, + }, + ); + TypeId::ERROR_TYPE } } AssignmentKind::ConditionalUpdate(operator) => { @@ -890,45 +939,41 @@ impl<'a> Environment<'a> { 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)); - } + // TODO WIP + let narrowed = current_value + .and_then(|cv| self.get_narrowed_or_object(cv, &checking_data.types)); - crate::utilities::notify!("Free variable with value!"); - } else { - crate::utilities::notify!("Free variable with no current value"); + if let Some(precise) = narrowed.or(current_value) { + // let ty = checking_data.types.get_type_by_id(precise); + + // // TODO temp for function + // let value = 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)); } - if let Some(narrowed) = narrowed { - narrowed + crate::utilities::notify!("Free variable with no current value"); + 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 { - 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 - } + crate::utilities::notify!("TODO record that free variable is `any` here"); + TypeId::ANY_TYPE } } VariableMutability::Mutable { reassignment_constraint } => { @@ -940,12 +985,15 @@ impl<'a> Environment<'a> { for ctx in self.parents_iter() { if let GeneralContext::Syntax(s) = ctx { if s.possibly_mutated_variables.contains(&variable_id) { + crate::utilities::notify!("Possibly mutated variables"); break; } - if let Some(value) = + + if let Some(current_value) = get_on_ctx!(ctx.info.variable_current_value.get(&variable_id)) + .copied() { - return Ok(VariableWithValue(og_var.clone(), *value)); + return Ok(VariableWithValue(og_var.clone(), current_value)); } if s.context_type.scope.is_dynamic_boundary().is_some() { @@ -986,8 +1034,11 @@ impl<'a> Environment<'a> { } } - let ty = if let Some(value) = reused_reference { - value + let ty = if let Some(reused_reference) = reused_reference { + // TODO temp. I believe this can break type contracts because of mutations + // but needed here because of for loop narrowing + let narrowed = self.get_narrowed_or_object(reused_reference, &checking_data.types); + narrowed.unwrap_or(reused_reference) } else { // TODO dynamic ? let ty = Type::RootPolyType(crate::types::PolyNature::FreeVariable { @@ -1022,6 +1073,7 @@ impl<'a> Environment<'a> { self, of, None::<&crate::types::generics::substitution::SubstitutionArguments<'static>>, + &checking_data.types, ) .expect("import not assigned yet"); return Ok(VariableWithValue(og_var.clone(), current_value)); @@ -1031,7 +1083,9 @@ impl<'a> Environment<'a> { self, og_var.get_id(), None::<&crate::types::generics::substitution::SubstitutionArguments<'static>>, + &checking_data.types, ); + if let Some(current_value) = current_value { Ok(VariableWithValue(og_var.clone(), current_value)) } else { diff --git a/checker/src/context/information.rs b/checker/src/context/information.rs index 486587a4..6f13dcd8 100644 --- a/checker/src/context/information.rs +++ b/checker/src/context/information.rs @@ -252,6 +252,31 @@ pub trait InformationChain { fn get_narrowed(&self, for_ty: TypeId) -> Option<TypeId> { self.get_chain_of_info().find_map(|info| info.narrowed_values.get(&for_ty).copied()) } + + fn get_narrowed_or_object(&self, for_ty: TypeId, types: &TypeStore) -> Option<TypeId> { + let value = self.get_narrowed(for_ty); + if let Some(value) = value { + Some(value) + } else if let Type::Constructor(crate::types::Constructor::ConditionalResult { + condition, + truthy_result, + otherwise_result, + result_union: _, + }) = types.get_type_by_id(for_ty) + { + let narrowed_condition = self.get_narrowed(*condition)?; + if let crate::Decidable::Known(condition) = + crate::types::is_type_truthy_falsy(narrowed_condition, types) + { + let value = if condition { truthy_result } else { otherwise_result }; + Some(*value) + } else { + None + } + } else { + value + } + } } pub struct ModuleInformation<'a> { @@ -393,13 +418,61 @@ pub fn merge_info( 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()); + // TODO temp fix for `... ? { ... } : { ... }`. + // TODO add undefineds to sides etc + for (on, properties) in truthy.current_properties { + // let properties = properties + // .into_iter() + // .map(|(publicity, key, value)| { + // let falsy_environment_property = otherwise + // .as_mut() + // .and_then(|otherwise| { + // pick_out_property(&mut otherwise.current_properties, (publicity, key), onto, types) + // }); + + // if let Some(existing) = falsy_environment_property { + // // Merging more complex properties has lots of issues + // todo!() + // } else { + // (publicity, key, PropertyValue::ConditionallyExists { condition, value }) + // } + // }) + // .collect(); + + if let Some(existing) = onto.current_properties.get_mut(&on) { + existing.extend(properties); + } else { + onto.current_properties.insert(on, properties); + } + } + + if let Some(otherwise) = otherwise { + for (on, properties) in otherwise.current_properties { + if let Some(existing) = onto.current_properties.get_mut(&on) { + existing.extend(properties); + } else { + onto.current_properties.insert(on, properties); + } + } } // TODO set more information? } } + +// `info_chain` and `types` are a bit excess, but `key_matches` requires it +// TODO needs to delete afterwards, to block it out for subsequent +fn _pick_out_property( + from: &mut Properties, + (want_publicity, want_key): (Publicity, &PropertyKey<'static>), + info_chain: &impl InformationChain, + types: &TypeStore, +) -> Option<(Publicity, PropertyKey<'static>, PropertyValue)> { + from.iter() + .position(|(publicity, key, _)| { + *publicity == want_publicity + && crate::types::key_matches((key, None), (want_key, None), info_chain, types).0 + }) + // TODO replace with deleted? + .map(|idx| from.remove(idx)) +} diff --git a/checker/src/context/mod.rs b/checker/src/context/mod.rs index 25126536..8749cdbe 100644 --- a/checker/src/context/mod.rs +++ b/checker/src/context/mod.rs @@ -920,9 +920,7 @@ impl<T: ContextType> Context<T> { for (on, constraint) in object_constraints { match self.info.object_constraints.entry(on) { Entry::Occupied(mut existing) => { - let new = types - .new_and_type(*existing.get(), constraint) - .expect("creating impossible restriction"); + let new = types.new_and_type(*existing.get(), constraint); existing.insert(new); } Entry::Vacant(v) => { @@ -980,11 +978,11 @@ impl<T: ContextType> Context<T> { } pub(crate) fn get_prototype(&self, on: TypeId) -> TypeId { - if let Some(prototype) = self.info.prototypes.get(&on) { + if let Some(prototype) = self.get_chain_of_info().find_map(|info| info.prototypes.get(&on)) + { *prototype - } else if let Some(parent) = self.context_type.get_parent() { - get_on_ctx!(parent.get_prototype(on)) } else { + crate::utilities::notify!("Could not find prototype"); TypeId::OBJECT_TYPE } } @@ -1017,6 +1015,7 @@ pub(crate) fn get_value_of_variable( info: &impl InformationChain, on: VariableId, closures: Option<&impl ClosureChain>, + types: &TypeStore, ) -> Option<TypeId> { for fact in info.get_chain_of_info() { let res = if let Some(closures) = closures { @@ -1032,7 +1031,7 @@ pub(crate) fn get_value_of_variable( // TODO in remaining info, don't loop again if let Some(res) = res { - let narrowed = info.get_narrowed(res); + let narrowed = info.get_narrowed_or_object(res, types); return Some(narrowed.unwrap_or(res)); } } diff --git a/checker/src/context/root.rs b/checker/src/context/root.rs index 69f0ffd7..2d57e70a 100644 --- a/checker/src/context/root.rs +++ b/checker/src/context/root.rs @@ -76,8 +76,8 @@ impl RootContext { ("Capitalize".to_owned(), TypeId::STRING_CAPITALIZE), ("Uncapitalize".to_owned(), TypeId::STRING_UNCAPITALIZE), ("NoInfer".to_owned(), TypeId::NO_INFER), - ("InclusiveRange".to_owned(), TypeId::INCLUSIVE_RANGE), - ("ExclusiveRange".to_owned(), TypeId::EXCLUSIVE_RANGE), + ("GreaterThan".to_owned(), TypeId::GREATER_THAN), + ("LessThan".to_owned(), TypeId::LESS_THAN), ("MultipleOf".to_owned(), TypeId::MULTIPLE_OF), ("NotNotANumber".to_owned(), TypeId::NUMBER_BUT_NOT_NOT_A_NUMBER), ("Not".to_owned(), TypeId::NOT_RESTRICTION), diff --git a/checker/src/diagnostics.rs b/checker/src/diagnostics.rs index 78943ade..2d1afed0 100644 --- a/checker/src/diagnostics.rs +++ b/checker/src/diagnostics.rs @@ -423,7 +423,7 @@ pub(crate) enum TypeCheckError<'a> { #[allow(clippy::upper_case_acronyms)] VariableUsedInTDZ(VariableUsedInTDZ), InvalidMathematicalOrBitwiseOperation { - operator: crate::features::operations::MathematicalAndBitwise, + operator: crate::features::operations::MathematicalOrBitwiseOperation, lhs: TypeStringRepresentation, rhs: TypeStringRepresentation, position: SpanWithSource, @@ -958,6 +958,7 @@ pub enum TypeCheckWarning { DisjointEquality { lhs: TypeStringRepresentation, rhs: TypeStringRepresentation, + result: bool, position: SpanWithSource, }, ItemMustBeUsedWithFlag { @@ -1039,11 +1040,15 @@ impl From<TypeCheckWarning> for Diagnostic { kind, } } - TypeCheckWarning::DisjointEquality { lhs, rhs, position } => Diagnostic::Position { - reason: format!("This equality is always false as {lhs} and {rhs} have no overlap"), - position, - kind, - }, + TypeCheckWarning::DisjointEquality { lhs, rhs, position, result } => { + Diagnostic::Position { + reason: format!( + "This equality is always {result} as {lhs} and {rhs} have no overlap" + ), + position, + kind, + } + } } } } diff --git a/checker/src/events/application.rs b/checker/src/events/application.rs index d3ddba9f..dc6db249 100644 --- a/checker/src/events/application.rs +++ b/checker/src/events/application.rs @@ -88,6 +88,7 @@ pub(crate) fn apply_events( top_environment, *id, Some(type_arguments), + types, ); if let Some(ty) = value { ty @@ -778,6 +779,11 @@ pub(crate) fn apply_events( }; type_arguments.set_during_application(*referenced_in_scope_as, new_function_ty); } + super::MiscellaneousEvents::MarkFunctionAsCalled(id) => { + if top_environment.is_always_run() { + types.called_functions.insert(*id); + } + } }, Event::FinalEvent(final_event) => { // I think this is okay diff --git a/checker/src/events/mod.rs b/checker/src/events/mod.rs index a376db6f..7bf999d8 100644 --- a/checker/src/events/mod.rs +++ b/checker/src/events/mod.rs @@ -185,6 +185,9 @@ pub enum MiscellaneousEvents { }, /// Creates a new function or class CreateConstructor { referenced_in_scope_as: TypeId, function: FunctionId }, + /// While [`Event::CallsType`] marks callbacks (and parameters) as being called, it does not mark regular function as being called + /// this event has no effect but does add it to the global `HashSet` of called types + MarkFunctionAsCalled(FunctionId), } /// A break in application @@ -252,7 +255,6 @@ pub enum ApplicationResult { position: SpanWithSource, }, /// From a `throw ***` statement (or expression). - /// Throw { thrown: TypeId, position: SpanWithSource, diff --git a/checker/src/events/printing.rs b/checker/src/events/printing.rs index 9e128329..72f86321 100644 --- a/checker/src/events/printing.rs +++ b/checker/src/events/printing.rs @@ -162,6 +162,9 @@ pub fn debug_effects<C: InformationChain>( buf.push_str("end"); } Event::Miscellaneous(misc) => match misc { + super::MiscellaneousEvents::MarkFunctionAsCalled(_) => { + buf.push_str("Calls inlined function"); + } super::MiscellaneousEvents::Has { .. } => { buf.push_str("Has"); } diff --git a/checker/src/features/assignments.rs b/checker/src/features/assignments.rs index 486aab57..2813e7b3 100644 --- a/checker/src/features/assignments.rs +++ b/checker/src/features/assignments.rs @@ -5,7 +5,7 @@ use crate::{ TypeId, }; -use super::operations::{LogicalOperator, MathematicalAndBitwise}; +use super::operations::{LogicalOperator, MathematicalOrBitwiseOperation}; /// A single or multiple items to assign to pub enum Assignable<A: crate::ASTImplementation> { @@ -50,7 +50,7 @@ pub enum AssignableArrayDestructuringField<A: crate::ASTImplementation> { /// Increment and decrement are are not binary add subtract as they cast their lhs to number pub enum AssignmentKind { Assign, - PureUpdate(MathematicalAndBitwise), + PureUpdate(MathematicalOrBitwiseOperation), ConditionalUpdate(LogicalOperator), IncrementOrDecrement(IncrementOrDecrement, AssignmentReturnStatus), } diff --git a/checker/src/features/conditional.rs b/checker/src/features/conditional.rs index caa7b332..d46db3a3 100644 --- a/checker/src/features/conditional.rs +++ b/checker/src/features/conditional.rs @@ -47,6 +47,7 @@ where &mut checking_data.types, ); + crate::utilities::notify!("Narrowed value {:?} in true branch", values); truthy_environment.info.narrowed_values = values; let result = then_evaluate(&mut truthy_environment, checking_data); @@ -81,6 +82,7 @@ where &mut checking_data.types, ); + crate::utilities::notify!("Narrowed value {:?} in false branch", values); falsy_environment.info.narrowed_values = values; let result = else_evaluate(&mut falsy_environment, checking_data); diff --git a/checker/src/features/constant_functions.rs b/checker/src/features/constant_functions.rs index a7b18ccf..ecf8497b 100644 --- a/checker/src/features/constant_functions.rs +++ b/checker/src/features/constant_functions.rs @@ -146,6 +146,13 @@ pub(crate) fn call_constant_function( Err(ConstantFunctionError::CannotComputeConstant) } } + "debug_number" => { + let arg = arguments.iter().next().unwrap(); + Ok(ConstantOutput::Diagnostic(format!( + "number: {:?}", + crate::types::intrinsics::get_range_and_mod_class(arg.value, types) + ))) + } "print_type" | "debug_type" | "print_and_debug_type" | "debug_type_independent" => { fn to_string( print: bool, @@ -291,12 +298,16 @@ pub(crate) fn call_constant_function( } "setPrototypeOf" => { if let [first, second] = arguments { - let _prototype = environment - .info - .prototypes - .insert(first.non_spread_type().unwrap(), second.non_spread_type().unwrap()); - // TODO - Ok(ConstantOutput::Value(TypeId::UNDEFINED_TYPE)) + if let (Ok(first), Ok(second)) = (first.non_spread_type(), second.non_spread_type()) + { + let _prototype = environment.info.prototypes.insert(first, second); + + crate::utilities::notify!("Set {:?} prototype to {:?}", first, second); + + Ok(ConstantOutput::Value(first)) + } else { + Err(ConstantFunctionError::CannotComputeConstant) + } } else { Err(ConstantFunctionError::CannotComputeConstant) } diff --git a/checker/src/features/functions.rs b/checker/src/features/functions.rs index 849e5bf8..43081834 100644 --- a/checker/src/features/functions.rs +++ b/checker/src/features/functions.rs @@ -822,6 +822,7 @@ where let closes_over = create_closed_over_references( &function_environment.context_type.closed_over_references, &function_environment, + &checking_data.types, ); let Syntax { @@ -1112,7 +1113,7 @@ pub fn new_name_expected_object( &mut environment.info, ); - types.new_and_type(expected, name_object.build_object()).unwrap() + types.new_and_type(expected, name_object.build_object()) // .unwrap() } /// Reverse of the above @@ -1137,6 +1138,7 @@ pub fn extract_name(expecting: TypeId, types: &TypeStore, environment: &Environm } } +#[must_use] pub fn class_generics_to_function_generics( prototype: TypeId, types: &TypeStore, diff --git a/checker/src/features/iteration.rs b/checker/src/features/iteration.rs index d7b2c0cf..e638cbb1 100644 --- a/checker/src/features/iteration.rs +++ b/checker/src/features/iteration.rs @@ -138,8 +138,6 @@ pub fn synthesise_iteration<T: crate::ReadFromFS, A: crate::ASTImplementation>( // } } IterationBehavior::DoWhile(condition) => { - // let is_do_while = matches!(behavior, IterationBehavior::DoWhile(..)); - // Same as above but condition is evaluated at end. Don't know whether events should be evaluated once...? let (condition, result, ..) = environment.new_lexical_environment_fold_into_parent( Scope::Iteration { label }, @@ -252,6 +250,20 @@ pub fn synthesise_iteration<T: crate::ReadFromFS, A: crate::ASTImplementation>( TypeId::TRUE }; + let values = + super::narrowing::narrow_based_on_expression_into_vec( + condition, + false, + environment, + &mut checking_data.types, + ); + + crate::utilities::notify!( + "Narrowed values in loop {:?}", + values + ); + environment.info.narrowed_values = values; + // TODO not always needed add_loop_described_break_event( condition, @@ -494,8 +506,9 @@ pub(crate) fn run_iteration_block( let events_to_be_applied = iterations * events.len(); crate::utilities::notify!( - "count = {:?}. with", - (events_to_be_applied, input.max_inline) + "applying {:?} events (max {:?})", + events_to_be_applied, + input.max_inline ); (events_to_be_applied < input.max_inline as usize).then_some(iterations) }); @@ -508,20 +521,6 @@ pub(crate) fn run_iteration_block( crate::utilities::notify!("Running {} times", iterations); - // crate::utilities::notify!( - // "Evaluating a constant amount of iterations {:?}", - // iterations - // ); - - // if let InitialVariablesInput::Calculated(initial) = initial { - // for (variable_id, initial_value) in initial.iter() { - // invocation_context - // .get_latest_info(top_environment) - // .variable_current_value - // .insert(*variable_id, *initial_value); - // } - // } - run_iteration_loop( invocation_context, iterations, @@ -672,7 +671,7 @@ fn evaluate_unknown_iteration_for_loop( let initial = match initial { RunBehavior::Run => ClosedOverVariables(Default::default()), RunBehavior::References(v) => { - crate::features::create_closed_over_references(&v, top_environment) + crate::features::create_closed_over_references(&v, top_environment, types) } }; @@ -909,8 +908,9 @@ fn calculate_result_of_loop( result: _, }) = value_after_running_expressions_in_loop { + let assignment = crate::types::helpers::get_origin(*assignment, types); debug_assert!( - assignment == less_than_reference_type_id, + assignment == *less_than_reference_type_id, "incrementor free variable type not the same as condition free variable type?" ); diff --git a/checker/src/features/mod.rs b/checker/src/features/mod.rs index 572559fe..0af0ae5f 100644 --- a/checker/src/features/mod.rs +++ b/checker/src/features/mod.rs @@ -28,7 +28,7 @@ use crate::{ diagnostics::TypeStringRepresentation, events::RootReference, types::{ - get_constraint, + get_constraint, helpers, logical::{Logical, LogicalOrValid}, properties, PartiallyAppliedGenerics, TypeStore, }, @@ -219,6 +219,7 @@ fn get_promise_value(constraint: TypeId, types: &TypeStore) -> Option<TypeId> { pub(crate) fn create_closed_over_references( closed_over_references: &ClosedOverReferencesInScope, current_environment: &Environment, + types: &TypeStore, ) -> ClosedOverVariables { ClosedOverVariables( closed_over_references @@ -229,9 +230,9 @@ pub(crate) fn create_closed_over_references( let c = None::< &crate::types::generics::substitution::SubstitutionArguments<'static>, >; - let get_value_of_variable = - get_value_of_variable(current_environment, *on, c); - let ty = if let Some(value) = get_value_of_variable { + let value = get_value_of_variable(current_environment, *on, c, types); + + let ty = if let Some(value) = value { value } else { // TODO think we are getting rid of this @@ -430,58 +431,72 @@ pub(crate) fn has_property( 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::UNIMPLEMENTED_ERROR_TYPE - } - Logical::Implies { .. } => { - crate::utilities::notify!("or or implies `in`"); + if let Some((condition, truthy, falsy)) = helpers::get_type_as_conditional(rhs, types) { + let truthy_result = has_property((publicity, key), truthy, information, types); + let otherwise_result = has_property((publicity, key), falsy, information, types); + types.new_conditional_type(condition, truthy_result, otherwise_result) + } else { + 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 { condition, left, right } => { + // TODO some problems here, need to recurse + let (left, right) = (*left, *right); + if let (LogicalOrValid::Logical(_), LogicalOrValid::Logical(_)) = + (&left, right) + { + TypeId::TRUE + } else if let LogicalOrValid::Logical(_) = left { + condition + } else { + types.new_logical_negation_type(condition) + } + } + Logical::Implies { .. } => { + crate::utilities::notify!("or or implies `in`"); + TypeId::UNIMPLEMENTED_ERROR_TYPE + } + Logical::BasedOnKey { .. } => { + crate::utilities::notify!("mapped in"); + TypeId::UNIMPLEMENTED_ERROR_TYPE + } + }, + Ok(LogicalOrValid::NeedsCalculation(result)) => { + crate::utilities::notify!("TODO {:?}", result); TypeId::UNIMPLEMENTED_ERROR_TYPE } - Logical::BasedOnKey { .. } => { - crate::utilities::notify!("mapped in"); - TypeId::UNIMPLEMENTED_ERROR_TYPE + Err(err) => { + crate::utilities::notify!("TODO {:?}", err); + TypeId::FALSE } - }, - Ok(LogicalOrValid::NeedsCalculation(result)) => { - crate::utilities::notify!("TODO {:?}", result); - TypeId::UNIMPLEMENTED_ERROR_TYPE - } - Err(err) => { - crate::utilities::notify!("TODO {:?}", err); - TypeId::FALSE } } - } - Type::Or(_, _) => { - crate::utilities::notify!("Condtionally"); - TypeId::UNIMPLEMENTED_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) + Type::Or(_, _) => { + unreachable!() + } + 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 index 1720ff17..52d840be 100644 --- a/checker/src/features/narrowing.rs +++ b/checker/src/features/narrowing.rs @@ -2,13 +2,13 @@ use crate::{ context::InformationChain, types::{ self, as_logical_and, as_logical_not, as_logical_or, - helpers::{get_conditional, get_origin}, + helpers::{get_origin, get_type_as_conditional}, properties, Constant, Constructor, PolyNature, TypeOperator, TypeStore, }, Map, Type, TypeId, }; -use super::operations::{CanonicalEqualityAndInequality, MathematicalAndBitwise}; +use super::operations::{CanonicalEqualityAndInequality, MathematicalOrBitwiseOperation}; pub fn narrow_based_on_expression_into_vec( condition: TypeId, @@ -18,6 +18,7 @@ pub fn narrow_based_on_expression_into_vec( ) -> Map<TypeId, TypeId> { let mut into = Default::default(); narrow_based_on_expression(condition, negate, &mut into, information, types); + into.iter_mut().for_each(|(on, value)| *value = types.new_narrowed(*on, *value)); into } @@ -36,28 +37,41 @@ pub fn narrow_based_on_expression( operator: CanonicalEqualityAndInequality::StrictEqual, rhs, } => { + let lhs_type = types.get_type_by_id(*lhs); if let Type::Constructor(Constructor::TypeOperator(TypeOperator::TypeOf(on))) = - types.get_type_by_id(*lhs) + lhs_type { - let from = get_origin(*on, types); + let from = *on; + let origin = get_origin(from, 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 { + let type_from_name = crate::features::string_name_to_type(c); + if let Some(type_from_name) = type_from_name { 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); + crate::utilities::notify!("{:?}", from); + // TODO temp fix + let narrowed_to = if let Some(TypeId::ANY_TYPE) = + crate::types::get_constraint(from, types) + { + crate::types::intrinsics::new_intrinsic( + &crate::types::intrinsics::Intrinsic::Not, + type_from_name, + types, + ) + } else { + let mut result = Vec::new(); + build_union_from_filter( + from, + Filter::Not(&Filter::IsType(type_from_name)), + &mut result, + information, + types, + ); + types.new_or_type_from_iterator(result) + }; + into.insert(origin, narrowed_to); } else { - let narrowed = types.new_narrowed(from, narrowed_to); - into.insert(from, narrowed); + into.insert(origin, type_from_name); } } else { crate::utilities::notify!("Type name was (shouldn't be here)"); @@ -67,83 +81,102 @@ pub fn narrow_based_on_expression( } } else if let Type::Constructor(Constructor::BinaryOperator { lhs: operand, - operator: MathematicalAndBitwise::Modulo, + operator: MathematicalOrBitwiseOperation::Modulo, rhs: modulo, result: _, - }) = types.get_type_by_id(*lhs) + }) = lhs_type { - if *rhs == TypeId::ZERO { - crate::utilities::notify!("TODO only if sensible"); + if negate { + crate::utilities::notify!("TODO do we not divisable by?"); + return; + } + let (operand, rhs, modulo) = (*operand, *rhs, *modulo); + let operand = get_origin(operand, types); + crate::utilities::notify!("Here {:?}", types.get_type_by_id(modulo)); + let narrowed_to = crate::types::intrinsics::new_intrinsic( + &crate::types::intrinsics::Intrinsic::MultipleOf, + modulo, + types, + ); - let (from, modulo) = (*operand, *modulo); - if negate { - crate::utilities::notify!("TODO do we not divisable by?"); - } else { - let narrowed_to = crate::types::intrinsics::new_intrinsic( - &crate::types::intrinsics::Intrinsic::MultipleOf, - modulo, - types, - ); - let narrowed = types.new_narrowed(from, narrowed_to); - into.insert(from, narrowed); - } + // TODO also from == x - 1 etc + let narrowed_to = if rhs == TypeId::ZERO { + narrowed_to } else { - crate::utilities::notify!("maybe subtract LHS"); - } + types.register_type(Type::Constructor( + crate::types::Constructor::BinaryOperator { + lhs: narrowed_to, + operator: super::operations::MathematicalOrBitwiseOperation::Add, + rhs, + result: TypeId::NUMBER_TYPE, + }, + )) + }; + into.insert(operand, narrowed_to); } 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) - ); + if let Type::RootPolyType(PolyNature::Parameter { .. }) = lhs_type { + crate::utilities::notify!("lhs is {:?} with {:?}", lhs_type, rhs); } if negate && lhs == rhs { - into.insert(*lhs, types.new_narrowed(*lhs, TypeId::NOT_NOT_A_NUMBER)); + into.insert(*lhs, TypeId::NOT_NOT_A_NUMBER); return; } - let lhs = get_origin(*lhs, types); + let lhs = *lhs; + let rhs = *rhs; let result = if negate { // TODO wip - let narrowed_to = if get_conditional(lhs, types).is_some() { + let narrowed_to = if get_type_as_conditional(lhs, types).is_some() { let mut result = Vec::new(); build_union_from_filter( lhs, - Filter::Not(&Filter::IsType(*rhs)), + 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) + types.new_or_type_from_iterator(result) } else { crate::types::intrinsics::new_intrinsic( &crate::types::intrinsics::Intrinsic::Not, - *rhs, + rhs, types, ) }; - types.new_narrowed(lhs, narrowed_to) + narrowed_to } else { - *rhs + rhs }; into.insert(lhs, result); - // PROPERTY HERE - if let Type::Constructor(Constructor::Property { + // CONDITION NARROWING HERE ((x ? 1 : 2) == 1 => x) + // There are missed conditons around things like `typeof` etc (oh well) + // it should be done higher up + if let Type::Constructor(Constructor::ConditionalResult { + condition, + truthy_result, + otherwise_result, + result_union: _, + }) = types.get_type_by_id(get_origin(lhs, types)) + { + if crate::types::helpers::type_equal(*truthy_result, rhs, types) { + narrow_based_on_expression(*condition, false, into, information, types); + } else if crate::types::helpers::type_equal(*otherwise_result, rhs, types) { + narrow_based_on_expression(*condition, true, into, information, types); + } + } + // PROPERTY NARROWING HERE (x.a: b => x: {a: b}) + else if let Type::Constructor(Constructor::Property { on, under, result: _, mode: _, - }) = types.get_type_by_id(lhs) + }) = types.get_type_by_id(get_origin(lhs, types)) { let on = *on; let narrowed_to = if !negate @@ -170,7 +203,7 @@ pub fn narrow_based_on_expression( // on, // types.get_type_by_id(narrowed_to) // ); - into.insert(on, types.new_narrowed(on, narrowed_to)); + into.insert(on, narrowed_to); } } } @@ -179,26 +212,41 @@ pub fn narrow_based_on_expression( operator: CanonicalEqualityAndInequality::LessThan, rhs, } => { - let (lhs, rhs) = (*lhs, *rhs); if negate { + crate::utilities::notify!("Skipping negate on less"); return; } + let lhs = get_origin(*lhs, types); + let rhs = get_origin(*rhs, types); if types.get_type_by_id(lhs).is_dependent() { let narrowed_to = crate::types::intrinsics::new_intrinsic( &crate::types::intrinsics::Intrinsic::LessThan, rhs, types, ); - let narrowed = types.new_narrowed(lhs, narrowed_to); - into.insert(lhs, narrowed); - } else if types.get_type_by_id(rhs).is_dependent() { + // TODO need to merge. This is very bad + let narrowed_to = if let Some(existing) = into.get(&lhs) { + crate::utilities::notify!("Here"); + types.new_and_type(*existing, narrowed_to) + } else { + narrowed_to + }; + into.insert(lhs, narrowed_to); + } + if types.get_type_by_id(rhs).is_dependent() { let narrowed_to = crate::types::intrinsics::new_intrinsic( &crate::types::intrinsics::Intrinsic::GreaterThan, lhs, types, ); - let narrowed = types.new_narrowed(rhs, narrowed_to); - into.insert(rhs, narrowed); + // TODO need to merge. This is very bad + let narrowed_to = if let Some(existing) = into.get(&rhs) { + crate::utilities::notify!("Here"); + types.new_and_type(narrowed_to, *existing) + } else { + narrowed_to + }; + into.insert(rhs, narrowed_to); } } Constructor::TypeOperator(TypeOperator::IsPrototype { lhs, rhs_prototype }) => { @@ -214,8 +262,7 @@ pub fn narrow_based_on_expression( 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); + into.insert(lhs, narrowed_to); } Constructor::TypeExtends(crate::types::TypeExtends { item, extends }) => { let (item, extends) = (*item, *extends); @@ -231,8 +278,7 @@ pub fn narrow_based_on_expression( 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); + into.insert(item, narrowed_to); } Constructor::TypeOperator(TypeOperator::HasProperty(on, under)) => { let on = *on; @@ -253,11 +299,10 @@ pub fn narrow_based_on_expression( ); types.new_or_type_from_iterator(items) }; - into.insert(on, types.new_narrowed(on, narrowed_to)); + into.insert(on, narrowed_to); } constructor => { if let Some(condition) = as_logical_not(constructor, types) { - crate::utilities::notify!("Here"); narrow_based_on_expression(condition, !negate, into, information, types); } else if let Some((lhs, rhs)) = as_logical_and(constructor, types) { // De Morgan's laws @@ -311,8 +356,6 @@ pub fn narrow_based_on_expression( 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 @@ -338,10 +381,7 @@ pub fn narrow_based_on_expression( types, ); let narrowed_to = types.new_or_type_from_iterator(result); - into.insert( - condition, - types.register_type(Type::Narrowed { from: condition, narrowed_to }), - ); + into.insert(condition, narrowed_to); } } } @@ -364,10 +404,11 @@ pub(crate) enum Filter<'a> { 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 FASLY: Filter<'static> = Filter::Falsy; pub(crate) static NOT_FASLY: Filter<'static> = Filter::Not(&FASLY); impl<'a> Filter<'a> { + // Returns `true` if `value` passes filter pub(crate) fn type_matches_filter( &self, value: TypeId, @@ -415,8 +456,12 @@ impl<'a> Filter<'a> { } } Filter::HasProperty { property, filter } => { - let value = - types::properties::get_simple_value(information, value, property, types); + let value = types::properties::get_simple_property_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)); @@ -432,6 +477,12 @@ impl<'a> Filter<'a> { (allowed_match && is_null_or_undefined) || (!allowed_match && !is_null_or_undefined) } Filter::Falsy => { + let can_be_falsy = [TypeId::NUMBER_TYPE, TypeId::STRING_TYPE, TypeId::BOOLEAN_TYPE]; + + if can_be_falsy.contains(&value) { + return true; + } + let is_falsy = [ TypeId::NULL_TYPE, TypeId::UNDEFINED_TYPE, @@ -447,6 +498,7 @@ impl<'a> Filter<'a> { } } +/// Important that this does not handle `any` well with negated filters. It needs to generate negated types but only has non-mutable access to `TypeStore` #[allow(clippy::used_underscore_binding)] pub(crate) fn build_union_from_filter( on: TypeId, @@ -455,11 +507,14 @@ pub(crate) fn build_union_from_filter( information: &impl InformationChain, types: &TypeStore, ) { - if let Some((_condition, lhs, rhs)) = get_conditional(on, types) { + if let Some(constraint) = crate::types::get_constraint(on, types) { + build_union_from_filter(constraint, filter, found, information, types); + } else if let TypeId::BOOLEAN_TYPE = on { + build_union_from_filter(TypeId::TRUE, filter, found, information, types); + build_union_from_filter(TypeId::FALSE, filter, found, information, types); + } else if let Some((_condition, lhs, rhs)) = get_type_as_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) { diff --git a/checker/src/features/operations.rs b/checker/src/features/operations.rs deleted file mode 100644 index 78a88f83..00000000 --- a/checker/src/features/operations.rs +++ /dev/null @@ -1,849 +0,0 @@ -//! Contains logic for mathematical, bitwise and logical operators - -use derive_enum_from_into::EnumFrom; -use source_map::{Span, SpanWithSource}; - -use crate::{ - diagnostics::{TypeCheckError, TypeStringRepresentation}, - features::conditional::new_conditional_context, - types::{ - cast_as_number, cast_as_string, helpers::simple_subtype, intrinsics, is_type_truthy_falsy, - Constructor, PartiallyAppliedGenerics, TypeStore, - }, - CheckingData, Constant, Decidable, Environment, Type, TypeId, -}; - -use super::objects::SpecialObject; - -/// For these **binary** operations both operands are synthesised -#[derive(Clone, Copy, Debug, binary_serialize_derive::BinarySerializable)] -pub enum MathematicalAndBitwise { - Add, - Subtract, - Multiply, - Divide, - Modulo, - Exponent, - BitwiseShiftLeft, - BitwiseShiftRight, - BitwiseShiftRightUnsigned, - BitwiseAnd, - BitwiseXOr, - BitwiseOr, -} - -#[derive(Clone, Copy, Debug, EnumFrom)] -pub enum PureBinaryOperation { - MathematicalAndBitwise(MathematicalAndBitwise), - // Some of these can be reduced - EqualityAndInequality(EqualityAndInequality), -} - -/// TODO report errors better here -pub fn evaluate_pure_binary_operation_handle_errors< - T: crate::ReadFromFS, - A: crate::ASTImplementation, ->( - (lhs, lhs_pos): (TypeId, SpanWithSource), - operator: PureBinaryOperation, - (rhs, rhs_pos): (TypeId, SpanWithSource), - checking_data: &mut CheckingData<T, A>, - environment: &mut Environment, -) -> TypeId { - match operator { - PureBinaryOperation::MathematicalAndBitwise(operator) => { - if let (MathematicalAndBitwise::Exponent, TypeId::ZERO) = (operator, rhs) { - // This holds for NaN. Thus can do in every case - return TypeId::ONE; - } - - let result = evaluate_mathematical_operation( - lhs, - operator, - rhs, - environment, - &mut checking_data.types, - checking_data.options.strict_casts, - ); - - match result { - Ok(result) => result, - Err(_err) => { - let position = lhs_pos - .without_source() - .union(rhs_pos.without_source()) - .with_source(environment.get_source()); - - checking_data.diagnostics_container.add_error( - TypeCheckError::InvalidMathematicalOrBitwiseOperation { - operator, - lhs: TypeStringRepresentation::from_type_id( - lhs, - environment, - &checking_data.types, - false, - ), - rhs: TypeStringRepresentation::from_type_id( - rhs, - environment, - &checking_data.types, - false, - ), - position, - }, - ); - TypeId::ERROR_TYPE - } - } - } - PureBinaryOperation::EqualityAndInequality(operator) => { - // Cannot error, but can be always true or false - let result = evaluate_equality_inequality_operation( - lhs, - &operator, - rhs, - environment, - &mut checking_data.types, - checking_data.options.strict_casts, - ); - - if let Ok((result, warning)) = result { - if let EqualityAndInequalityResultKind::Disjoint = warning { - let position = lhs_pos - .without_source() - .union(rhs_pos.without_source()) - .with_source(environment.get_source()); - - checking_data.diagnostics_container.add_warning( - crate::TypeCheckWarning::DisjointEquality { - lhs: TypeStringRepresentation::from_type_id( - lhs, - environment, - &checking_data.types, - false, - ), - rhs: TypeStringRepresentation::from_type_id( - rhs, - environment, - &checking_data.types, - false, - ), - position, - }, - ); - } - - result - } else { - let position = lhs_pos - .without_source() - .union(rhs_pos.without_source()) - .with_source(environment.get_source()); - - checking_data.diagnostics_container.add_error( - crate::TypeCheckError::InvalidEqualityOperation { - operator, - lhs: TypeStringRepresentation::from_type_id( - lhs, - environment, - &checking_data.types, - false, - ), - rhs: TypeStringRepresentation::from_type_id( - rhs, - environment, - &checking_data.types, - false, - ), - position, - }, - ); - - TypeId::ERROR_TYPE - } - } - } -} - -/// TODO proper error type -pub fn evaluate_mathematical_operation( - lhs: TypeId, - operator: MathematicalAndBitwise, - rhs: TypeId, - info: &impl crate::context::InformationChain, - types: &mut TypeStore, - strict_casts: bool, -) -> Result<TypeId, ()> { - fn attempt_constant_math_operator( - lhs: TypeId, - operator: MathematicalAndBitwise, - rhs: TypeId, - types: &mut TypeStore, - strict_casts: bool, - ) -> Result<TypeId, ()> { - if let MathematicalAndBitwise::Add = operator { - let constant = match (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) { - (Type::Constant(Constant::Number(lhs)), Type::Constant(Constant::Number(rhs))) => { - Constant::Number(lhs + rhs) - } - (Type::Constant(lhs), Type::Constant(rhs)) => { - let mut first = cast_as_string(lhs, strict_casts)?; - let second = cast_as_string(rhs, strict_casts)?; - // Concatenate strings - first.push_str(&second); - Constant::String(first) - } - _ => return Err(()), - }; - Ok(types.new_constant_type(constant)) - } else { - match (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) { - (Type::Constant(c1), Type::Constant(c2)) => { - let lhs = cast_as_number(c1, strict_casts).unwrap_or(f64::NAN); - let rhs = cast_as_number(c2, strict_casts).unwrap_or(f64::NAN); - // TODO hopefully Rust implementation is the same as JS - #[allow(clippy::cast_possible_truncation)] - let value = match operator { - MathematicalAndBitwise::Add => unreachable!(), - MathematicalAndBitwise::Subtract => lhs - rhs, - MathematicalAndBitwise::Multiply => lhs * rhs, - MathematicalAndBitwise::Divide => lhs / rhs, - MathematicalAndBitwise::Modulo => lhs % rhs, - MathematicalAndBitwise::Exponent => lhs.powf(rhs), - MathematicalAndBitwise::BitwiseShiftLeft => { - f64::from((lhs as i32).checked_shl(rhs as u32).unwrap_or(0)) - } - MathematicalAndBitwise::BitwiseShiftRight => { - f64::from((lhs as i32).checked_shr(rhs as u32).unwrap_or(0)) - } - MathematicalAndBitwise::BitwiseShiftRightUnsigned => { - (lhs as i32).wrapping_shr(rhs as u32).into() - } - MathematicalAndBitwise::BitwiseAnd => { - f64::from((lhs as i32) & (rhs as i32)) - } - MathematicalAndBitwise::BitwiseXOr => { - f64::from((lhs as i32) ^ (rhs as i32)) - } - MathematicalAndBitwise::BitwiseOr => f64::from((lhs as i32) | (rhs as i32)), - }; - let value = ordered_float::NotNan::try_from(value); - let ty = match value { - Ok(value) => types.new_constant_type(Constant::Number(value)), - Err(_) => TypeId::NAN, - }; - Ok(ty) - } - _ => Err(()), - } - } - } - - if lhs == TypeId::ERROR_TYPE || rhs == TypeId::ERROR_TYPE { - return Ok(TypeId::ERROR_TYPE); - } - - let is_dependent = - types.get_type_by_id(lhs).is_dependent() || types.get_type_by_id(rhs).is_dependent(); - - if is_dependent { - let can_be_string = if let MathematicalAndBitwise::Add = operator { - let left_is_string = simple_subtype(lhs, TypeId::STRING_TYPE, info, types); - let right_is_string = simple_subtype(lhs, TypeId::STRING_TYPE, info, types); - let left_is_string_or_number = - left_is_string || simple_subtype(lhs, TypeId::NUMBER_TYPE, info, types); - let right_is_string_or_number = - right_is_string || simple_subtype(rhs, TypeId::NUMBER_TYPE, info, types); - if !left_is_string_or_number || !right_is_string_or_number { - return Err(()); - } - left_is_string || right_is_string - } else { - let left_is_number = simple_subtype(lhs, TypeId::NUMBER_TYPE, info, types); - if !left_is_number || !simple_subtype(rhs, TypeId::NUMBER_TYPE, info, types) { - return Err(()); - } - false - }; - - // :) - if let (MathematicalAndBitwise::Exponent, TypeId::ONE, true) = - (operator, rhs, intrinsics::is_not_not_a_number(lhs, types)) - { - return Ok(lhs); - } else if let (MathematicalAndBitwise::Add, TypeId::ZERO) - | (MathematicalAndBitwise::Multiply, TypeId::ONE) = (operator, rhs) - { - return Ok(lhs); - } else if let (MathematicalAndBitwise::Add, TypeId::ZERO) - | (MathematicalAndBitwise::Multiply, TypeId::ONE) = (operator, lhs) - { - return Ok(rhs); - } - - let result = if can_be_string { - TypeId::STRING_TYPE - } else if let ( - MathematicalAndBitwise::Add | MathematicalAndBitwise::Multiply, - Some(lhs_range), - Some(rhs_range), - ) = (operator, intrinsics::get_range(lhs, types), intrinsics::get_range(rhs, types)) - { - match operator { - MathematicalAndBitwise::Add => { - intrinsics::range_to_type(lhs_range.space_addition(rhs_range), types) - } - MathematicalAndBitwise::Multiply => { - intrinsics::range_to_type(lhs_range.space_multiplication(rhs_range), types) - } - _ => unreachable!(), - } - } else { - TypeId::NUMBER_TYPE - }; - - let constructor = crate::types::Constructor::BinaryOperator { lhs, operator, rhs, result }; - Ok(types.register_type(crate::Type::Constructor(constructor))) - } else { - attempt_constant_math_operator(lhs, operator, rhs, types, strict_casts) - } -} - -/// Not canonical / reducible form of [`CanonicalEqualityAndInequality`]. -/// (for examples `a > b` is equivalent to `b < a` (after side effects) and `a !== b` is equivalent to `!(a === b)`) -#[derive(Clone, Copy, Debug)] -pub enum EqualityAndInequality { - StrictEqual, - StrictNotEqual, - Equal, - NotEqual, - GreaterThan, - LessThan, - LessThanOrEqual, - GreaterThanOrEqual, -} - -/// Canonical / irreducible form of [`EqualityAndInequality`]. -#[derive(Clone, Copy, Debug, binary_serialize_derive::BinarySerializable)] -pub enum CanonicalEqualityAndInequality { - StrictEqual, - LessThan, -} - -pub enum EqualityAndInequalityResultKind { - Constant, - Disjoint, - Condition, -} - -pub fn evaluate_equality_inequality_operation( - mut lhs: TypeId, - operator: &EqualityAndInequality, - mut rhs: TypeId, - info: &impl crate::context::InformationChain, - types: &mut TypeStore, - strict_casts: bool, -) -> Result<(TypeId, EqualityAndInequalityResultKind), ()> { - // `NaN == t` is always true - if lhs == TypeId::NAN || rhs == TypeId::NAN { - return Ok((TypeId::FALSE, EqualityAndInequalityResultKind::Constant)); - } - - match operator { - EqualityAndInequality::StrictEqual => { - // crate::utilities::notify!("{:?} === {:?}", lhs, rhs); - - let left_dependent = types.get_type_by_id(lhs).is_dependent(); - let is_dependent = left_dependent || types.get_type_by_id(rhs).is_dependent(); - - if is_dependent { - if lhs == rhs - && intrinsics::is_not_not_a_number(lhs, types) - && intrinsics::is_not_not_a_number(rhs, types) - { - // I think this is okay - return Ok((TypeId::TRUE, EqualityAndInequalityResultKind::Constant)); - } - - // Checks lhs and rhs type to see if they overlap - if crate::types::disjoint::types_are_disjoint( - lhs, - rhs, - &mut Vec::new(), - info, - types, - ) { - return Ok((TypeId::FALSE, EqualityAndInequalityResultKind::Disjoint)); - } - - // 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, - rhs, - }; - - Ok(( - types.register_type(crate::Type::Constructor(constructor)), - EqualityAndInequalityResultKind::Condition, - )) - } else { - match attempt_constant_equality(lhs, rhs, types) { - Ok(ty) => Ok(( - if ty { TypeId::TRUE } else { TypeId::FALSE }, - EqualityAndInequalityResultKind::Constant, - )), - Err(()) => { - unreachable!( - "should have been caught `is_dependent` above, {:?} === {:?}", - types.get_type_by_id(lhs), - types.get_type_by_id(rhs) - ) - } - } - } - } - EqualityAndInequality::LessThan => { - fn attempt_less_than( - lhs: TypeId, - rhs: TypeId, - types: &mut TypeStore, - strict_casts: bool, - ) -> Result<bool, ()> { - // Similar but reversed semantics to add - match (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) { - ( - Type::Constant(Constant::String(string1)), - Type::Constant(Constant::String(string2)), - ) => { - // Yah rust includes string alphanumerical equivalence of strings - Ok(string1 < string2) - } - (Type::Constant(c1), Type::Constant(c2)) => { - let lhs = cast_as_number(c1, strict_casts)?; - let rhs = cast_as_number(c2, strict_casts)?; - Ok(lhs < rhs) - } - (lhs, rhs) => { - crate::utilities::notify!("{:?}", (lhs, rhs)); - // Ok(TypeId::OPEN_BOOLEAN_TYPE) - Err(()) - } - } - } - - let is_dependent = types.get_type_by_id(lhs).is_dependent() - || types.get_type_by_id(rhs).is_dependent(); - - if is_dependent { - { - if let Type::Constructor(Constructor::BinaryOperator { - lhs: op_lhs, - operator, - rhs: op_rhs, - result: _, - }) = types.get_type_by_id(lhs) - { - if let ( - Type::Constant(Constant::Number(add)), - MathematicalAndBitwise::Add, - Type::Constant(Constant::Number(lt)), - ) = (types.get_type_by_id(*op_rhs), operator, types.get_type_by_id(rhs)) - { - crate::utilities::notify!("Shifted LT"); - lhs = *op_lhs; - rhs = types.register_type(Type::Constant(Constant::Number(lt - add))); - } - } - } - - { - // let lhs = get_constraint(lhs, types).unwrap_or(lhs); - // let rhs = get_constraint(rhs, types).unwrap_or(rhs); - - if !simple_subtype(lhs, TypeId::NUMBER_TYPE, info, types) - || !simple_subtype(rhs, TypeId::NUMBER_TYPE, info, types) - { - return Err(()); - } - - // Tidies some things for counting loop iterations - - // Checking disjoint-ness for inequalities (TODO under option) via distribution - if let (Some(lhs_range), Some(rhs_range)) = - (intrinsics::get_range(lhs, types), intrinsics::get_range(rhs, types)) - { - if lhs_range.below(rhs_range) { - return Ok((TypeId::TRUE, EqualityAndInequalityResultKind::Constant)); - } - if lhs_range.above(rhs_range) { - return Ok((TypeId::FALSE, EqualityAndInequalityResultKind::Disjoint)); - } - } - } - - let constructor = Constructor::CanonicalRelationOperator { - lhs, - operator: CanonicalEqualityAndInequality::LessThan, - rhs, - }; - Ok(( - types.register_type(crate::Type::Constructor(constructor)), - EqualityAndInequalityResultKind::Condition, - )) - } else { - attempt_less_than(lhs, rhs, types, strict_casts).map(|value| { - ( - if value { TypeId::TRUE } else { TypeId::FALSE }, - EqualityAndInequalityResultKind::Constant, - ) - }) - } - } - // equal OR less than - EqualityAndInequality::LessThanOrEqual => { - let (equality_result, warning) = evaluate_equality_inequality_operation( - lhs, - &EqualityAndInequality::StrictEqual, - rhs, - info, - types, - strict_casts, - )?; - - if equality_result == TypeId::TRUE { - Ok((equality_result, warning)) - } else if equality_result == TypeId::FALSE { - evaluate_equality_inequality_operation( - lhs, - &EqualityAndInequality::LessThan, - rhs, - info, - types, - strict_casts, - ) - } else { - let (less_than_result, warning) = evaluate_equality_inequality_operation( - lhs, - &EqualityAndInequality::LessThan, - rhs, - info, - types, - strict_casts, - )?; - Ok((types.new_logical_or_type(equality_result, less_than_result), warning)) - } - } - EqualityAndInequality::StrictNotEqual => { - let (equality_result, kind) = evaluate_equality_inequality_operation( - lhs, - &EqualityAndInequality::StrictEqual, - rhs, - info, - types, - strict_casts, - )?; - if let EqualityAndInequalityResultKind::Condition = kind { - Ok((types.new_logical_negation_type(equality_result), kind)) - } else { - let negated = if let TypeId::TRUE = equality_result { - TypeId::FALSE - } else if let TypeId::FALSE = equality_result { - TypeId::TRUE - } else { - todo!() - }; - Ok((negated, kind)) - } - } - EqualityAndInequality::Equal => { - crate::utilities::notify!("TODO equal operator"); - Ok((TypeId::OPEN_BOOLEAN_TYPE, EqualityAndInequalityResultKind::Condition)) - } - EqualityAndInequality::NotEqual => { - let (equality_result, kind) = evaluate_equality_inequality_operation( - lhs, - &EqualityAndInequality::Equal, - rhs, - info, - types, - strict_casts, - )?; - if let EqualityAndInequalityResultKind::Condition = kind { - Ok((types.new_logical_negation_type(equality_result), kind)) - } else { - let negated = if let TypeId::TRUE = equality_result { - TypeId::FALSE - } else if let TypeId::FALSE = equality_result { - TypeId::TRUE - } else { - todo!() - }; - Ok((negated, kind)) - } - } - // Swapping operands! - EqualityAndInequality::GreaterThan => evaluate_equality_inequality_operation( - rhs, - &EqualityAndInequality::LessThan, - lhs, - info, - types, - strict_casts, - ), - // Swapping operands! - EqualityAndInequality::GreaterThanOrEqual => evaluate_equality_inequality_operation( - rhs, - &EqualityAndInequality::LessThanOrEqual, - lhs, - info, - types, - strict_casts, - ), - } -} - -#[allow(clippy::let_and_return)] -pub fn is_null_or_undefined( - ty: TypeId, - info: &impl crate::context::InformationChain, - types: &mut TypeStore, -) -> TypeId { - let is_null = evaluate_equality_inequality_operation( - ty, - &EqualityAndInequality::StrictEqual, - TypeId::NULL_TYPE, - info, - types, - false, - ) - .map_or(TypeId::ERROR_TYPE, |(left, _)| left); - - if let TypeId::TRUE = is_null { - is_null - } else { - let is_undefined = evaluate_equality_inequality_operation( - ty, - &EqualityAndInequality::StrictEqual, - TypeId::UNDEFINED_TYPE, - info, - types, - false, - ) - .map_or(TypeId::ERROR_TYPE, |(left, _)| left); - - if let TypeId::FALSE = is_null { - is_undefined - } else { - types.new_logical_or_type(is_null, is_undefined) - } - } -} - -#[derive(Copy, Clone, Debug)] -pub enum LogicalOperator { - And, - Or, - /// TODO is this canonical? - NullCoalescing, -} - -/// TODO strict casts! -pub fn evaluate_logical_operation_with_expression< - 'a, - T: crate::ReadFromFS, - A: crate::ASTImplementation, ->( - lhs: (TypeId, Span), - operator: LogicalOperator, - rhs: &'a A::Expression<'a>, - checking_data: &mut CheckingData<T, A>, - environment: &mut Environment, - expecting: TypeId, -) -> Result<TypeId, ()> { - match operator { - LogicalOperator::And => Ok(new_conditional_context( - environment, - lhs, - |env: &mut Environment, data: &mut CheckingData<T, A>| { - A::synthesise_expression(rhs, expecting, env, data) - }, - Some(|_env: &mut Environment, _data: &mut CheckingData<T, A>| lhs.0), - checking_data, - )), - LogicalOperator::Or => Ok(new_conditional_context( - environment, - lhs, - |env: &mut Environment, checking_data: &mut CheckingData<T, A>| { - 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<T, A>| { - A::synthesise_expression(rhs, expecting, env, data) - }), - checking_data, - )), - LogicalOperator::NullCoalescing => { - let is_lhs_null_or_undefined = - is_null_or_undefined(lhs.0, environment, &mut checking_data.types); - // Equivalent to: `(lhs is null or undefined) ? lhs : rhs` - Ok(new_conditional_context( - environment, - (is_lhs_null_or_undefined, lhs.1), - |env: &mut Environment, checking_data: &mut CheckingData<T, A>| { - 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<T, A>| { - A::synthesise_expression(rhs, expecting, env, data) - }), - checking_data, - )) - } - } -} - -/// `typeof` and some others done elsewhere -#[derive(Clone, Copy, Debug, binary_serialize_derive::BinarySerializable)] -pub enum UnaryOperation { - /// Treated as `(value ? false : true)` - LogicalNot, - /// Treated as `0 - value` (could also do -1 * value?) - Negation, - /// Treated as `value ^ 0xFFFF_FFFF` - BitwiseNot, -} - -/// Tries to evaluate unary operation for constant terms. Else delegates to binary operations that handle equivalent thing -pub fn evaluate_unary_operator( - operator: UnaryOperation, - operand: TypeId, - info: &impl crate::context::InformationChain, - types: &mut TypeStore, - strict_casts: bool, -) -> Result<TypeId, ()> { - if operand == TypeId::ERROR_TYPE { - return Ok(operand); - } - - match operator { - UnaryOperation::LogicalNot => { - if let Decidable::Known(value) = is_type_truthy_falsy(operand, types) { - if value { - Ok(TypeId::FALSE) - } else { - Ok(TypeId::TRUE) - } - } else { - let is_boolean = simple_subtype(operand, TypeId::BOOLEAN_TYPE, info, types); - if is_boolean { - Ok(types.new_logical_negation_type(operand)) - } else { - Err(()) - } - } - } - UnaryOperation::Negation | UnaryOperation::BitwiseNot => { - if let Type::Constant(cst) = types.get_type_by_id(operand) { - let value = cast_as_number(cst, strict_casts).expect("hmm"); - let value = match operator { - UnaryOperation::BitwiseNot => f64::from(!(value as i32)), - UnaryOperation::Negation => -value, - UnaryOperation::LogicalNot => unreachable!(), - }; - let value = ordered_float::NotNan::try_from(value); - Ok(match value { - Ok(value) => types.new_constant_type(Constant::Number(value)), - Err(_) => TypeId::NAN, - }) - } else { - match operator { - UnaryOperation::BitwiseNot => evaluate_mathematical_operation( - TypeId::MAX_U32, - MathematicalAndBitwise::BitwiseXOr, - operand, - info, - types, - strict_casts, - ), - UnaryOperation::Negation => evaluate_mathematical_operation( - TypeId::ZERO, - MathematicalAndBitwise::Subtract, - operand, - info, - types, - strict_casts, - ), - UnaryOperation::LogicalNot => unreachable!("handled above"), - } - } - } - } -} - -/// Returns whether lhs and rhs are always equal or never equal. TODO more -/// -/// TODO return decidable. -fn attempt_constant_equality(lhs: TypeId, rhs: TypeId, types: &mut TypeStore) -> Result<bool, ()> { - if lhs == rhs { - Ok(true) - } else if matches!(lhs, TypeId::NULL_TYPE | TypeId::UNDEFINED_TYPE) - || matches!(rhs, TypeId::NULL_TYPE | TypeId::UNDEFINED_TYPE) - { - // If above `==`` failed => false (as always have same `TypeId`) - Ok(false) - } else { - let lhs = types.get_type_by_id(lhs); - let rhs = types.get_type_by_id(rhs); - if let (Type::Constant(cst1), Type::Constant(cst2)) = (lhs, rhs) { - Ok(cst1 == cst2) - } else if let (Type::Object(..) | Type::SpecialObject(SpecialObject::Function(..)), _) - | (_, Type::Object(..) | Type::SpecialObject(SpecialObject::Function(..))) = (lhs, rhs) - { - // Same objects and functions always have same type id. Poly case doesn't occur here - Ok(false) - } - // Temp fix for closures - else if let ( - Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on: on_lhs, .. }), - Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on: on_rhs, .. }), - ) = (lhs, rhs) - { - // TODO does this work? - attempt_constant_equality(*on_lhs, *on_rhs, types) - } else { - crate::utilities::notify!("{:?} === {:?} is apparently false", lhs, rhs); - Err(()) - } - } -} diff --git a/checker/src/features/operations/logical.rs b/checker/src/features/operations/logical.rs new file mode 100644 index 00000000..25ecfc72 --- /dev/null +++ b/checker/src/features/operations/logical.rs @@ -0,0 +1,113 @@ +use super::{ + super::{conditional, narrowing}, + relation::is_null_or_undefined, +}; +use crate::{Type, TypeId}; + +#[derive(Copy, Clone, Debug)] +pub enum LogicalOperator { + And, + Or, + /// TODO is this canonical? + NullCoalescing, +} + +/// TODO strict casts! +pub fn evaluate_logical_operation_with_expression< + 'a, + T: crate::ReadFromFS, + A: crate::ASTImplementation, +>( + lhs: (TypeId, source_map::Span), + operator: LogicalOperator, + rhs: &'a A::Expression<'a>, + checking_data: &mut crate::CheckingData<T, A>, + environment: &mut crate::Environment, + expecting: TypeId, +) -> Result<TypeId, ()> { + match operator { + LogicalOperator::And => Ok(conditional::new_conditional_context( + environment, + lhs, + |env: &mut crate::Environment, data: &mut crate::CheckingData<T, A>| { + A::synthesise_expression(rhs, expecting, env, data) + }, + Some(|env: &mut crate::Environment, checking_data: &mut crate::CheckingData<T, A>| { + if let Some(constraint) = crate::types::get_constraint(lhs.0, &checking_data.types) + { + let mut result = Vec::new(); + narrowing::build_union_from_filter( + constraint, + narrowing::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 + } + }), + checking_data, + )), + LogicalOperator::Or => Ok(conditional::new_conditional_context( + environment, + lhs, + |env: &mut crate::Environment, checking_data: &mut crate::CheckingData<T, A>| { + if let Some(constraint) = crate::types::get_constraint(lhs.0, &checking_data.types) + { + let mut result = Vec::new(); + narrowing::build_union_from_filter( + constraint, + 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 crate::Environment, data: &mut crate::CheckingData<T, A>| { + A::synthesise_expression(rhs, expecting, env, data) + }), + checking_data, + )), + LogicalOperator::NullCoalescing => { + let is_lhs_null_or_undefined = + is_null_or_undefined(lhs.0, environment, &mut checking_data.types); + // Equivalent to: `(lhs is null or undefined) ? lhs : rhs` + Ok(conditional::new_conditional_context( + environment, + (is_lhs_null_or_undefined, lhs.1), + |env: &mut crate::Environment, checking_data: &mut crate::CheckingData<T, A>| { + if let Some(constraint) = + crate::types::get_constraint(lhs.0, &checking_data.types) + { + let mut result = Vec::new(); + narrowing::build_union_from_filter( + constraint, + 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 crate::Environment, data: &mut crate::CheckingData<T, A>| { + A::synthesise_expression(rhs, expecting, env, data) + }), + checking_data, + )) + } + } +} diff --git a/checker/src/features/operations/mathematical_bitwise.rs b/checker/src/features/operations/mathematical_bitwise.rs new file mode 100644 index 00000000..240e659a --- /dev/null +++ b/checker/src/features/operations/mathematical_bitwise.rs @@ -0,0 +1,262 @@ +use crate::{ + types::{cast_as_number, cast_as_string, helpers, intrinsics, Constant, Type}, + TypeId, +}; + +/// For these **binary** operations both operands are synthesised +#[derive(Clone, Copy, Debug, binary_serialize_derive::BinarySerializable)] +pub enum MathematicalOrBitwiseOperation { + Add, + Subtract, + Multiply, + Divide, + Modulo, + Exponent, + BitwiseShiftLeft, + BitwiseShiftRight, + BitwiseShiftRightUnsigned, + BitwiseAnd, + BitwiseXOr, + BitwiseOr, +} + +/// TODO proper error type +pub fn evaluate_mathematical_operation( + lhs: TypeId, + operator: MathematicalOrBitwiseOperation, + rhs: TypeId, + info: &impl crate::context::InformationChain, + types: &mut crate::TypeStore, + strict_casts: bool, + operate_on_number_intrinsics: bool, +) -> Result<TypeId, ()> { + fn attempt_constant_math_operator( + lhs: TypeId, + operator: MathematicalOrBitwiseOperation, + rhs: TypeId, + types: &mut crate::TypeStore, + strict_casts: bool, + ) -> Result<TypeId, ()> { + if let MathematicalOrBitwiseOperation::Add = operator { + match (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) { + (Type::Constant(Constant::Number(lhs)), Type::Constant(Constant::Number(rhs))) => { + Ok(types.new_constant_type(Constant::Number(lhs + rhs))) + } + (Type::Constant(lhs), Type::Constant(rhs)) => { + let mut first = cast_as_string(lhs, strict_casts)?; + let second = cast_as_string(rhs, strict_casts)?; + // Concatenate strings + first.push_str(&second); + Ok(types.new_constant_type(Constant::String(first))) + } + (lhs, rhs) => { + crate::utilities::notify!("here {:?} + {:?}", lhs, rhs); + Err(()) + } + } + } else { + match (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) { + (Type::Constant(c1), Type::Constant(c2)) => { + let lhs = cast_as_number(c1, strict_casts).unwrap_or(f64::NAN); + let rhs = cast_as_number(c2, strict_casts).unwrap_or(f64::NAN); + // TODO hopefully Rust implementation is the same as JS + #[allow(clippy::cast_possible_truncation)] + let value = match operator { + MathematicalOrBitwiseOperation::Add => unreachable!(), + MathematicalOrBitwiseOperation::Subtract => lhs - rhs, + MathematicalOrBitwiseOperation::Multiply => lhs * rhs, + MathematicalOrBitwiseOperation::Divide => lhs / rhs, + MathematicalOrBitwiseOperation::Modulo => lhs % rhs, + MathematicalOrBitwiseOperation::Exponent => lhs.powf(rhs), + MathematicalOrBitwiseOperation::BitwiseShiftLeft => { + f64::from((lhs as i32).checked_shl(rhs as u32).unwrap_or(0)) + } + MathematicalOrBitwiseOperation::BitwiseShiftRight => { + f64::from((lhs as i32).checked_shr(rhs as u32).unwrap_or(0)) + } + MathematicalOrBitwiseOperation::BitwiseShiftRightUnsigned => { + (lhs as i32).wrapping_shr(rhs as u32).into() + } + MathematicalOrBitwiseOperation::BitwiseAnd => { + f64::from((lhs as i32) & (rhs as i32)) + } + MathematicalOrBitwiseOperation::BitwiseXOr => { + f64::from((lhs as i32) ^ (rhs as i32)) + } + MathematicalOrBitwiseOperation::BitwiseOr => { + f64::from((lhs as i32) | (rhs as i32)) + } + }; + let value = ordered_float::NotNan::try_from(value); + let ty = match value { + Ok(value) => types.new_constant_type(Constant::Number(value)), + Err(_) => TypeId::NAN, + }; + Ok(ty) + } + (lhs, rhs) => { + crate::utilities::notify!("here {:?} @ {:?}", lhs, rhs); + Err(()) + } + } + } + } + + let lhs_ty = types.get_type_by_id(lhs); + let rhs_ty = types.get_type_by_id(rhs); + + if lhs == TypeId::ERROR_TYPE || rhs == TypeId::ERROR_TYPE { + return Ok(TypeId::ERROR_TYPE); + } + + crate::utilities::notify!("lhs={:?}, rhs={:?}", lhs_ty, rhs_ty); + let either_dependent = lhs_ty.is_dependent() || rhs_ty.is_dependent(); + + if either_dependent { + let can_be_string = if let MathematicalOrBitwiseOperation::Add = operator { + let left_is_string = helpers::simple_subtype(lhs, TypeId::STRING_TYPE, info, types); + let right_is_string = helpers::simple_subtype(lhs, TypeId::STRING_TYPE, info, types); + let left_is_string_or_number = + left_is_string || helpers::simple_subtype(lhs, TypeId::NUMBER_TYPE, info, types); + let right_is_string_or_number = + right_is_string || helpers::simple_subtype(rhs, TypeId::NUMBER_TYPE, info, types); + if !left_is_string_or_number || !right_is_string_or_number { + crate::utilities::notify!("Here"); + return Err(()); + } + + left_is_string || right_is_string + } else { + if !helpers::simple_subtype(lhs, TypeId::NUMBER_TYPE, info, types) + || !helpers::simple_subtype(rhs, TypeId::NUMBER_TYPE, info, types) + { + crate::utilities::notify!("Here :/"); + return Err(()); + } + + false + }; + + // :) + if let (MathematicalOrBitwiseOperation::Exponent, TypeId::ZERO) = (operator, rhs) { + // This holds for NaN. Thus can do in every case + return Ok(TypeId::ONE); + } else if let (MathematicalOrBitwiseOperation::Exponent, TypeId::ONE, true) = + (operator, rhs, intrinsics::is_not_not_a_number(lhs, types)) + { + return Ok(lhs); + } else if let (MathematicalOrBitwiseOperation::Add, TypeId::ZERO) + | (MathematicalOrBitwiseOperation::Multiply, TypeId::ONE) = (operator, rhs) + { + return Ok(lhs); + } else if let (MathematicalOrBitwiseOperation::Add, TypeId::ZERO) + | (MathematicalOrBitwiseOperation::Multiply, TypeId::ONE) = (operator, lhs) + { + return Ok(rhs); + } + + if let ( + MathematicalOrBitwiseOperation::Add | MathematicalOrBitwiseOperation::Multiply, + (lhs_range, lhs_modulo), + (rhs_range, rhs_modulo), + true, + ) = ( + operator, + intrinsics::get_range_and_mod_class(lhs, types), + intrinsics::get_range_and_mod_class(rhs, types), + operate_on_number_intrinsics, + ) { + crate::utilities::notify!( + "{:?} with {:?}. {:?} & {:?}", + (lhs_range, lhs_modulo), + (rhs_range, rhs_modulo), + rhs_range.and_then(crate::utilities::float_range::FloatRange::as_single), + lhs_range.and_then(crate::utilities::float_range::FloatRange::as_single) + ); + if let (Some(lhs_range), Some(rhs_range)) = (lhs_range, rhs_range) { + let range = match operator { + MathematicalOrBitwiseOperation::Add => lhs_range.space_addition(rhs_range), + MathematicalOrBitwiseOperation::Multiply => { + lhs_range.space_multiplication(rhs_range) + } + _ => unreachable!(), + }; + // That is a lot types + let result = intrinsics::range_to_type(range, types); + let constructor = + crate::types::Constructor::BinaryOperator { lhs, operator, rhs, result }; + + return Ok(types.register_type(crate::Type::Constructor(constructor))); + } + + // else if let (Some(lhs_modulo), Some(offset)) = + // (lhs_modulo, rhs_range.and_then(crate::utilities::float_range::FloatRange::as_single)) + // { + // crate::utilities::notify!("Here"); + // let mod_class = lhs_modulo.offset(offset); + // intrinsics::modulo_to_type(mod_class, types) + // } else if let (Some(rhs_modulo), Some(offset)) = + // (rhs_modulo, lhs_range.and_then(crate::utilities::float_range::FloatRange::as_single)) + // { + // let mod_class = rhs_modulo.offset(offset); + // intrinsics::modulo_to_type(mod_class, types) + // } + } + + if let (Some((condition, lhs_truthy_result, lhs_falsy_result)), true) = + (helpers::get_type_as_conditional(lhs, types), operate_on_number_intrinsics) + { + crate::utilities::notify!("Here lhs dependent"); + let truthy_result = evaluate_mathematical_operation( + lhs_truthy_result, + operator, + rhs, + info, + types, + strict_casts, + operate_on_number_intrinsics, + )?; + let falsy_result = evaluate_mathematical_operation( + lhs_falsy_result, + operator, + rhs, + info, + types, + strict_casts, + operate_on_number_intrinsics, + )?; + + Ok(types.new_conditional_type(condition, truthy_result, falsy_result)) + } else if let (true, Some((condition, rhs_truthy_result, rhs_falsy_result))) = + (operate_on_number_intrinsics, helpers::get_type_as_conditional(rhs, types)) + { + let truthy_result = evaluate_mathematical_operation( + lhs, + operator, + rhs_truthy_result, + info, + types, + strict_casts, + operate_on_number_intrinsics, + )?; + let falsy_result = evaluate_mathematical_operation( + lhs, + operator, + rhs_falsy_result, + info, + types, + strict_casts, + operate_on_number_intrinsics, + )?; + + Ok(types.new_conditional_type(condition, truthy_result, falsy_result)) + } else { + let result = if can_be_string { TypeId::STRING_TYPE } else { TypeId::NUMBER_TYPE }; + let constructor = + crate::types::Constructor::BinaryOperator { lhs, operator, rhs, result }; + Ok(types.register_type(crate::Type::Constructor(constructor))) + } + } else { + attempt_constant_math_operator(lhs, operator, rhs, types, strict_casts) + } +} diff --git a/checker/src/features/operations/mod.rs b/checker/src/features/operations/mod.rs new file mode 100644 index 00000000..a0e3855f --- /dev/null +++ b/checker/src/features/operations/mod.rs @@ -0,0 +1,12 @@ +mod logical; +mod mathematical_bitwise; +mod relation; +mod unary; + +pub use logical::{evaluate_logical_operation_with_expression, LogicalOperator}; +pub use mathematical_bitwise::{evaluate_mathematical_operation, MathematicalOrBitwiseOperation}; +pub use relation::{ + evaluate_equality_inequality_operation, is_null_or_undefined, CanonicalEqualityAndInequality, + EqualityAndInequality, EqualityAndInequalityResultKind, +}; +pub use unary::{evaluate_unary_operator, UnaryOperation}; diff --git a/checker/src/features/operations/relation.rs b/checker/src/features/operations/relation.rs new file mode 100644 index 00000000..b703e135 --- /dev/null +++ b/checker/src/features/operations/relation.rs @@ -0,0 +1,370 @@ +use super::MathematicalOrBitwiseOperation; +use crate::types::{ + cast_as_number, disjoint, get_constraint, helpers, intrinsics, Constant, Constructor, + PartiallyAppliedGenerics, Type, TypeId, +}; + +/// Not canonical / reducible form of [`CanonicalEqualityAndInequality`]. +/// (for examples `a > b` is equivalent to `b < a` (after side effects) and `a !== b` is equivalent to `!(a === b)`) +#[derive(Clone, Copy, Debug)] +pub enum EqualityAndInequality { + StrictEqual, + StrictNotEqual, + Equal, + NotEqual, + GreaterThan, + LessThan, + LessThanOrEqual, + GreaterThanOrEqual, +} + +/// Canonical / irreducible form of [`EqualityAndInequality`]. +#[derive(Clone, Copy, Debug, binary_serialize_derive::BinarySerializable)] +pub enum CanonicalEqualityAndInequality { + StrictEqual, + LessThan, +} + +pub enum EqualityAndInequalityResultKind { + Constant, + Disjoint, + Condition, +} + +pub fn evaluate_equality_inequality_operation( + mut lhs: TypeId, + operator: &EqualityAndInequality, + mut rhs: TypeId, + info: &impl crate::context::InformationChain, + types: &mut crate::types::TypeStore, + strict_casts: bool, +) -> Result<(TypeId, EqualityAndInequalityResultKind), ()> { + // `NaN == t` is always true + if lhs == TypeId::NAN || rhs == TypeId::NAN { + return Ok((TypeId::FALSE, EqualityAndInequalityResultKind::Constant)); + } + + match operator { + EqualityAndInequality::StrictEqual => { + // crate::utilities::notify!("{:?} === {:?}", lhs, rhs); + + if let (Type::Constant(lhs), Type::Constant(rhs)) = + (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) + { + let result = if lhs == rhs { TypeId::TRUE } else { TypeId::FALSE }; + return Ok((result, EqualityAndInequalityResultKind::Constant)); + } + + if let ( + Type::Object(_) | Type::SpecialObject(_), + Type::Object(_) | Type::SpecialObject(_), + ) = (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) + { + let result = if lhs == rhs { TypeId::TRUE } else { TypeId::FALSE }; + return Ok((result, EqualityAndInequalityResultKind::Constant)); + } + + // if is_dependent { + if lhs == rhs + && intrinsics::is_not_not_a_number(lhs, types) + && intrinsics::is_not_not_a_number(rhs, types) + { + // I think this is okay + return Ok((TypeId::TRUE, EqualityAndInequalityResultKind::Constant)); + } + + // Checks lhs and rhs type to see if they overlap + if disjoint::types_are_disjoint(lhs, rhs, &mut Vec::new(), info, types) { + return Ok((TypeId::FALSE, EqualityAndInequalityResultKind::Disjoint)); + } + + let left_dependent = types.get_type_by_id(lhs).is_dependent(); + + // Sort if `*constant* == ...`. Ideally want constant type on the RHS + let (lhs, rhs) = if left_dependent { (lhs, rhs) } else { (rhs, rhs) }; + let operator = CanonicalEqualityAndInequality::StrictEqual; + let result_ty = + types.register_type(Type::Constructor(Constructor::CanonicalRelationOperator { + lhs, + operator, + rhs, + })); + + Ok((result_ty, EqualityAndInequalityResultKind::Condition)) + + // } else { + // match attempt_constant_equality(lhs, rhs, types) { + // Ok(ty) => Ok(( + // if ty { TypeId::TRUE } else { TypeId::FALSE }, + // EqualityAndInequalityResultKind::Constant, + // )), + // Err(()) => { + // unreachable!( + // "should have been caught `is_dependent` above, {:?} === {:?}", + // types.get_type_by_id(lhs), + // types.get_type_by_id(rhs) + // ) + // } + // } + // } + } + EqualityAndInequality::LessThan => { + fn attempt_less_than( + lhs: TypeId, + rhs: TypeId, + types: &mut crate::types::TypeStore, + strict_casts: bool, + ) -> Result<bool, ()> { + // Similar but reversed semantics to add + match (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) { + ( + Type::Constant(Constant::String(string1)), + Type::Constant(Constant::String(string2)), + ) => { + // Yah rust includes string alphanumerical equivalence of strings + Ok(string1 < string2) + } + (Type::Constant(c1), Type::Constant(c2)) => { + let lhs = cast_as_number(c1, strict_casts)?; + let rhs = cast_as_number(c2, strict_casts)?; + Ok(lhs < rhs) + } + (lhs, rhs) => { + crate::utilities::notify!("{:?}", (lhs, rhs)); + // Ok(TypeId::OPEN_BOOLEAN_TYPE) + Err(()) + } + } + } + + let either_is_dependent = types.get_type_by_id(lhs).is_dependent() + || types.get_type_by_id(rhs).is_dependent(); + + if either_is_dependent { + { + if let Type::Constructor(Constructor::BinaryOperator { + lhs: op_lhs, + operator, + rhs: op_rhs, + result: _, + }) = types.get_type_by_id(lhs) + { + if let ( + Type::Constant(Constant::Number(add)), + MathematicalOrBitwiseOperation::Add, + Type::Constant(Constant::Number(lt)), + ) = (types.get_type_by_id(*op_rhs), operator, types.get_type_by_id(rhs)) + { + crate::utilities::notify!("Shifted LT"); + lhs = *op_lhs; + rhs = types.register_type(Type::Constant(Constant::Number(lt - add))); + } + } + } + + { + if !helpers::simple_subtype(lhs, TypeId::NUMBER_TYPE, info, types) + || !helpers::simple_subtype(rhs, TypeId::NUMBER_TYPE, info, types) + { + return Err(()); + } + + let lhs = get_constraint(lhs, types).unwrap_or(lhs); + let rhs = get_constraint(rhs, types).unwrap_or(rhs); + + // Tidies some things for counting loop iterations + + // Checking disjoint-ness for inequalities (TODO under option) via distribution + if let ((Some(lhs_range), _), (Some(rhs_range), _)) = ( + intrinsics::get_range_and_mod_class(lhs, types), + intrinsics::get_range_and_mod_class(rhs, types), + ) { + crate::utilities::notify!("{:?}", (lhs_range, rhs_range)); + if lhs_range.below(rhs_range) { + return Ok((TypeId::TRUE, EqualityAndInequalityResultKind::Constant)); + } + if lhs_range.above(rhs_range) { + return Ok((TypeId::FALSE, EqualityAndInequalityResultKind::Disjoint)); + } + } + + // Transitivity + // TODO extra from & + if let ( + Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::LESS_THAN, + arguments: larg, + }), + Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::GREATER_THAN, + arguments: rarg, + }), + ) = (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) + { + crate::utilities::notify!("{:?} {:?}", larg, rarg); + // Only one level + let transitivity = larg.get_structure_restriction(TypeId::NUMBER_GENERIC) + == rarg.get_structure_restriction(TypeId::NUMBER_GENERIC); + if transitivity { + return Ok((TypeId::TRUE, EqualityAndInequalityResultKind::Constant)); + } + } + } + + let constructor = Constructor::CanonicalRelationOperator { + lhs, + operator: CanonicalEqualityAndInequality::LessThan, + rhs, + }; + Ok(( + types.register_type(crate::Type::Constructor(constructor)), + EqualityAndInequalityResultKind::Condition, + )) + } else { + attempt_less_than(lhs, rhs, types, strict_casts).map(|value| { + ( + if value { TypeId::TRUE } else { TypeId::FALSE }, + EqualityAndInequalityResultKind::Constant, + ) + }) + } + } + // equal OR less than + EqualityAndInequality::LessThanOrEqual => { + let (equality_result, warning) = evaluate_equality_inequality_operation( + lhs, + &EqualityAndInequality::StrictEqual, + rhs, + info, + types, + strict_casts, + )?; + + if equality_result == TypeId::TRUE { + Ok((equality_result, warning)) + } else if equality_result == TypeId::FALSE { + evaluate_equality_inequality_operation( + lhs, + &EqualityAndInequality::LessThan, + rhs, + info, + types, + strict_casts, + ) + } else { + let (less_than_result, warning) = evaluate_equality_inequality_operation( + lhs, + &EqualityAndInequality::LessThan, + rhs, + info, + types, + strict_casts, + )?; + Ok((types.new_logical_or_type(equality_result, less_than_result), warning)) + } + } + EqualityAndInequality::StrictNotEqual => { + let (equality_result, kind) = evaluate_equality_inequality_operation( + lhs, + &EqualityAndInequality::StrictEqual, + rhs, + info, + types, + strict_casts, + )?; + if let EqualityAndInequalityResultKind::Condition = kind { + Ok((types.new_logical_negation_type(equality_result), kind)) + } else { + let negated = if let TypeId::TRUE = equality_result { + TypeId::FALSE + } else if let TypeId::FALSE = equality_result { + TypeId::TRUE + } else { + todo!() + }; + Ok((negated, kind)) + } + } + EqualityAndInequality::Equal => { + crate::utilities::notify!("TODO equal operator"); + Ok((TypeId::OPEN_BOOLEAN_TYPE, EqualityAndInequalityResultKind::Condition)) + } + EqualityAndInequality::NotEqual => { + let (equality_result, kind) = evaluate_equality_inequality_operation( + lhs, + &EqualityAndInequality::Equal, + rhs, + info, + types, + strict_casts, + )?; + if let EqualityAndInequalityResultKind::Condition = kind { + Ok((types.new_logical_negation_type(equality_result), kind)) + } else { + let negated = if let TypeId::TRUE = equality_result { + TypeId::FALSE + } else if let TypeId::FALSE = equality_result { + TypeId::TRUE + } else { + todo!() + }; + Ok((negated, kind)) + } + } + // Swapping operands! + EqualityAndInequality::GreaterThan => evaluate_equality_inequality_operation( + rhs, + &EqualityAndInequality::LessThan, + lhs, + info, + types, + strict_casts, + ), + // Swapping operands! + EqualityAndInequality::GreaterThanOrEqual => evaluate_equality_inequality_operation( + rhs, + &EqualityAndInequality::LessThanOrEqual, + lhs, + info, + types, + strict_casts, + ), + } +} + +#[allow(clippy::let_and_return)] +pub fn is_null_or_undefined( + ty: TypeId, + info: &impl crate::context::InformationChain, + types: &mut crate::types::TypeStore, +) -> TypeId { + let is_null = evaluate_equality_inequality_operation( + ty, + &EqualityAndInequality::StrictEqual, + TypeId::NULL_TYPE, + info, + types, + false, + ) + .map_or(TypeId::ERROR_TYPE, |(left, _)| left); + + if let TypeId::TRUE = is_null { + is_null + } else { + let is_undefined = evaluate_equality_inequality_operation( + ty, + &EqualityAndInequality::StrictEqual, + TypeId::UNDEFINED_TYPE, + info, + types, + false, + ) + .map_or(TypeId::ERROR_TYPE, |(left, _)| left); + + if let TypeId::FALSE = is_null { + is_undefined + } else { + types.new_logical_or_type(is_null, is_undefined) + } + } +} diff --git a/checker/src/features/operations/unary.rs b/checker/src/features/operations/unary.rs new file mode 100644 index 00000000..5c44e324 --- /dev/null +++ b/checker/src/features/operations/unary.rs @@ -0,0 +1,87 @@ +use crate::{ + types::{ + cast_as_number, helpers::simple_subtype, is_type_truthy_falsy, Constant, Type, TypeId, + }, + Decidable, +}; + +/// `typeof` and some others done elsewhere +#[derive(Clone, Copy, Debug, binary_serialize_derive::BinarySerializable)] +pub enum UnaryOperation { + /// Treated as `(value ? false : true)` + LogicalNot, + /// Treated as `0 - value` (could also do -1 * value?) + Negation, + /// Treated as `value ^ 0xFFFF_FFFF` + BitwiseNot, +} + +/// Tries to evaluate unary operation for constant terms. Else delegates to binary operations that handle equivalent thing +pub fn evaluate_unary_operator( + operator: UnaryOperation, + operand: TypeId, + info: &impl crate::context::InformationChain, + types: &mut crate::TypeStore, + strict_casts: bool, +) -> Result<TypeId, ()> { + if operand == TypeId::ERROR_TYPE { + return Ok(operand); + } + + match operator { + UnaryOperation::LogicalNot => { + if let Decidable::Known(value) = is_type_truthy_falsy(operand, types) { + if value { + Ok(TypeId::FALSE) + } else { + Ok(TypeId::TRUE) + } + } else { + let is_boolean = simple_subtype(operand, TypeId::BOOLEAN_TYPE, info, types); + if is_boolean { + Ok(types.new_logical_negation_type(operand)) + } else { + Err(()) + } + } + } + UnaryOperation::Negation | UnaryOperation::BitwiseNot => { + if let Type::Constant(cst) = types.get_type_by_id(operand) { + let value = cast_as_number(cst, strict_casts).expect("hmm"); + let value = match operator { + UnaryOperation::BitwiseNot => f64::from(!(value as i32)), + UnaryOperation::Negation => -value, + UnaryOperation::LogicalNot => unreachable!(), + }; + let value = ordered_float::NotNan::try_from(value); + Ok(match value { + Ok(value) => types.new_constant_type(Constant::Number(value)), + Err(_) => TypeId::NAN, + }) + } else { + match operator { + UnaryOperation::BitwiseNot => super::evaluate_mathematical_operation( + TypeId::MAX_U32, + super::MathematicalOrBitwiseOperation::BitwiseXOr, + operand, + info, + types, + strict_casts, + false, + ), + UnaryOperation::Negation => super::evaluate_mathematical_operation( + TypeId::ZERO, + super::MathematicalOrBitwiseOperation::Subtract, + operand, + info, + types, + strict_casts, + // TODO + true, + ), + UnaryOperation::LogicalNot => unreachable!("handled above"), + } + } + } + } +} diff --git a/checker/src/features/template_literal.rs b/checker/src/features/template_literal.rs index 747877b2..ab68d775 100644 --- a/checker/src/features/template_literal.rs +++ b/checker/src/features/template_literal.rs @@ -144,11 +144,12 @@ where checking_data.types.new_constant_type(Constant::String(static_part.to_owned())); let result = super::operations::evaluate_mathematical_operation( acc, - crate::features::operations::MathematicalAndBitwise::Add, + crate::features::operations::MathematicalOrBitwiseOperation::Add, lhs, environment, &mut checking_data.types, checking_data.options.strict_casts, + checking_data.options.advanced_numbers, ); if let Ok(result) = result { acc = result; @@ -164,11 +165,12 @@ where ); let result = super::operations::evaluate_mathematical_operation( acc, - crate::features::operations::MathematicalAndBitwise::Add, + crate::features::operations::MathematicalOrBitwiseOperation::Add, rhs, environment, &mut checking_data.types, checking_data.options.strict_casts, + checking_data.options.advanced_numbers, ); if let Ok(result) = result { acc = result; @@ -184,11 +186,12 @@ where checking_data.types.new_constant_type(Constant::String(final_part.to_owned())); let result = super::operations::evaluate_mathematical_operation( acc, - crate::features::operations::MathematicalAndBitwise::Add, + crate::features::operations::MathematicalOrBitwiseOperation::Add, value, environment, &mut checking_data.types, checking_data.options.strict_casts, + checking_data.options.advanced_numbers, ); if let Ok(result) = result { result diff --git a/checker/src/options.rs b/checker/src/options.rs index 5fd2919c..19fc0ef5 100644 --- a/checker/src/options.rs +++ b/checker/src/options.rs @@ -54,6 +54,11 @@ pub struct TypeCheckOptions { pub measure_time: bool, + /// Enables two things: + /// - range and modulo class inference + /// - modifications to ranges and classes based on operations + pub advanced_numbers: bool, + /// Printing internal diagnostics in dts pub debug_dts: bool, } @@ -79,6 +84,7 @@ impl Default for TypeCheckOptions { measure_time: false, debug_dts: false, extra_syntax: true, + advanced_numbers: false, } } } diff --git a/checker/src/synthesis/block.rs b/checker/src/synthesis/block.rs index 9f0bf7ea..ee9b8af5 100644 --- a/checker/src/synthesis/block.rs +++ b/checker/src/synthesis/block.rs @@ -42,7 +42,10 @@ pub(super) fn synthesise_block<T: crate::ReadFromFS>( !matches!( e, StatementOrDeclaration::Statement( - Statement::Comment(..) | Statement::MultiLineComment(..) | Statement::Empty(..) + Statement::Comment(..) + | Statement::MultiLineComment(..) + | Statement::Empty(..) + | Statement::AestheticSemiColon(..) ) | StatementOrDeclaration::Declaration(Declaration::Function(..)) ) }) { diff --git a/checker/src/synthesis/classes.rs b/checker/src/synthesis/classes.rs index 294df65e..9479c1bc 100644 --- a/checker/src/synthesis/classes.rs +++ b/checker/src/synthesis/classes.rs @@ -125,7 +125,7 @@ fn synthesise_class_declaration_extends_and_members< let _name = P::as_option_str(&class.name).map_or_else(String::new, str::to_owned); let class_prototype = class_type; - crate::utilities::notify!("At start {:?}", environment.context_type.free_variables); + // crate::utilities::notify!("At start {:?}", environment.context_type.free_variables); let extends = class.extends.as_ref().map(|extends_expression| { let extends = @@ -192,8 +192,6 @@ fn synthesise_class_declaration_extends_and_members< } }; - crate::utilities::notify!("{:?}", (getter_setter, is_async, is_generator)); - let internal_marker = if let (true, ParserPropertyKey::Identifier(name, _, _)) = (is_declare, method.name.get_ast_ref()) { @@ -209,6 +207,25 @@ fn synthesise_class_declaration_extends_and_members< let internal = internal_marker.is_some(); let has_defined_this = method.parameters.leading.0.is_some(); + let this_shape = if internal && !has_defined_this { + TypeId::ANY_TYPE + } else if let Type::Class { type_parameters: Some(type_parameters), .. } = + checking_data.types.get_type_by_id(class_prototype) + { + use source_map::Nullable; + let arguments = crate::types::GenericArguments::ExplicitRestrictions( + type_parameters + .iter() + .map(|id| (*id, (*id, crate::SpanWithSource::NULL))) + .collect(), + ); + checking_data.types.register_type(Type::PartiallyAppliedGenerics( + crate::types::PartiallyAppliedGenerics { on: class_prototype, arguments }, + )) + } else { + class_prototype + }; + let behavior = FunctionRegisterBehavior::ClassMethod { is_async, is_generator, @@ -216,11 +233,7 @@ fn synthesise_class_declaration_extends_and_members< super_type: None, // TODO expecting: TypeId::ANY_TYPE, - this_shape: if internal && !has_defined_this { - TypeId::ANY_TYPE - } else { - class_prototype - }, + this_shape, internal_marker, name: key.into_name_type(&mut checking_data.types), }; @@ -291,7 +304,13 @@ fn synthesise_class_declaration_extends_and_members< ); static_property_keys.push(key); } - ClassMember::Indexer { name, indexer_type, return_type, is_readonly, position } => { + ClassMember::Indexer { + name: _name, + indexer_type, + return_type, + is_readonly, + position, + } => { // TODO this redoes work done at registration. Because the info gets overwritten let key = synthesise_type_annotation(indexer_type, environment, checking_data); let value = synthesise_type_annotation(return_type, environment, checking_data); @@ -306,7 +325,7 @@ fn synthesise_class_declaration_extends_and_members< // TODO check declare // TODO WIP - crate::utilities::notify!("Indexing (again) for '{}'", name); + // crate::utilities::notify!("Indexing (again) for '{}' {:?}", name, value); let value = PropertyValue::Value(value); environment.info.register_property_on_type( class_type, @@ -367,8 +386,6 @@ fn synthesise_class_declaration_extends_and_members< environment.info.variable_current_value.insert(variable, class_variable_type); } - crate::utilities::notify!("At end {:?}", environment.context_type.free_variables); - { // Static items and blocks static_property_keys.reverse(); @@ -699,7 +716,13 @@ fn register_extends_and_member<T: crate::ReadFromFS>( ); } } - ClassMember::Indexer { name, indexer_type, return_type, is_readonly, position } => { + ClassMember::Indexer { + name: _name, + indexer_type, + return_type, + is_readonly, + position, + } => { // TODO think this is okay let key = synthesise_type_annotation(indexer_type, environment, checking_data); let value = synthesise_type_annotation(return_type, environment, checking_data); @@ -714,9 +737,8 @@ fn register_extends_and_member<T: crate::ReadFromFS>( // TODO check declare // TODO WIP - crate::utilities::notify!("Indexing for '{}'", name); + // crate::utilities::notify!("Indexing for '{}' {:?}", name, class_type); let value = PropertyValue::Value(value); - crate::utilities::notify!("{:?}", class_type); environment.info.register_property_on_type( class_type, Publicity::Public, diff --git a/checker/src/synthesis/expressions.rs b/checker/src/synthesis/expressions.rs index 08e95b32..b7ab5cfa 100644 --- a/checker/src/synthesis/expressions.rs +++ b/checker/src/synthesis/expressions.rs @@ -32,9 +32,10 @@ use crate::{ objects::ObjectBuilder, operations::is_null_or_undefined, operations::{ - evaluate_logical_operation_with_expression, - evaluate_pure_binary_operation_handle_errors, evaluate_unary_operator, - EqualityAndInequality, MathematicalAndBitwise, UnaryOperation, + evaluate_equality_inequality_operation, evaluate_logical_operation_with_expression, + evaluate_mathematical_operation, evaluate_unary_operator, EqualityAndInequality, + EqualityAndInequalityResultKind, LogicalOperator, MathematicalOrBitwiseOperation, + UnaryOperation, }, template_literal::synthesise_template_literal_expression, variables::VariableWithValue, @@ -46,7 +47,7 @@ use crate::{ }, types::{ calling::{CallingInput, UnsynthesisedArgument}, - get_larger_type, + helpers::get_larger_type, logical::{Logical, LogicalOrValid}, printing::{print_property_key, print_type}, properties::{ @@ -251,11 +252,9 @@ pub(super) fn synthesise_expression<T: crate::ReadFromFS>( | BinaryOperator::NullCoalescing = operator { let operator = match operator { - BinaryOperator::LogicalAnd => crate::features::operations::LogicalOperator::And, - BinaryOperator::LogicalOr => crate::features::operations::LogicalOperator::Or, - BinaryOperator::NullCoalescing => { - crate::features::operations::LogicalOperator::NullCoalescing - } + BinaryOperator::LogicalAnd => LogicalOperator::And, + BinaryOperator::LogicalOr => LogicalOperator::Or, + BinaryOperator::NullCoalescing => LogicalOperator::NullCoalescing, _ => unreachable!(), }; return evaluate_logical_operation_with_expression( @@ -267,49 +266,120 @@ pub(super) fn synthesise_expression<T: crate::ReadFromFS>( expecting, // TODO unwrap ) .unwrap(); - } + } else if let BinaryOperator::StrictEqual + | BinaryOperator::StrictNotEqual + | BinaryOperator::Equal + | BinaryOperator::NotEqual + | BinaryOperator::GreaterThan + | BinaryOperator::LessThan + | BinaryOperator::LessThanEqual + | BinaryOperator::GreaterThanEqual = operator + { + let rhs_ty = + synthesise_expression(rhs, environment, checking_data, TypeId::ANY_TYPE); + let operator = match operator { + BinaryOperator::StrictEqual => EqualityAndInequality::StrictEqual, + BinaryOperator::StrictNotEqual => EqualityAndInequality::StrictNotEqual, + BinaryOperator::Equal => EqualityAndInequality::Equal, + BinaryOperator::NotEqual => EqualityAndInequality::NotEqual, + BinaryOperator::GreaterThan => EqualityAndInequality::GreaterThan, + BinaryOperator::LessThan => EqualityAndInequality::LessThan, + BinaryOperator::LessThanEqual => EqualityAndInequality::LessThanOrEqual, + BinaryOperator::GreaterThanEqual => EqualityAndInequality::GreaterThanOrEqual, + _ => unreachable!(), + }; + let result = evaluate_equality_inequality_operation( + lhs_ty, + &operator, + rhs_ty, + environment, + &mut checking_data.types, + checking_data.options.strict_casts, + ); - let rhs_ty = synthesise_expression(rhs, environment, checking_data, TypeId::ANY_TYPE); + if let Ok((result, warning)) = result { + if let EqualityAndInequalityResultKind::Disjoint = warning { + let lhs_pos = + ASTNode::get_position(&**lhs).with_source(environment.get_source()); + let rhs_pos = + ASTNode::get_position(&**rhs).with_source(environment.get_source()); + let position = lhs_pos + .without_source() + .union(rhs_pos.without_source()) + .with_source(environment.get_source()); + + checking_data.diagnostics_container.add_warning( + crate::TypeCheckWarning::DisjointEquality { + lhs: TypeStringRepresentation::from_type_id( + lhs_ty, + environment, + &checking_data.types, + false, + ), + rhs: TypeStringRepresentation::from_type_id( + rhs_ty, + environment, + &checking_data.types, + false, + ), + result: result == TypeId::TRUE, + position, + }, + ); + } + + return result; + } + + let lhs_pos = ASTNode::get_position(&**lhs).with_source(environment.get_source()); + let rhs_pos = ASTNode::get_position(&**rhs).with_source(environment.get_source()); + let position = lhs_pos + .without_source() + .union(rhs_pos.without_source()) + .with_source(environment.get_source()); + + checking_data.diagnostics_container.add_error( + crate::TypeCheckError::InvalidEqualityOperation { + operator, + lhs: TypeStringRepresentation::from_type_id( + lhs_ty, + environment, + &checking_data.types, + false, + ), + rhs: TypeStringRepresentation::from_type_id( + rhs_ty, + environment, + &checking_data.types, + false, + ), + position, + }, + ); - if lhs_ty == TypeId::ERROR_TYPE || rhs_ty == TypeId::ERROR_TYPE { return TypeId::ERROR_TYPE; } - let lhs_pos = ASTNode::get_position(&**lhs).with_source(environment.get_source()); - let rhs_pos = ASTNode::get_position(&**rhs).with_source(environment.get_source()); - + let rhs_ty = synthesise_expression(rhs, environment, checking_data, TypeId::ANY_TYPE); let operator = match operator { - BinaryOperator::Add => MathematicalAndBitwise::Add.into(), - BinaryOperator::Subtract => MathematicalAndBitwise::Subtract.into(), - BinaryOperator::Multiply => MathematicalAndBitwise::Multiply.into(), - BinaryOperator::Divide => MathematicalAndBitwise::Divide.into(), - BinaryOperator::Modulo => MathematicalAndBitwise::Modulo.into(), - BinaryOperator::Exponent => MathematicalAndBitwise::Exponent.into(), - BinaryOperator::BitwiseShiftLeft => MathematicalAndBitwise::BitwiseShiftLeft.into(), + BinaryOperator::Add => MathematicalOrBitwiseOperation::Add, + BinaryOperator::Subtract => MathematicalOrBitwiseOperation::Subtract, + BinaryOperator::Multiply => MathematicalOrBitwiseOperation::Multiply, + BinaryOperator::Divide => MathematicalOrBitwiseOperation::Divide, + BinaryOperator::Modulo => MathematicalOrBitwiseOperation::Modulo, + BinaryOperator::Exponent => MathematicalOrBitwiseOperation::Exponent, + BinaryOperator::BitwiseShiftLeft => { + MathematicalOrBitwiseOperation::BitwiseShiftLeft + } BinaryOperator::BitwiseShiftRight => { - MathematicalAndBitwise::BitwiseShiftRight.into() + MathematicalOrBitwiseOperation::BitwiseShiftRight } BinaryOperator::BitwiseShiftRightUnsigned => { - MathematicalAndBitwise::BitwiseShiftRightUnsigned.into() - } - BinaryOperator::BitwiseAnd => MathematicalAndBitwise::BitwiseAnd.into(), - BinaryOperator::BitwiseXOr => MathematicalAndBitwise::BitwiseXOr.into(), - BinaryOperator::BitwiseOr => MathematicalAndBitwise::BitwiseOr.into(), - BinaryOperator::StrictEqual => EqualityAndInequality::StrictEqual.into(), - BinaryOperator::StrictNotEqual => EqualityAndInequality::StrictNotEqual.into(), - BinaryOperator::Equal => EqualityAndInequality::Equal.into(), - BinaryOperator::NotEqual => EqualityAndInequality::NotEqual.into(), - BinaryOperator::GreaterThan => EqualityAndInequality::GreaterThan.into(), - BinaryOperator::LessThan => EqualityAndInequality::LessThan.into(), - BinaryOperator::LessThanEqual => EqualityAndInequality::LessThanOrEqual.into(), - BinaryOperator::GreaterThanEqual => { - EqualityAndInequality::GreaterThanOrEqual.into() - } - BinaryOperator::LogicalAnd - | BinaryOperator::LogicalOr - | BinaryOperator::NullCoalescing => { - unreachable!() + MathematicalOrBitwiseOperation::BitwiseShiftRightUnsigned } + BinaryOperator::BitwiseAnd => MathematicalOrBitwiseOperation::BitwiseAnd, + BinaryOperator::BitwiseXOr => MathematicalOrBitwiseOperation::BitwiseXOr, + BinaryOperator::BitwiseOr => MathematicalOrBitwiseOperation::BitwiseOr, BinaryOperator::Pipe | BinaryOperator::Compose => { checking_data.raise_unimplemented_error( "special operations", @@ -317,14 +387,49 @@ pub(super) fn synthesise_expression<T: crate::ReadFromFS>( ); return TypeId::UNIMPLEMENTED_ERROR_TYPE; } + _ => { + unreachable!() + } }; - Instance::RValue(evaluate_pure_binary_operation_handle_errors( - (lhs_ty, lhs_pos), + let result = evaluate_mathematical_operation( + lhs_ty, operator, - (rhs_ty, rhs_pos), - checking_data, + rhs_ty, environment, - )) + &mut checking_data.types, + checking_data.options.strict_casts, + checking_data.options.advanced_numbers, + ); + if let Ok(value) = result { + Instance::RValue(value) + } else { + let lhs_pos = ASTNode::get_position(&**lhs).with_source(environment.get_source()); + let rhs_pos = ASTNode::get_position(&**rhs).with_source(environment.get_source()); + let position = lhs_pos + .without_source() + .union(rhs_pos.without_source()) + .with_source(environment.get_source()); + + checking_data.diagnostics_container.add_error( + TypeCheckError::InvalidMathematicalOrBitwiseOperation { + operator, + lhs: TypeStringRepresentation::from_type_id( + lhs_ty, + environment, + &checking_data.types, + false, + ), + rhs: TypeStringRepresentation::from_type_id( + rhs_ty, + environment, + &checking_data.types, + false, + ), + position, + }, + ); + return TypeId::ERROR_TYPE; + } } Expression::UnaryOperation { operand, operator, position } => { match operator { @@ -1060,37 +1165,37 @@ fn operator_to_assignment_kind( ), BinaryAssignmentOperator::AddAssign | BinaryAssignmentOperator::BitwiseShiftRightUnsigned => { - AssignmentKind::PureUpdate(MathematicalAndBitwise::Add) + AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::Add) } BinaryAssignmentOperator::SubtractAssign => { - AssignmentKind::PureUpdate(MathematicalAndBitwise::Subtract) + AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::Subtract) } BinaryAssignmentOperator::MultiplyAssign => { - AssignmentKind::PureUpdate(MathematicalAndBitwise::Multiply) + AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::Multiply) } BinaryAssignmentOperator::DivideAssign => { - AssignmentKind::PureUpdate(MathematicalAndBitwise::Divide) + AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::Divide) } BinaryAssignmentOperator::ModuloAssign => { - AssignmentKind::PureUpdate(MathematicalAndBitwise::Modulo) + AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::Modulo) } BinaryAssignmentOperator::ExponentAssign => { - AssignmentKind::PureUpdate(MathematicalAndBitwise::Exponent) + AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::Exponent) } BinaryAssignmentOperator::BitwiseShiftLeftAssign => { - AssignmentKind::PureUpdate(MathematicalAndBitwise::BitwiseShiftLeft) + AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::BitwiseShiftLeft) } BinaryAssignmentOperator::BitwiseShiftRightAssign => { - AssignmentKind::PureUpdate(MathematicalAndBitwise::BitwiseShiftRight) + AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::BitwiseShiftRight) } BinaryAssignmentOperator::BitwiseAndAssign => { - AssignmentKind::PureUpdate(MathematicalAndBitwise::BitwiseAnd) + AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::BitwiseAnd) } BinaryAssignmentOperator::BitwiseXOrAssign => { - AssignmentKind::PureUpdate(MathematicalAndBitwise::BitwiseXOr) + AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::BitwiseXOr) } BinaryAssignmentOperator::BitwiseOrAssign => { - AssignmentKind::PureUpdate(MathematicalAndBitwise::BitwiseOr) + AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::BitwiseOr) } } } @@ -1234,6 +1339,7 @@ pub(super) fn synthesise_object_literal<T: crate::ReadFromFS>( print_type(*truthy_result, &checking_data.types, environment, true), print_type(*otherwise_result, &checking_data.types, environment, true) ); + // Concatenate some types here if they all have the same keys // && matches!( // (lv, rv), diff --git a/checker/src/synthesis/functions.rs b/checker/src/synthesis/functions.rs index 9f628135..ab5dbf65 100644 --- a/checker/src/synthesis/functions.rs +++ b/checker/src/synthesis/functions.rs @@ -856,7 +856,7 @@ pub(super) fn build_overloaded_function( let func = types.new_hoisted_function_type(as_function); // IMPORTANT THAT RESULT IS ON THE RIGHT OF AND TYPE - result = types.new_and_type(func, result).unwrap(); + result = types.new_and_type(func, result); } result diff --git a/checker/src/synthesis/hoisting.rs b/checker/src/synthesis/hoisting.rs index 4f9a2ea3..0781f2bf 100644 --- a/checker/src/synthesis/hoisting.rs +++ b/checker/src/synthesis/hoisting.rs @@ -501,8 +501,7 @@ pub(crate) fn hoist_statements<T: crate::ReadFromFS>( &mut sub_environment, checking_data, ); - extends = - checking_data.types.new_and_type(extends, new).unwrap(); + extends = checking_data.types.new_and_type(extends, new); } checking_data.types.set_extends_on_interface(ty, extends); } @@ -539,8 +538,7 @@ pub(crate) fn hoist_statements<T: crate::ReadFromFS>( environment, checking_data, ); - extends = - checking_data.types.new_and_type(extends, new).unwrap(); + extends = checking_data.types.new_and_type(extends, new); } checking_data.types.set_extends_on_interface(ty, extends); } diff --git a/checker/src/synthesis/interfaces.rs b/checker/src/synthesis/interfaces.rs index fed3dc65..edd068e5 100644 --- a/checker/src/synthesis/interfaces.rs +++ b/checker/src/synthesis/interfaces.rs @@ -448,7 +448,7 @@ pub(super) fn synthesise_signatures<T: crate::ReadFromFS, B: SynthesiseInterface for ta in iter { let ty = synthesise_type_annotation(ta, environment, checking_data); - extends = checking_data.types.new_and_type(extends, ty).unwrap(); + extends = checking_data.types.new_and_type(extends, ty); } checking_data.types.set_extends_on_interface(interface_type, extends); diff --git a/checker/src/synthesis/mod.rs b/checker/src/synthesis/mod.rs index f0c13d2f..e9a2ecf3 100644 --- a/checker/src/synthesis/mod.rs +++ b/checker/src/synthesis/mod.rs @@ -164,6 +164,8 @@ impl crate::ASTImplementation for EznoParser { }, type_annotations: !is_js, partial_syntax: lsp_mode, + // TODO + retain_blank_lines: lsp_mode, is_expressions: extra_syntax, ..Default::default() } diff --git a/checker/src/synthesis/statements.rs b/checker/src/synthesis/statements.rs index cd291cd1..227555b7 100644 --- a/checker/src/synthesis/statements.rs +++ b/checker/src/synthesis/statements.rs @@ -183,6 +183,10 @@ pub(super) fn synthesise_statement<T: crate::ReadFromFS>( environment, checking_data, |environment, checking_data| { + crate::utilities::notify!( + "environment.info.narrowed_values={:?}", + environment.info.narrowed_values + ); synthesise_block_or_single_statement(&stmt.inner, environment, checking_data); }, position.with_source(environment.get_source()), diff --git a/checker/src/synthesis/type_annotations.rs b/checker/src/synthesis/type_annotations.rs index bcbb40c8..304b3333 100644 --- a/checker/src/synthesis/type_annotations.rs +++ b/checker/src/synthesis/type_annotations.rs @@ -11,13 +11,11 @@ use crate::{ features::objects::ObjectBuilder, types::{ generics::generic_type_arguments::GenericArguments, - properties::{PropertyKey, PropertyValue, Publicity}, - Constant, Constructor, PartiallyAppliedGenerics, PolyNature, Type, TypeId, - }, - types::{ generics::ExplicitTypeArguments, + helpers::{ArrayItem, Counter}, intrinsics::{self, distribute_tsc_string_intrinsic}, - ArrayItem, Counter, + properties::{PropertyKey, PropertyValue, Publicity}, + Constant, Constructor, PartiallyAppliedGenerics, PolyNature, Type, TypeId, }, CheckingData, Map, }; @@ -248,10 +246,10 @@ pub fn synthesise_type_annotation<T: crate::ReadFromFS>( // crate::utilities::notify!( // "{:?} and {:?}", // inner_type_alias_id, - // inner_type_alias_id.is_some_and(intrinsics::tsc_string_intrinsic) + // inner_type_alias_id.is_some_and(intrinsics::is_tsc_string_intrinsic) // ); - if intrinsics::tsc_string_intrinsic(inner_type_id) { + if intrinsics::is_tsc_string_intrinsic(inner_type_id) { distribute_tsc_string_intrinsic( inner_type_id, type_arguments.get(&TypeId::STRING_GENERIC).unwrap().0, @@ -354,10 +352,15 @@ pub fn synthesise_type_annotation<T: crate::ReadFromFS>( let mut acc = iterator.next().expect("Empty intersection"); for right in iterator { - if let Ok(new_ty) = checking_data.types.new_and_type(acc, right) { - acc = new_ty; - } else { - checking_data.diagnostics_container.add_error( + let is_disjoint = crate::types::disjoint::types_are_disjoint( + acc, + right, + &mut Vec::new(), + environment, + &checking_data.types, + ); + if is_disjoint { + checking_data.diagnostics_container.add_warning( TypeCheckWarning::TypesDoNotIntersect { left: TypeStringRepresentation::from_type_id( acc, @@ -374,8 +377,10 @@ pub fn synthesise_type_annotation<T: crate::ReadFromFS>( position: position.with_source(environment.get_source()), }, ); - return TypeId::ERROR_TYPE; + return TypeId::NEVER_TYPE; } + + acc = checking_data.types.new_and_type(acc, right); } acc } @@ -483,7 +488,7 @@ pub fn synthesise_type_annotation<T: crate::ReadFromFS>( items.push((pos, ArrayItem::Optional(annotation_ty))); } TupleElementKind::Spread => { - let slice = crate::types::as_slice( + let slice = crate::types::helpers::as_slice( annotation_ty, &checking_data.types, environment, @@ -514,15 +519,15 @@ pub fn synthesise_type_annotation<T: crate::ReadFromFS>( ); for (ty_position, item) in items { + use crate::types::helpers::ArrayItem; + let value = match item { - crate::types::ArrayItem::Member(item_ty) => PropertyValue::Value(item_ty), - crate::types::ArrayItem::Optional(item_ty) => { - PropertyValue::ConditionallyExists { - condition: TypeId::OPEN_BOOLEAN_TYPE, - truthy: Box::new(PropertyValue::Value(item_ty)), - } - } - crate::types::ArrayItem::Wildcard(on) => { + ArrayItem::Member(item_ty) => PropertyValue::Value(item_ty), + ArrayItem::Optional(item_ty) => PropertyValue::ConditionallyExists { + condition: TypeId::OPEN_BOOLEAN_TYPE, + truthy: Box::new(PropertyValue::Value(item_ty)), + }, + ArrayItem::Wildcard(on) => { crate::utilities::notify!("found wildcard"); let after = idx.into_type(&mut checking_data.types); @@ -716,7 +721,8 @@ pub fn synthesise_type_annotation<T: crate::ReadFromFS>( checking_data.types.register_type(Type::Constructor( crate::types::Constructor::BinaryOperator { lhs: acc, - operator: crate::features::operations::MathematicalAndBitwise::Add, + operator: + crate::features::operations::MathematicalOrBitwiseOperation::Add, rhs: lhs, result: TypeId::STRING_TYPE, }, @@ -733,7 +739,7 @@ pub fn synthesise_type_annotation<T: crate::ReadFromFS>( }; let constructor = crate::types::Constructor::BinaryOperator { lhs: acc, - operator: crate::features::operations::MathematicalAndBitwise::Add, + operator: crate::features::operations::MathematicalOrBitwiseOperation::Add, rhs, result: TypeId::STRING_TYPE, }; @@ -749,7 +755,8 @@ pub fn synthesise_type_annotation<T: crate::ReadFromFS>( checking_data.types.register_type(Type::Constructor( crate::types::Constructor::BinaryOperator { lhs: acc, - operator: crate::features::operations::MathematicalAndBitwise::Add, + operator: + crate::features::operations::MathematicalOrBitwiseOperation::Add, rhs: lhs, result: TypeId::STRING_TYPE, }, diff --git a/checker/src/synthesis/variables.rs b/checker/src/synthesis/variables.rs index 236d890f..56286456 100644 --- a/checker/src/synthesis/variables.rs +++ b/checker/src/synthesis/variables.rs @@ -15,7 +15,8 @@ use crate::{ }, synthesis::parser_property_key_to_checker_property_key, types::{ - get_larger_type, printing, + helpers::get_larger_type, + printing, properties::{ get_properties_on_single_type, get_property_key_names_on_a_single_type, PropertyKey, Publicity, diff --git a/checker/src/types/calling.rs b/checker/src/types/calling.rs index 63f46696..9ea6c083 100644 --- a/checker/src/types/calling.rs +++ b/checker/src/types/calling.rs @@ -22,7 +22,7 @@ use crate::{ types::{ functions::{FunctionBehavior, FunctionEffect, FunctionType}, generics::substitution::SubstitutionArguments, - get_structure_arguments_based_on_object_constraint, + helpers::get_structure_arguments_based_on_object_constraint, logical::{Invalid, Logical, LogicalOrValid, NeedsCalculation, PossibleLogical}, properties::AccessMode, substitute, GenericChainLink, ObjectNature, PartiallyAppliedGenerics, Type, @@ -474,7 +474,8 @@ fn get_logical_callable_from_type( // } if ty == TypeId::ANY_TYPE { crate::utilities::notify!("Calling ANY"); - return Ok(NeedsCalculation::Infer { on: from.unwrap() }.into()); + // TODO temp + return Ok(NeedsCalculation::Infer { on: from.unwrap_or(TypeId::ERROR_TYPE) }.into()); } let le_ty = types.get_type_by_id(ty); @@ -613,7 +614,10 @@ fn call_logical<B: CallCheckingBehavior>( || is_independent_function || matches!( const_fn_ident.as_str(), - "satisfies" | "is_dependent" | "bind" | "proxy:constructor" + "satisfies" + | "is_dependent" | "bind" + | "proxy:constructor" | "setPrototypeOf" + | "getPrototypeOf" ); // { @@ -862,6 +866,13 @@ fn call_logical<B: CallCheckingBehavior>( call_site: input.call_site, possibly_thrown, }); + } else { + // This fixes tree shaking of functions that are called within callbacks + // TODO this could be done under an option + let value = Event::Miscellaneous( + crate::events::MiscellaneousEvents::MarkFunctionAsCalled(function.function), + ); + behavior.get_latest_info(top_environment).events.push(value); } Ok(result) diff --git a/checker/src/types/disjoint.rs b/checker/src/types/disjoint.rs index a49c5f59..9e088ea6 100644 --- a/checker/src/types/disjoint.rs +++ b/checker/src/types/disjoint.rs @@ -1,4 +1,7 @@ -use super::{Constant, PartiallyAppliedGenerics, Type, TypeId, TypeStore}; +use super::{ + helpers, Constant, Constructor, MathematicalOrBitwiseOperation, PartiallyAppliedGenerics, Type, + TypeId, TypeStore, +}; use crate::context::InformationChain; /// For equality + [`crate::intrinsics::Intrinsics::Not`] @@ -30,15 +33,9 @@ pub fn types_are_disjoint( if let Type::Or(lhs_lhs, lhs_rhs) = lhs_ty { types_are_disjoint(*lhs_lhs, rhs, already_checked, information, types) && types_are_disjoint(*lhs_rhs, rhs, already_checked, information, types) - } else if let Type::And(lhs_lhs, lhs_rhs) = lhs_ty { - types_are_disjoint(*lhs_lhs, rhs, already_checked, information, types) - || types_are_disjoint(*lhs_rhs, rhs, already_checked, information, types) } else if let Type::Or(rhs_lhs, rhs_rhs) = rhs_ty { types_are_disjoint(lhs, *rhs_lhs, already_checked, information, types) && types_are_disjoint(lhs, *rhs_rhs, already_checked, information, types) - } else if let Type::And(rhs_lhs, rhs_rhs) = rhs_ty { - types_are_disjoint(lhs, *rhs_lhs, already_checked, information, types) - || types_are_disjoint(lhs, *rhs_rhs, already_checked, information, types) } else if let Type::AliasTo { to, parameters: None, name: _ } = lhs_ty { // TODO temp fix, need infer ANY if matches!(*to, TypeId::ANY_TYPE) { @@ -74,7 +71,7 @@ pub fn types_are_disjoint( }) = lhs_ty { use super::subtyping; - let inner = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap(); + let lhs_inner = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap(); let mut state = subtyping::State { // TODO already_checked: already_checked.clone(), @@ -84,16 +81,16 @@ pub fn types_are_disjoint( object_constraints: None, }; - crate::utilities::notify!("{:?}", (lhs, inner)); + // crate::utilities::notify!("{:?}", (lhs, lhs_inner)); - subtyping::type_is_subtype(rhs, inner, &mut state, information, types).is_subtype() + subtyping::type_is_subtype(lhs_inner, rhs, &mut state, information, types).is_subtype() } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on: TypeId::NOT_RESTRICTION, arguments, }) = rhs_ty { use super::subtyping; - let inner = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap(); + let rhs_inner = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap(); let mut state = subtyping::State { // TODO already_checked: already_checked.clone(), @@ -103,75 +100,99 @@ pub fn types_are_disjoint( object_constraints: None, }; - crate::utilities::notify!("{:?}", (lhs, inner)); + crate::utilities::notify!("{:?}", (rhs, rhs_inner)); - subtyping::type_is_subtype(lhs, inner, &mut state, information, types).is_subtype() - } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE, - arguments: _, - }) = lhs_ty + subtyping::type_is_subtype(rhs_inner, lhs, &mut state, information, types).is_subtype() + } else if let Type::And(rhs_lhs, rhs_rhs) = rhs_ty { + types_are_disjoint(lhs, *rhs_lhs, already_checked, information, types) + || types_are_disjoint(lhs, *rhs_rhs, already_checked, information, types) + } else if let Type::And(lhs_lhs, lhs_rhs) = lhs_ty { + types_are_disjoint(*lhs_lhs, rhs, already_checked, information, types) + || types_are_disjoint(*lhs_rhs, rhs, already_checked, information, types) + } else if let Type::PartiallyAppliedGenerics( + args @ PartiallyAppliedGenerics { on: TypeId::MULTIPLE_OF, arguments: _ }, + ) = lhs_ty { - let range = super::intrinsics::get_range(lhs, types).unwrap(); - if let Some(rhs_range) = super::intrinsics::get_range(rhs, types) { - let overlap = range.overlaps(rhs_range); - crate::utilities::notify!("{:?}", overlap); - !overlap - } else { - crate::utilities::notify!("Here"); - true - } - } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE, - arguments: _, - }) = rhs_ty + // TODO also offset + number_modulo_disjoint(args, rhs, types) + } else if let Type::PartiallyAppliedGenerics( + args @ PartiallyAppliedGenerics { on: TypeId::MULTIPLE_OF, arguments: _ }, + ) = rhs_ty { - let range = super::intrinsics::get_range(rhs, types).unwrap(); - if let Some(lhs_range) = super::intrinsics::get_range(lhs, types) { - let overlap = range.overlaps(lhs_range); - crate::utilities::notify!("{:?}", overlap); - !overlap - } else { - crate::utilities::notify!("Here"); - true - } - } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: TypeId::MULTIPLE_OF, - arguments, + // TODO also offset + number_modulo_disjoint(args, lhs, types) + } else if let Type::PartiallyAppliedGenerics( + args @ PartiallyAppliedGenerics { + on: TypeId::GREATER_THAN | TypeId::LESS_THAN, + arguments: _, + }, + ) = rhs_ty + { + number_range_disjoint(args, lhs, types) + } else if let Type::PartiallyAppliedGenerics( + args @ PartiallyAppliedGenerics { + on: TypeId::GREATER_THAN | TypeId::LESS_THAN, + arguments: _, + }, + ) = lhs_ty + { + number_range_disjoint(args, rhs, types) + } else if let Type::Constructor(Constructor::BinaryOperator { + lhs: _lhs, + operator: MathematicalOrBitwiseOperation::Modulo, + rhs, + result: _, }) = lhs_ty { - // Little bit complex here because dealing with decimal types, not integers - if let (Type::Constant(Constant::Number(lhs)), Type::Constant(Constant::Number(rhs))) = ( - types.get_type_by_id( - arguments.get_structure_restriction(TypeId::NUMBER_FLOOR_GENERIC).unwrap(), - ), - types.get_type_by_id(rhs), - ) { - let result = rhs % lhs != 0.; - crate::utilities::notify!("{:?} {:?}", rhs, lhs); - result + if let ( + Type::Constant(Constant::Number(lhs_mod)), + Type::Constant(Constant::Number(num)), + ) = (types.get_type_by_id(*rhs), rhs_ty) + { + crate::utilities::notify!("{:?}", (num, lhs_mod)); + // Modulos return negative for negative number :( + // Checking whether out of range here + num.abs() > **lhs_mod } else { - crate::utilities::notify!("Here"); false } - } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: TypeId::MULTIPLE_OF, - arguments, + } else if let Type::Constructor(Constructor::BinaryOperator { + lhs: _lhs, + operator: MathematicalOrBitwiseOperation::Modulo, + rhs, + result: _, }) = rhs_ty { - // Little bit complex here because dealing with decimal types, not integers - if let (Type::Constant(Constant::Number(lhs)), Type::Constant(Constant::Number(rhs))) = ( - types.get_type_by_id(lhs), - types.get_type_by_id( - arguments.get_structure_restriction(TypeId::NUMBER_FLOOR_GENERIC).unwrap(), - ), - ) { - let result = lhs % rhs != 0.; - crate::utilities::notify!("{:?} {:?}", lhs, rhs); - result + if let ( + Type::Constant(Constant::Number(num)), + Type::Constant(Constant::Number(rhs_mod)), + ) = (types.get_type_by_id(*rhs), lhs_ty) + { + crate::utilities::notify!("{:?}", (num, rhs_mod)); + // Modulos return negative for negative number :( + // Checking whether out of range here + num.abs() > **rhs_mod } else { - crate::utilities::notify!("Here"); false } + } else if let Type::Constructor(Constructor::BinaryOperator { + operator: MathematicalOrBitwiseOperation::Add, + result: TypeId::STRING_TYPE, + .. + }) = rhs_ty + { + let lhs = helpers::TemplatelLiteralExpansion::from_type(lhs, types); + let rhs = helpers::TemplatelLiteralExpansion::from_type(rhs, types); + lhs.is_disjoint(&rhs) + } else if let Type::Constructor(Constructor::BinaryOperator { + operator: MathematicalOrBitwiseOperation::Add, + result: TypeId::STRING_TYPE, + .. + }) = lhs_ty + { + let lhs = helpers::TemplatelLiteralExpansion::from_type(lhs, types); + let rhs = helpers::TemplatelLiteralExpansion::from_type(rhs, types); + lhs.is_disjoint(&rhs) } else if let Some(lhs) = super::get_constraint(lhs, types) { // TODO not sure whether these should be here? types_are_disjoint(lhs, rhs, already_checked, information, types) @@ -192,6 +213,18 @@ pub fn types_are_disjoint( } } else if let Type::Constant(rhs_cst) = rhs_ty { types_are_disjoint(rhs_cst.get_backing_type(), lhs, already_checked, information, types) + } else if let Type::Object(crate::types::ObjectNature::AnonymousTypeAnnotation( + _properties, + )) = lhs_ty + { + // TODO check properties + false + } else if let Type::Object(crate::types::ObjectNature::AnonymousTypeAnnotation( + _properties, + )) = rhs_ty + { + // TODO check properties + false } else { crate::utilities::notify!( "{:?} cap {:?} == empty ? cases. Might be missing, calling disjoint", @@ -202,3 +235,100 @@ pub fn types_are_disjoint( } } } + +fn number_modulo_disjoint( + this: &PartiallyAppliedGenerics, + other: TypeId, + types: &TypeStore, +) -> bool { + crate::utilities::notify!("Here number_modulo_disjoint"); + let PartiallyAppliedGenerics { arguments, .. } = this; + let other_ty = types.get_type_by_id(other); + let argument = + types.get_type_by_id(arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap()); + + let Type::Constant(Constant::Number(argument)) = argument else { + crate::utilities::notify!("Gets complex here"); + return false; + }; + + let offset = 0f64.try_into().unwrap(); + let this = crate::utilities::modulo_class::ModuloClass::new(*argument, offset); + + // Little bit complex here because dealing with decimal types, not integers + if let Type::Constant(Constant::Number(other)) = other_ty { + !this.contains(*other) + } else { + let (range, modulo_class) = super::intrinsics::get_range_and_mod_class(other, types); + crate::utilities::notify!("Disjoint with modulo, {:?}, {:?}", range, modulo_class); + if let Some(range) = range { + return !range.contains_multiple_of(*argument); + } + if let Some(modulo_class) = modulo_class { + return this.disjoint(modulo_class); + } + crate::utilities::notify!("Here {:?}", other_ty); + false + } +} + +fn number_range_disjoint( + this: &PartiallyAppliedGenerics, + other: TypeId, + types: &TypeStore, +) -> bool { + let PartiallyAppliedGenerics { on, arguments, .. } = this; + let greater_than = *on == TypeId::GREATER_THAN; + let this_ty = + types.get_type_by_id(arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap()); + + if let Type::Constant(Constant::Number(this)) = this_ty { + let other_ty = types.get_type_by_id(other); + if let Type::Constant(Constant::Number(other)) = other_ty { + crate::utilities::notify!("{:?} {} {}", on, other, this); + if greater_than { + other < this + } else { + other > this + } + } else { + crate::utilities::notify!("Unsure here {:?}", (other_ty, this_ty)); + false + } + } else { + crate::utilities::notify!("Unsure here"); + false + } +} + +// fn todo() { +// // else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { +// // on: TypeId::GREATER_THAN +// // arguments: _, +// // }) = lhs_ty +// // { +// // let range = super::intrinsics::get_range(lhs, types).unwrap(); +// // if let Some(rhs_range) = super::intrinsics::get_range(rhs, types) { +// // let overlap = range.overlaps(rhs_range); +// // crate::utilities::notify!("{:?}", overlap); +// // !overlap +// // } else { +// // crate::utilities::notify!("Here"); +// // true +// // } +// // } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { +// // on: TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE, +// // arguments: _, +// // }) = rhs_ty +// // { +// // let range = super::intrinsics::get_range(rhs, types).unwrap(); +// // if let Some(lhs_range) = super::intrinsics::get_range(lhs, types) { +// // let overlap = range.overlaps(lhs_range); +// // crate::utilities::notify!("{:?}", overlap); +// // !overlap +// // } else { +// // crate::utilities::notify!("Here"); +// // true +// // } +// // } +// } diff --git a/checker/src/types/generics/substitution.rs b/checker/src/types/generics/substitution.rs index dacfe81a..1a6c6900 100644 --- a/checker/src/types/generics/substitution.rs +++ b/checker/src/types/generics/substitution.rs @@ -146,7 +146,7 @@ pub(crate) fn substitute( let generic_arguments = structure_arguments.clone(); // Fold intrinsic type - if intrinsics::tsc_string_intrinsic(on) { + if intrinsics::is_tsc_string_intrinsic(on) { let arg = structure_arguments.get_structure_restriction(TypeId::STRING_GENERIC).unwrap(); let value = substitute(arg, arguments, environment, types); @@ -195,7 +195,7 @@ pub(crate) fn substitute( let rhs = *rhs; let lhs = substitute(*lhs, arguments, environment, types); let rhs = substitute(rhs, arguments, environment, types); - types.new_and_type(lhs, rhs).unwrap_or(TypeId::NEVER_TYPE) + types.new_and_type(lhs, rhs) // .unwrap_or(TypeId::NEVER_TYPE) } Type::Or(lhs, rhs) => { let rhs = *rhs; @@ -237,8 +237,18 @@ pub(crate) fn substitute( let lhs = substitute(lhs, arguments, environment, types); let rhs = substitute(rhs, arguments, environment, types); - match evaluate_mathematical_operation(lhs, operator, rhs, environment, types, false) - { + // TODO + let advanced_numbers = false; + + match evaluate_mathematical_operation( + lhs, + operator, + rhs, + environment, + types, + false, + advanced_numbers, + ) { Ok(result) => result, Err(()) => { unreachable!( diff --git a/checker/src/types/helpers.rs b/checker/src/types/helpers.rs new file mode 100644 index 00000000..f8b6ab18 --- /dev/null +++ b/checker/src/types/helpers.rs @@ -0,0 +1,471 @@ +use super::{ + get_constraint, properties, subtyping, Constant, Constructor, GenericArguments, GenericChain, + InformationChain, MathematicalOrBitwiseOperation, PartiallyAppliedGenerics, PolyNature, Type, + TypeId, TypeStore, +}; + +pub(crate) fn get_structure_arguments_based_on_object_constraint<'a, C: InformationChain>( + object: TypeId, + info_chain: &C, + types: &'a TypeStore, +) -> Option<&'a GenericArguments> { + if let Some(object_constraint) = + info_chain.get_chain_of_info().find_map(|c| c.object_constraints.get(&object).copied()) + { + let ty = types.get_type_by_id(object_constraint); + if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { arguments, .. }) = ty { + Some(arguments) + } else { + crate::utilities::notify!("Generics might be missed here {:?}", ty); + None + } + } else { + None + } +} + +pub(crate) fn tuple_like(id: TypeId, types: &TypeStore, environment: &crate::Environment) -> bool { + // TODO should be `ObjectNature::AnonymousObjectType` or something else + let ty = types.get_type_by_id(id); + if let Type::Object(super::ObjectNature::RealDeal) = ty { + environment + .get_chain_of_info() + .any(|info| info.prototypes.get(&id).is_some_and(|p| *p == TypeId::ARRAY_TYPE)) + } else if let Type::AliasTo { to, .. } = ty { + tuple_like(*to, types, environment) + } else { + false + } +} + +pub(crate) fn _unfold_tuple(_ty: TypeId) -> TypeId { + // return Type::PropertyOf() + todo!() +} + +pub(crate) fn _assign_to_tuple(_ty: TypeId) -> TypeId { + todo!() + // if let PropertyKey::Type(slice) = +} + +pub fn get_array_length( + ctx: &impl InformationChain, + on: TypeId, + types: &TypeStore, +) -> Result<ordered_float::NotNan<f64>, Option<TypeId>> { + let length_property = properties::PropertyKey::String(std::borrow::Cow::Borrowed("length")); + let id = properties::get_simple_property_value(ctx, on, &length_property, types).ok_or(None)?; + if let Type::Constant(Constant::Number(n)) = types.get_type_by_id(id) { + Ok(*n) + } else { + Err(Some(id)) + } +} + +/// TODO name? +#[derive(Clone, Copy, Debug)] +pub enum ArrayItem { + Member(TypeId), + Optional(TypeId), + Wildcard(TypeId), +} + +/// WIP +pub(crate) fn as_slice( + ty: TypeId, + types: &TypeStore, + environment: &crate::Environment, +) -> Result<Vec<ArrayItem>, ()> { + if tuple_like(ty, types, environment) { + let ty = if let Type::AliasTo { to, .. } = types.get_type_by_id(ty) { *to } else { ty }; + let properties = + environment.get_chain_of_info().find_map(|info| info.current_properties.get(&ty)); + if let Some(properties) = properties { + Ok(properties + .iter() + .filter_map(|(_, key, value)| { + let not_length_value = !key.is_equal_to("length"); + not_length_value.then(|| { + crate::utilities::notify!("key (should be incremental) {:?}", key); + if key.as_number(types).is_some() { + if let crate::PropertyValue::ConditionallyExists { .. } = value { + ArrayItem::Optional(value.as_get_type(types)) + } else { + ArrayItem::Member(value.as_get_type(types)) + } + } else { + ArrayItem::Wildcard(value.as_get_type(types)) + } + }) + }) + .collect()) + } else { + crate::utilities::notify!("BAD"); + Err(()) + } + } else { + Err(()) + } +} + +/// WIP for counting slice indexes +#[derive(derive_enum_from_into::EnumFrom, Clone, Copy, Debug)] +pub enum Counter { + On(usize), + AddTo(TypeId), +} + +impl Counter { + /// TODO &mut or Self -> Self? + pub fn increment(&mut self, types: &mut TypeStore) { + match self { + Counter::On(value) => { + *value += 1; + } + Counter::AddTo(value) => { + *value = types.register_type(Type::Constructor(Constructor::BinaryOperator { + lhs: *value, + operator: MathematicalOrBitwiseOperation::Add, + rhs: TypeId::ONE, + // TODO could be greater than + result: TypeId::NUMBER_TYPE, + })); + } + } + } + + pub fn add_type(&mut self, ty: TypeId, types: &mut TypeStore) { + let current = self.into_type(types); + let new = types.register_type(Type::Constructor(Constructor::BinaryOperator { + lhs: ty, + operator: MathematicalOrBitwiseOperation::Add, + rhs: current, + // TODO could be greater than + result: TypeId::NUMBER_TYPE, + })); + *self = Counter::AddTo(new); + } + + pub(crate) fn into_property_key(self) -> properties::PropertyKey<'static> { + match self { + Counter::On(value) => properties::PropertyKey::from_usize(value), + Counter::AddTo(ty) => properties::PropertyKey::Type(ty), + } + } + + pub(crate) fn into_type(self, types: &mut TypeStore) -> TypeId { + match self { + Counter::On(value) => { + types.new_constant_type(Constant::Number((value as f64).try_into().unwrap())) + } + Counter::AddTo(ty) => ty, + } + } +} + +/// To fill in for TSC behavior for mapped types +#[must_use] +pub fn references_key_of(id: TypeId, types: &TypeStore) -> bool { + match types.get_type_by_id(id) { + Type::AliasTo { to, .. } => references_key_of(*to, types), + Type::Or(lhs, rhs) | Type::And(lhs, rhs) => { + references_key_of(*lhs, types) || references_key_of(*rhs, types) + } + Type::RootPolyType(c) => references_key_of(c.get_constraint(), types), + Type::Constructor(c) => { + if let Constructor::KeyOf(..) = c { + true + } else if let Constructor::BinaryOperator { lhs, rhs, operator: _, result: _ } = c { + references_key_of(*lhs, types) || references_key_of(*rhs, types) + } else { + crate::utilities::notify!("TODO might have missed keyof {:?}", c); + false + } + } + Type::PartiallyAppliedGenerics(a) => { + if let GenericArguments::ExplicitRestrictions(ref e) = a.arguments { + e.0.iter().any(|(_, (lhs, _))| references_key_of(*lhs, types)) + } else { + false + } + } + Type::Interface { .. } + | Type::Class { .. } + | Type::Constant(_) + | Type::Narrowed { .. } + | Type::FunctionReference(_) + | Type::Object(_) + | Type::SpecialObject(_) => false, + } +} + +#[allow(clippy::match_like_matches_macro)] +#[must_use] +pub fn _type_is_error(ty: TypeId, types: &TypeStore) -> bool { + if ty == TypeId::UNIMPLEMENTED_ERROR_TYPE { + true + } else if let Type::RootPolyType(PolyNature::Error(_)) = types.get_type_by_id(ty) { + true + } else { + false + } +} + +/// TODO want to skip mapped generics because that would break subtyping +#[must_use] +pub fn get_type_as_conditional(ty: TypeId, types: &TypeStore) -> Option<(TypeId, TypeId, TypeId)> { + match types.get_type_by_id(ty) { + Type::Constructor(crate::types::Constructor::ConditionalResult { + condition, + truthy_result, + otherwise_result, + result_union: _, + }) => Some((*condition, *truthy_result, *otherwise_result)), + Type::Or(left, right) => Some((TypeId::OPEN_BOOLEAN_TYPE, *left, *right)), + // For reasons ! + Type::RootPolyType(PolyNature::MappedGeneric { .. }) => None, + _ => { + if let Some(constraint) = get_constraint(ty, types) { + get_type_as_conditional(constraint, types) + } else { + None + } + } + } +} + +/// TODO wip +#[must_use] +pub fn is_pseudo_continous((ty, generics): (TypeId, GenericChain), types: &TypeStore) -> bool { + if let TypeId::NUMBER_TYPE | TypeId::STRING_TYPE = ty { + true + } else if let Some(arg) = generics.as_ref().and_then(|args| args.get_single_argument(ty)) { + is_pseudo_continous((arg, generics), types) + } else { + let ty = types.get_type_by_id(ty); + if let Type::Or(left, right) = ty { + is_pseudo_continous((*left, generics), types) + || is_pseudo_continous((*right, generics), types) + } else if let Type::And(left, right) = ty { + is_pseudo_continous((*left, generics), types) + && is_pseudo_continous((*right, generics), types) + } else if let Type::RootPolyType(PolyNature::MappedGeneric { extends, .. }) = ty { + is_pseudo_continous((*extends, generics), types) + } else { + false + } + } +} + +#[must_use] +pub fn is_inferrable_type(ty: TypeId) -> bool { + matches!(ty, TypeId::ANY_TO_INFER_TYPE | TypeId::OBJECT_TYPE) +} + +/// For quick checking. Wraps [`subtyping::type_is_subtype`] +#[must_use] +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: true }, + object_constraints: None, + }; + + subtyping::type_is_subtype(to_satisfy, expr_ty, &mut state, information, types).is_subtype() +} + +// unfolds narrowing +#[must_use] +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 + } +} + +/// Temp fix for equality of narrowing stuff +#[must_use] +pub fn is_not_of_constant(ty: TypeId, types: &TypeStore) -> bool { + if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::NOT_RESTRICTION, + arguments, + }) = types.get_type_by_id(ty) + { + let inner = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap(); + types.get_type_by_id(inner).is_constant() + } else { + false + } +} + +// TODO narrowed as well +#[must_use] +pub fn type_equal(lhs: TypeId, rhs: TypeId, types: &TypeStore) -> bool { + if lhs == rhs { + true + } else if let (Type::Constant(lhs), Type::Constant(rhs)) = + (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) + { + lhs == rhs + } else { + false + } +} + +#[derive(Debug, Copy, Clone)] +pub struct AndCondition(pub TypeId); + +#[derive(Debug)] +pub struct OrCase(pub Vec<AndCondition>); + +#[must_use] +pub fn into_conditions(id: TypeId, types: &TypeStore) -> Vec<AndCondition> { + let ty = types.get_type_by_id(id); + + // Temp fix + if let Type::Constructor(Constructor::BinaryOperator { result, .. }) = ty { + return if matches!(*result, TypeId::NUMBER_TYPE | TypeId::STRING_TYPE) { + vec![AndCondition(id)] + } else { + into_conditions(*result, types) + }; + } + + if let Type::And(lhs, rhs) = ty { + let mut buf = into_conditions(*lhs, types); + buf.append(&mut into_conditions(*rhs, types)); + buf + } else if let Some(backing) = get_constraint_or_alias(id, types) { + into_conditions(backing, types) + } else { + vec![AndCondition(id)] + } +} + +#[must_use] +pub fn into_cases(id: TypeId, types: &TypeStore) -> Vec<OrCase> { + let ty = types.get_type_by_id(id); + if let Type::Or(lhs, rhs) = ty { + let mut buf = into_cases(*lhs, types); + buf.append(&mut into_cases(*rhs, types)); + buf + } else if let Some(backing) = get_constraint_or_alias(id, types) { + into_cases(backing, types) + } else { + vec![OrCase(into_conditions(id, types))] + } +} + +/// Returns the constraint or base of a constant for a type. Otherwise just return the type +#[must_use] +pub fn get_larger_type(on: TypeId, types: &TypeStore) -> TypeId { + if let Some(poly_base) = get_constraint(on, types) { + poly_base + } else if let Type::Constant(cst) = types.get_type_by_id(on) { + cst.get_backing_type() + } else { + on + } +} + +#[must_use] +pub fn get_constraint_or_alias(on: TypeId, types: &TypeStore) -> Option<TypeId> { + match types.get_type_by_id(on) { + Type::RootPolyType(rpt) => Some(rpt.get_constraint()), + Type::Constructor(constr) => Some(constr.get_constraint()), + Type::AliasTo { to, parameters: None, .. } => Some(*to), + Type::Narrowed { narrowed_to, .. } => Some(*narrowed_to), + _ => None, + } +} + +#[derive(Debug)] +pub struct TemplatelLiteralExpansion { + pub parts: Vec<(String, TypeId)>, + pub rest: String, +} + +impl TemplatelLiteralExpansion { + // pub const EMPTY: TemplatelLiteralExpansion = + // TemplatelLiteralExpansion { parts: Vec::new(), rest: String::default() }; + + /// TODO more, maybe involving types + #[must_use] + pub fn is_disjoint(&self, other: &Self) -> bool { + crate::utilities::notify!("{:?}", (self, other)); + if let (Some((lhs, _)), Some((rhs, _))) = (self.parts.first(), other.parts.first()) { + let prefix_length = std::cmp::min(lhs.len(), rhs.len()); + if lhs[..prefix_length] != rhs[..prefix_length] { + return true; + } + } + + let postfix_len = std::cmp::min(self.rest.len(), other.rest.len()); + self.rest[..postfix_len] != other.rest[..postfix_len] + } + + #[must_use] + pub fn as_single_string(&self) -> Option<&str> { + if self.parts.is_empty() { + Some(&self.rest) + } else { + None + } + } + + #[must_use] + pub fn is_empty(&self) -> bool { + self.parts.is_empty() && self.rest.is_empty() + } + + #[must_use] + pub fn concatenate(self, mut other: Self) -> Self { + let mut parts: Vec<(String, TypeId)> = self.parts; + let rest = if other.parts.is_empty() { + format!("{}{}", self.rest, other.rest) + } else { + other.parts[0].0.insert_str(0, &self.rest); + parts.append(&mut other.parts); + other.rest + }; + Self { parts, rest } + } + + /// Forms a TL no matter what + #[must_use] + pub fn from_type(id: TypeId, types: &TypeStore) -> Self { + let ty = types.get_type_by_id(id); + if let Type::Constant(constant) = ty { + TemplatelLiteralExpansion { + parts: Default::default(), + rest: constant.as_js_string().into_owned(), + } + } else if let Type::Constructor(Constructor::BinaryOperator { + lhs, + operator: MathematicalOrBitwiseOperation::Add, + rhs, + result: TypeId::STRING_TYPE, + }) = ty + { + let lhs = Self::from_type(*lhs, types); + let rhs = Self::from_type(*rhs, types); + lhs.concatenate(rhs) + } else if let Some(base) = get_constraint_or_alias(id, types) { + Self::from_type(base, types) + } else { + TemplatelLiteralExpansion { + parts: vec![(Default::default(), id)], + rest: Default::default(), + } + } + } +} diff --git a/checker/src/types/intrinsics.rs b/checker/src/types/intrinsics.rs index 17581d27..cde00b31 100644 --- a/checker/src/types/intrinsics.rs +++ b/checker/src/types/intrinsics.rs @@ -2,15 +2,18 @@ use source_map::SpanWithSource; use crate::{ types::{ - generics::generic_type_arguments::GenericArguments, get_constraint, - PartiallyAppliedGenerics, TypeStore, + generics::generic_type_arguments::GenericArguments, get_constraint, helpers::into_cases, + Constant, Constructor, MathematicalOrBitwiseOperation, PartiallyAppliedGenerics, TypeStore, }, TypeId, }; use super::Type; -pub use crate::utilities::float_range::FloatRange; +pub use crate::utilities::{ + float_range::{FloatRange, InclusiveExclusive}, + modulo_class::ModuloClass, +}; /// These are special marker types (using [`Type::Alias`]) /// @@ -93,7 +96,7 @@ pub(crate) fn apply_string_intrinsic(on: TypeId, s: &str) -> String { } #[must_use] -pub fn tsc_string_intrinsic(id: TypeId) -> bool { +pub fn is_tsc_string_intrinsic(id: TypeId) -> bool { matches!( id, TypeId::STRING_UPPERCASE @@ -105,8 +108,8 @@ pub fn tsc_string_intrinsic(id: TypeId) -> bool { #[must_use] pub fn is_intrinsic(id: TypeId) -> bool { - tsc_string_intrinsic(id) - || ezno_number_intrinsic(id) + is_tsc_string_intrinsic(id) + || is_ezno_number_intrinsic(id) || matches!( id, TypeId::LITERAL_RESTRICTION @@ -119,8 +122,8 @@ pub fn is_intrinsic(id: TypeId) -> bool { } #[must_use] -pub fn ezno_number_intrinsic(id: TypeId) -> bool { - matches!(id, TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE | TypeId::MULTIPLE_OF) +pub fn is_ezno_number_intrinsic(id: TypeId) -> bool { + matches!(id, TypeId::GREATER_THAN | TypeId::LESS_THAN | TypeId::MULTIPLE_OF) } #[must_use] @@ -128,15 +131,17 @@ pub fn get_greater_than(on: TypeId, types: &TypeStore) -> Option<(bool, TypeId)> let on = get_constraint(on, types).unwrap_or(on); let ty = types.get_type_by_id(on); if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: on @ (TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE), + on: TypeId::GREATER_THAN, arguments, }) = ty { - let inclusive = *on == TypeId::INCLUSIVE_RANGE; - let floor = arguments.get_structure_restriction(TypeId::NUMBER_FLOOR_GENERIC).unwrap(); - Some((inclusive, floor)) + let floor = arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(); + Some((false, floor)) } else if let Type::And(lhs, rhs) = ty { get_greater_than(*lhs, types).or_else(|| get_greater_than(*rhs, types)) + } else if let Type::Or(lhs, rhs) = ty { + // TODO temp + get_greater_than(*lhs, types).or_else(|| get_greater_than(*rhs, types)) } else if let Type::Constant(crate::Constant::Number(..)) = ty { Some((true, on)) } else { @@ -149,17 +154,17 @@ pub fn get_less_than(on: TypeId, types: &TypeStore) -> Option<(bool, TypeId)> { let on = get_constraint(on, types).unwrap_or(on); let ty = types.get_type_by_id(on); if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: on @ (TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE), + on: TypeId::LESS_THAN, arguments, }) = ty { - let inclusive = *on == TypeId::INCLUSIVE_RANGE; - Some(( - inclusive, - arguments.get_structure_restriction(TypeId::NUMBER_CEILING_GENERIC).unwrap(), - )) + let floor = arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(); + Some((false, floor)) } else if let Type::And(lhs, rhs) = ty { get_less_than(*lhs, types).or_else(|| get_less_than(*rhs, types)) + } else if let Type::Or(lhs, rhs) = ty { + // TODO temp + get_less_than(*lhs, types).or_else(|| get_less_than(*rhs, types)) } else if let Type::Constant(crate::Constant::Number(..)) = ty { Some((true, on)) } else { @@ -168,60 +173,50 @@ pub fn get_less_than(on: TypeId, types: &TypeStore) -> Option<(bool, TypeId)> { } #[must_use] -pub fn get_range(on: TypeId, types: &TypeStore) -> Option<FloatRange> { - let on = get_constraint(on, types).unwrap_or(on); - let ty = types.get_type_by_id(on); - if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: on @ (TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE), - arguments, - }) = ty - { - let inclusive = *on == TypeId::INCLUSIVE_RANGE; - crate::utilities::notify!("{:?} {:?}", on, arguments); - let floor = arguments.get_structure_restriction(TypeId::NUMBER_FLOOR_GENERIC).unwrap(); - let ceiling = arguments.get_structure_restriction(TypeId::NUMBER_CEILING_GENERIC).unwrap(); - if let ( - Type::Constant(crate::Constant::Number(floor)), - Type::Constant(crate::Constant::Number(ceiling)), - ) = (types.get_type_by_id(floor), types.get_type_by_id(ceiling)) - { - let (floor, ceiling) = (*floor, *ceiling); - Some(if inclusive { - FloatRange::Inclusive { floor, ceiling } - } else { - FloatRange::Exclusive { floor, ceiling } - }) - } else { - crate::utilities::notify!("Not bottom top number"); - None - } - } else if let Type::Constant(crate::Constant::Number(number)) = ty { - Some(FloatRange::single(*number)) +pub fn range_to_type(range: FloatRange, types: &mut TypeStore) -> TypeId { + // TODO skip if infinite + let floor_ty = types.new_constant_type(Constant::Number(range.floor.1)); + let ceiling_ty = types.new_constant_type(Constant::Number(range.ceiling.1)); + let floor = range + .get_greater_than() + .map(|_number| new_intrinsic(&Intrinsic::GreaterThan, floor_ty, types)); + + let ceiling = + range.get_less_than().map(|_number| new_intrinsic(&Intrinsic::LessThan, ceiling_ty, types)); + + let mut ty = if let (Some(f), Some(c)) = (floor, ceiling) { + types.new_and_type(f, c) } else { - None + floor.or(ceiling).unwrap() + }; + + if let InclusiveExclusive::Inclusive = range.floor.0 { + ty = types.new_or_type(ty, floor_ty); } + if let InclusiveExclusive::Inclusive = range.ceiling.0 { + ty = types.new_or_type(ty, ceiling_ty); + } + ty } -#[must_use] -pub fn range_to_type(range: FloatRange, types: &mut TypeStore) -> TypeId { - use source_map::Nullable; - - let on = if let FloatRange::Inclusive { .. } = range { - TypeId::INCLUSIVE_RANGE +pub fn modulo_to_type(mod_class: ModuloClass, types: &mut TypeStore) -> TypeId { + // TODO skip if infinite + let modulo = types.new_constant_type(Constant::Number(mod_class.modulo)); + let ty = new_intrinsic(&Intrinsic::MultipleOf, modulo, types); + if mod_class.offset == 0. { + ty } else { - TypeId::EXCLUSIVE_RANGE - }; - let (FloatRange::Inclusive { floor, ceiling } | FloatRange::Exclusive { floor, ceiling }) = - range; - let floor = types.new_constant_type(crate::Constant::Number(floor)); - let ceiling = types.new_constant_type(crate::Constant::Number(ceiling)); - let arguments = GenericArguments::ExplicitRestrictions(crate::Map::from_iter([ - (TypeId::NUMBER_FLOOR_GENERIC, (floor, SpanWithSource::NULL)), - (TypeId::NUMBER_CEILING_GENERIC, (ceiling, SpanWithSource::NULL)), - ])); - types.register_type(Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on, arguments })) + let offset = types.new_constant_type(Constant::Number(mod_class.offset)); + types.register_type(Type::Constructor(Constructor::BinaryOperator { + lhs: ty, + operator: MathematicalOrBitwiseOperation::Add, + rhs: offset, + result: TypeId::NUMBER_TYPE, + })) + } } +// TODO offsets as well #[must_use] pub fn get_multiple(on: TypeId, types: &TypeStore) -> Option<TypeId> { let on = get_constraint(on, types).unwrap_or(on); @@ -231,7 +226,7 @@ pub fn get_multiple(on: TypeId, types: &TypeStore) -> Option<TypeId> { arguments, }) = ty { - arguments.get_structure_restriction(TypeId::NUMBER_FLOOR_GENERIC) + arguments.get_structure_restriction(TypeId::NUMBER_GENERIC) } else if let Type::And(lhs, rhs) = ty { get_multiple(*lhs, types).or_else(|| get_multiple(*rhs, types)) } else if let Type::Constant(crate::Constant::Number(..)) = ty { @@ -255,7 +250,7 @@ pub fn is_not_not_a_number(on: TypeId, types: &TypeStore) -> bool { } else if let Type::Or(lhs, rhs) = ty { is_not_not_a_number(*lhs, types) && is_not_not_a_number(*rhs, types) } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: TypeId::MULTIPLE_OF | TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE, + on: TypeId::MULTIPLE_OF | TypeId::GREATER_THAN | TypeId::LESS_THAN, arguments: _, }) = ty { @@ -281,26 +276,26 @@ pub fn new_intrinsic(intrinsic: &Intrinsic, argument: TypeId, types: &mut TypeSt Intrinsic::NoInfer => (TypeId::NO_INFER, TypeId::T_TYPE), Intrinsic::Literal => (TypeId::LITERAL_RESTRICTION, TypeId::T_TYPE), Intrinsic::LessThan => { - let arguments = GenericArguments::ExplicitRestrictions(crate::Map::from_iter([ - (TypeId::NUMBER_FLOOR_GENERIC, (TypeId::NEG_INFINITY, SpanWithSource::NULL)), - (TypeId::NUMBER_CEILING_GENERIC, (argument, SpanWithSource::NULL)), - ])); + let arguments = GenericArguments::ExplicitRestrictions(crate::Map::from_iter([( + TypeId::NUMBER_GENERIC, + (argument, SpanWithSource::NULL), + )])); return types.register_type(Type::PartiallyAppliedGenerics( - PartiallyAppliedGenerics { on: TypeId::EXCLUSIVE_RANGE, arguments }, + PartiallyAppliedGenerics { on: TypeId::LESS_THAN, arguments }, )); } Intrinsic::GreaterThan => { - let arguments = GenericArguments::ExplicitRestrictions(crate::Map::from_iter([ - (TypeId::NUMBER_FLOOR_GENERIC, (argument, SpanWithSource::NULL)), - (TypeId::NUMBER_CEILING_GENERIC, (TypeId::INFINITY, SpanWithSource::NULL)), - ])); + let arguments = GenericArguments::ExplicitRestrictions(crate::Map::from_iter([( + TypeId::NUMBER_GENERIC, + (argument, SpanWithSource::NULL), + )])); return types.register_type(Type::PartiallyAppliedGenerics( - PartiallyAppliedGenerics { on: TypeId::EXCLUSIVE_RANGE, arguments }, + PartiallyAppliedGenerics { on: TypeId::GREATER_THAN, arguments }, )); } - Intrinsic::MultipleOf => (TypeId::MULTIPLE_OF, TypeId::NUMBER_FLOOR_GENERIC), + Intrinsic::MultipleOf => (TypeId::MULTIPLE_OF, TypeId::NUMBER_GENERIC), Intrinsic::Exclusive => (TypeId::EXCLUSIVE_RESTRICTION, TypeId::T_TYPE), Intrinsic::Not => { // Double negation @@ -316,6 +311,7 @@ pub fn new_intrinsic(intrinsic: &Intrinsic, argument: TypeId, types: &mut TypeSt } Intrinsic::CaseInsensitive => (TypeId::CASE_INSENSITIVE, TypeId::STRING_GENERIC), }; + let arguments = GenericArguments::ExplicitRestrictions(crate::Map::from_iter([( to_pair, (argument, SpanWithSource::NULL), @@ -323,3 +319,168 @@ pub fn new_intrinsic(intrinsic: &Intrinsic, argument: TypeId, types: &mut TypeSt types.register_type(Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on, arguments })) } + +#[must_use] +pub fn get_range_and_mod_class( + ty: TypeId, + types: &TypeStore, +) -> (Option<FloatRange>, Option<ModuloClass>) { + let cases = into_cases(ty, types); + let interesting = cases.iter().enumerate().find_map(|(idx, case)| { + case.0 + .iter() + .copied() + .try_fold(Vec::new(), |mut acc, ty| { + if let Ok(item) = PureNumberIntrinsic::try_from_type(ty.0, types) { + acc.push(item); + Some(acc) + } else { + None + } + }) + .map(|value| (idx, value)) + }); + + if let Some((idx, interesting)) = interesting { + let mut range: Option<FloatRange> = None; + let mut modulo_class: Option<ModuloClass> = None; + for number_condition in interesting { + match number_condition { + PureNumberIntrinsic::GreaterThan(greater_than) => { + range = Some(match range { + None => FloatRange::new_greater_than(greater_than), + Some(mut range) => { + range.floor.1 = range.floor.1.max(greater_than); + range + } + }); + } + PureNumberIntrinsic::LessThan(less_than) => { + range = Some(match range { + None => FloatRange::new_less_than(less_than), + Some(mut range) => { + range.ceiling.1 = range.ceiling.1.min(less_than); + range + } + }); + } + PureNumberIntrinsic::Modulo { modulo, offset } => { + modulo_class = Some(if modulo_class.is_none() { + ModuloClass::new(modulo, offset) + } else { + crate::utilities::notify!("TODO intersection"); + return (None, None); + }); + } + } + } + + for (other_idx, case) in cases.into_iter().enumerate() { + // Skip the interesting case + if idx == other_idx { + continue; + } + // Test whether all cases fit inside this new condition. Also modify ranges for inclusion if not + if let [value] = &case.0[..] { + if let Type::Constant(Constant::Number(num)) = types.get_type_by_id(value.0) { + let num = *num; + if let Some(ref modulo_class) = modulo_class { + if !modulo_class.contains(num) { + return (None, None); + } + } + if let Some(ref mut range) = range { + if range.floor.1 == num { + range.floor.0 = InclusiveExclusive::Inclusive; + } else if range.ceiling.1 == num { + range.ceiling.0 = InclusiveExclusive::Inclusive; + } else if !range.contains(num) { + crate::utilities::notify!( + "Here, number not contained in current range" + ); + return (None, None); + } + } + } + } else { + return (None, None); + } + } + (range, modulo_class) + } else if let Type::Constant(Constant::Number(num)) = types.get_type_by_id(ty) { + (Some(FloatRange::new_single(*num)), None) + } else { + // crate::utilities::notify!("Not interesting or constant {:?}", ty); + (None, None) + } +} + +type BetterF64 = ordered_float::NotNan<f64>; + +/// Unit. No combinations at this point +#[derive(Debug)] +pub enum PureNumberIntrinsic { + GreaterThan(BetterF64), + LessThan(BetterF64), + Modulo { modulo: BetterF64, offset: BetterF64 }, +} + +impl PureNumberIntrinsic { + pub fn try_from_type(ty: TypeId, types: &TypeStore) -> Result<PureNumberIntrinsic, ()> { + let ty = types.get_type_by_id(ty); + if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: on @ (TypeId::GREATER_THAN | TypeId::LESS_THAN | TypeId::MULTIPLE_OF), + arguments, + }) = ty + { + let arg = arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(); + if let Type::Constant(Constant::Number(number)) = types.get_type_by_id(arg) { + match *on { + TypeId::GREATER_THAN => Ok(PureNumberIntrinsic::GreaterThan(*number)), + TypeId::LESS_THAN => Ok(PureNumberIntrinsic::LessThan(*number)), + TypeId::MULTIPLE_OF => Ok(PureNumberIntrinsic::Modulo { + modulo: *number, + offset: 0f64.try_into().unwrap(), + }), + _ => todo!(), + } + } else { + Err(()) + } + } else if let Type::Constructor(Constructor::BinaryOperator { + lhs, + operator: MathematicalOrBitwiseOperation::Add, + rhs, + .. + }) = ty + { + let lhs_ty = types.get_type_by_id(super::get_constraint(*lhs, types).unwrap_or(*lhs)); + crate::utilities::notify!("{:?}", lhs_ty); + let lhs_modulo = if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::MULTIPLE_OF, + arguments, + }) = lhs_ty + { + let arg = arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(); + if let Type::Constant(Constant::Number(number)) = types.get_type_by_id(arg) { + Some(number) + } else { + None + } + } else { + None + }; + + if let (Some(modulo), Type::Constant(Constant::Number(offset))) = + (lhs_modulo, types.get_type_by_id(*rhs)) + { + Ok(PureNumberIntrinsic::Modulo { modulo: *modulo, offset: *offset }) + } else { + Err(()) + } + } else { + // crate::utilities::notify!("err here {:?}", ty); + Err(()) + } + } +} diff --git a/checker/src/types/mod.rs b/checker/src/types/mod.rs index a4bd6d90..81dcd86b 100644 --- a/checker/src/types/mod.rs +++ b/checker/src/types/mod.rs @@ -4,6 +4,7 @@ pub mod classes; pub mod disjoint; pub mod functions; pub mod generics; +pub mod helpers; pub mod intrinsics; pub mod logical; pub mod printing; @@ -33,13 +34,12 @@ pub use crate::features::objects::SpecialObject; use crate::{ context::InformationChain, events::RootReference, - features::operations::{CanonicalEqualityAndInequality, MathematicalAndBitwise}, + features::operations::{CanonicalEqualityAndInequality, MathematicalOrBitwiseOperation}, subtyping::SliceArguments, Decidable, FunctionId, }; use derive_debug_extras::DebugExtras; -use derive_enum_from_into::EnumFrom; use source_map::SpanWithSource; pub type ExplicitTypeArgument = (TypeId, SpanWithSource); @@ -143,32 +143,29 @@ impl TypeId { pub const WRITABLE_KEY_ARGUMENT: Self = Self(43); // Ezno intrinsics + pub const NUMBER_GENERIC: Self = Self(44); + pub const GREATER_THAN: Self = Self(45); + pub const LESS_THAN: Self = Self(46); + pub const MULTIPLE_OF: Self = Self(47); + pub const NOT_NOT_A_NUMBER: Self = Self(48); + pub const NUMBER_BUT_NOT_NOT_A_NUMBER: Self = Self(49); - /// Used in [`Self::INCLUSIVE_RANGE`], [`Self::EXCLUSIVE_RANGE`] and [`Self::MULTIPLE_OF`] - pub const NUMBER_FLOOR_GENERIC: Self = Self(44); - pub const NUMBER_CEILING_GENERIC: Self = Self(45); - pub const INCLUSIVE_RANGE: Self = Self(46); - pub const EXCLUSIVE_RANGE: Self = Self(47); - pub const MULTIPLE_OF: Self = Self(48); - pub const NOT_NOT_A_NUMBER: Self = Self(49); - pub const NUMBER_BUT_NOT_NOT_A_NUMBER: Self = Self(50); - - pub const LITERAL_RESTRICTION: Self = Self(51); - pub const EXCLUSIVE_RESTRICTION: Self = Self(52); - pub const NOT_RESTRICTION: Self = Self(53); + pub const LITERAL_RESTRICTION: Self = Self(50); + pub const EXCLUSIVE_RESTRICTION: Self = Self(51); + pub const NOT_RESTRICTION: Self = Self(52); /// This is needed for the TSC string intrinsics - pub const CASE_INSENSITIVE: Self = Self(54); + pub const CASE_INSENSITIVE: Self = Self(53); /// WIP - pub const OPEN_BOOLEAN_TYPE: Self = Self(55); - pub const OPEN_NUMBER_TYPE: Self = Self(56); + pub const OPEN_BOOLEAN_TYPE: Self = Self(54); + pub const OPEN_NUMBER_TYPE: Self = Self(55); /// For `+` operator - pub const STRING_OR_NUMBER: Self = Self(57); + pub const STRING_OR_NUMBER: Self = Self(56); /// Above add one (because [`TypeId`] starts at zero). Used to assert that the above is all correct - pub(crate) const INTERNAL_TYPE_COUNT: usize = 58; + pub(crate) const INTERNAL_TYPE_COUNT: usize = 57; } #[derive(Debug, binary_serialize_derive::BinarySerializable)] @@ -193,13 +190,11 @@ pub enum Type { /// - Can be used for subtypes (aka N aliases number then more types on top) /// - **Does not imply .prototype = ** AliasTo { - to: TypeId, name: String, parameters: Option<Vec<TypeId>>, + to: TypeId, }, - /// For number and other rooted types - /// - /// Although they all alias Object + /// Properties are in environment (for declaration merging) Interface { name: String, parameters: Option<Vec<TypeId>>, @@ -334,9 +329,11 @@ impl Type { // Fine Type::Narrowed { .. } | Type::Constructor(_) | Type::RootPolyType(_) => true, // TODO what about if left or right - Type::And(_, _) | Type::Or(_, _) => false, + // TODO should not be true here + Type::And(_, _) | Type::Or(_, _) => true, // TODO what about if it aliases dependent? - Type::AliasTo { .. } => false, + // TODO should not be true here + Type::AliasTo { .. } => true, Type::Interface { .. } | Type::Class { .. } => false, Type::Constant(_) | Type::SpecialObject(_) @@ -374,7 +371,7 @@ impl Type { pub enum Constructor { BinaryOperator { lhs: TypeId, - operator: MathematicalAndBitwise, + operator: MathematicalOrBitwiseOperation, rhs: TypeId, /// for add + number intrinsics result: TypeId, @@ -420,18 +417,33 @@ pub enum Constructor { } impl Constructor { - fn get_base(&self) -> Option<TypeId> { + #[must_use] + pub fn get_constraint(&self) -> TypeId { match self { - Constructor::ConditionalResult { result_union: result, .. } - | Constructor::Awaited { result, .. } - | Constructor::Property { result, .. } - | Constructor::Image { result, .. } => Some(*result), - Constructor::BinaryOperator { .. } - | Constructor::CanonicalRelationOperator { .. } - | Constructor::TypeExtends(_) - | Constructor::TypeOperator(_) => None, - // TODO or symbol - Constructor::KeyOf(_) => Some(TypeId::STRING_TYPE), + Constructor::BinaryOperator { result, .. } + | Constructor::Awaited { on: _, result } + | Constructor::Image { on: _, with: _, result } => *result, + Constructor::Property { on: _, under: _, result, mode: _ } => { + // crate::utilities::notify!("Here, result of a property get"); + *result + } + Constructor::ConditionalResult { result_union, .. } => { + // TODO dynamic and open poly + *result_union + } + Constructor::TypeOperator(op) => match op { + // TODO union of names + TypeOperator::TypeOf(_) => TypeId::STRING_TYPE, + TypeOperator::IsPrototype { .. } | TypeOperator::HasProperty(..) => { + TypeId::BOOLEAN_TYPE + } + }, + Constructor::CanonicalRelationOperator { .. } => TypeId::BOOLEAN_TYPE, + Constructor::TypeExtends(op) => { + let crate::types::TypeExtends { .. } = op; + TypeId::BOOLEAN_TYPE + } + Constructor::KeyOf(_) => TypeId::STRING_TYPE, } } } @@ -605,7 +617,6 @@ pub fn is_type_truthy_falsy(id: TypeId, types: &TypeStore) -> Decidable<bool> { | Type::Constructor(_) | Type::Interface { .. } | Type::PartiallyAppliedGenerics(..) - | Type::Narrowed { .. } | Type::Class { .. } => { // TODO some of these case are known Decidable::Unknown(id) @@ -617,6 +628,7 @@ pub fn is_type_truthy_falsy(id: TypeId, types: &TypeStore) -> Decidable<bool> { // TODO strict casts Decidable::Known(cast_as_boolean(cst, false).unwrap()) } + Type::Narrowed { narrowed_to, .. } => is_type_truthy_falsy(*narrowed_to, types), } } } @@ -671,32 +683,7 @@ pub(crate) fn is_explicit_generic(on: TypeId, types: &TypeStore) -> bool { pub(crate) fn get_constraint(on: TypeId, types: &TypeStore) -> Option<TypeId> { match types.get_type_by_id(on) { Type::RootPolyType(nature) => Some(nature.get_constraint()), - Type::Constructor(constructor) => match constructor.clone() { - Constructor::BinaryOperator { result, .. } - | Constructor::Awaited { on: _, result } - | Constructor::Image { on: _, with: _, result } => Some(result), - Constructor::Property { on: _, under: _, result, mode: _ } => { - // crate::utilities::notify!("Here, result of a property get"); - Some(result) - } - Constructor::ConditionalResult { result_union, .. } => { - // TODO dynamic and open poly - Some(result_union) - } - 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::KeyOf(_) => Some(TypeId::STRING_TYPE), - }, + Type::Constructor(constructor) => Some(constructor.get_constraint()), Type::Narrowed { from: _, narrowed_to } => Some(*narrowed_to), Type::Object(ObjectNature::RealDeal) => { // crate::utilities::notify!("Might be missing some mutations that are possible here"); @@ -706,18 +693,6 @@ pub(crate) fn get_constraint(on: TypeId, types: &TypeStore) -> Option<TypeId> { } } -/// Returns the constraint or base of a constant for a type. Otherwise just return the type -#[must_use] -pub fn get_larger_type(on: TypeId, types: &TypeStore) -> TypeId { - if let Some(poly_base) = get_constraint(on, types) { - poly_base - } else if let Type::Constant(cst) = types.get_type_by_id(on) { - cst.get_backing_type() - } else { - on - } -} - /// TODO T on `Array`, U, V on `Map` etc. Works around not having `&mut TypeStore` and mutations in-between #[derive(Clone, Debug, binary_serialize_derive::BinarySerializable)] pub enum LookUpGeneric { @@ -791,317 +766,3 @@ impl TypeCombinable for TypeId { TypeId::UNDEFINED_TYPE } } - -/// Used for **both** inference and narrowing -pub enum Confirmation { - HasProperty { on: (), property: () }, - IsType { on: (), ty: () }, -} - -pub(crate) fn get_structure_arguments_based_on_object_constraint<'a, C: InformationChain>( - object: TypeId, - info_chain: &C, - types: &'a TypeStore, -) -> Option<&'a GenericArguments> { - if let Some(object_constraint) = - info_chain.get_chain_of_info().find_map(|c| c.object_constraints.get(&object).copied()) - { - let ty = types.get_type_by_id(object_constraint); - if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { arguments, .. }) = ty { - Some(arguments) - } else { - crate::utilities::notify!("Generics might be missed here {:?}", ty); - None - } - } else { - None - } -} - -pub(crate) fn tuple_like(id: TypeId, types: &TypeStore, environment: &crate::Environment) -> bool { - // TODO should be `ObjectNature::AnonymousObjectType` or something else - let ty = types.get_type_by_id(id); - if let Type::Object(ObjectNature::RealDeal) = ty { - environment - .get_chain_of_info() - .any(|info| info.prototypes.get(&id).is_some_and(|p| *p == TypeId::ARRAY_TYPE)) - } else if let Type::AliasTo { to, .. } = ty { - tuple_like(*to, types, environment) - } else { - false - } -} - -pub(crate) fn _unfold_tuple(_ty: TypeId) -> TypeId { - // return Type::PropertyOf() - todo!() -} - -pub(crate) fn _assign_to_tuple(_ty: TypeId) -> TypeId { - todo!() - // if let PropertyKey::Type(slice) = -} - -fn get_array_length( - ctx: &impl InformationChain, - on: TypeId, - types: &TypeStore, -) -> Result<ordered_float::NotNan<f64>, Option<TypeId>> { - 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) - } else { - Err(Some(id)) - } -} - -/// TODO name? -#[derive(Clone, Copy, Debug)] -pub enum ArrayItem { - Member(TypeId), - Optional(TypeId), - Wildcard(TypeId), -} - -/// WIP -pub(crate) fn as_slice( - ty: TypeId, - types: &TypeStore, - environment: &crate::Environment, -) -> Result<Vec<ArrayItem>, ()> { - if tuple_like(ty, types, environment) { - let ty = if let Type::AliasTo { to, .. } = types.get_type_by_id(ty) { *to } else { ty }; - let properties = - environment.get_chain_of_info().find_map(|info| info.current_properties.get(&ty)); - if let Some(properties) = properties { - Ok(properties - .iter() - .filter_map(|(_, key, value)| { - let not_length_value = !key.is_equal_to("length"); - not_length_value.then(|| { - crate::utilities::notify!("key (should be incremental) {:?}", key); - if key.as_number(types).is_some() { - if let crate::PropertyValue::ConditionallyExists { .. } = value { - ArrayItem::Optional(value.as_get_type(types)) - } else { - ArrayItem::Member(value.as_get_type(types)) - } - } else { - ArrayItem::Wildcard(value.as_get_type(types)) - } - }) - }) - .collect()) - } else { - crate::utilities::notify!("BAD"); - Err(()) - } - } else { - Err(()) - } -} - -/// WIP for counting slice indexes -#[derive(EnumFrom, Clone, Copy, Debug)] -pub enum Counter { - On(usize), - AddTo(TypeId), -} - -impl Counter { - /// TODO &mut or Self -> Self? - pub fn increment(&mut self, types: &mut TypeStore) { - match self { - Counter::On(value) => { - *value += 1; - } - Counter::AddTo(value) => { - *value = types.register_type(Type::Constructor(Constructor::BinaryOperator { - lhs: *value, - operator: MathematicalAndBitwise::Add, - rhs: TypeId::ONE, - // TODO could be greater than - result: TypeId::NUMBER_TYPE, - })); - } - } - } - - pub fn add_type(&mut self, ty: TypeId, types: &mut TypeStore) { - let current = self.into_type(types); - let new = types.register_type(Type::Constructor(Constructor::BinaryOperator { - lhs: ty, - operator: MathematicalAndBitwise::Add, - rhs: current, - // TODO could be greater than - result: TypeId::NUMBER_TYPE, - })); - *self = Counter::AddTo(new); - } - - pub(crate) fn into_property_key(self) -> properties::PropertyKey<'static> { - match self { - Counter::On(value) => properties::PropertyKey::from_usize(value), - Counter::AddTo(ty) => properties::PropertyKey::Type(ty), - } - } - - pub(crate) fn into_type(self, types: &mut TypeStore) -> TypeId { - match self { - Counter::On(value) => { - types.new_constant_type(Constant::Number((value as f64).try_into().unwrap())) - } - Counter::AddTo(ty) => ty, - } - } -} - -pub(crate) mod helpers { - use super::{ - get_constraint, subtyping, Constructor, GenericArguments, GenericChain, InformationChain, - PartiallyAppliedGenerics, PolyNature, Type, TypeId, TypeStore, - }; - - /// To fill in for TSC behavior for mapped types - #[must_use] - pub fn references_key_of(id: TypeId, types: &TypeStore) -> bool { - match types.get_type_by_id(id) { - Type::AliasTo { to, .. } => references_key_of(*to, types), - Type::Or(lhs, rhs) | Type::And(lhs, rhs) => { - references_key_of(*lhs, types) || references_key_of(*rhs, types) - } - Type::RootPolyType(c) => references_key_of(c.get_constraint(), types), - Type::Constructor(c) => { - if let Constructor::KeyOf(..) = c { - true - } else if let Constructor::BinaryOperator { lhs, rhs, operator: _, result: _ } = c { - references_key_of(*lhs, types) || references_key_of(*rhs, types) - } else { - crate::utilities::notify!("TODO might have missed keyof {:?}", c); - false - } - } - Type::PartiallyAppliedGenerics(a) => { - if let GenericArguments::ExplicitRestrictions(ref e) = a.arguments { - e.0.iter().any(|(_, (lhs, _))| references_key_of(*lhs, types)) - } else { - false - } - } - Type::Interface { .. } - | Type::Class { .. } - | Type::Constant(_) - | Type::Narrowed { .. } - | Type::FunctionReference(_) - | Type::Object(_) - | Type::SpecialObject(_) => false, - } - } - - #[allow(clippy::match_like_matches_macro)] - #[must_use] - pub fn _type_is_error(ty: TypeId, types: &TypeStore) -> bool { - if ty == TypeId::UNIMPLEMENTED_ERROR_TYPE { - true - } else if let Type::RootPolyType(PolyNature::Error(_)) = types.get_type_by_id(ty) { - true - } else { - false - } - } - - /// TODO want to skip mapped generics because that would break subtyping - #[must_use] - pub fn get_conditional(ty: TypeId, types: &TypeStore) -> Option<(TypeId, TypeId, TypeId)> { - match types.get_type_by_id(ty) { - Type::Constructor(crate::types::Constructor::ConditionalResult { - condition, - truthy_result, - otherwise_result, - result_union: _, - }) => Some((*condition, *truthy_result, *otherwise_result)), - Type::Or(left, right) => Some((TypeId::OPEN_BOOLEAN_TYPE, *left, *right)), - // For reasons ! - Type::RootPolyType(PolyNature::MappedGeneric { .. }) => None, - _ => { - if let Some(constraint) = get_constraint(ty, types) { - get_conditional(constraint, types) - } else { - None - } - } - } - } - - /// TODO wip - #[must_use] - pub fn is_pseudo_continous((ty, generics): (TypeId, GenericChain), types: &TypeStore) -> bool { - if let TypeId::NUMBER_TYPE | TypeId::STRING_TYPE = ty { - true - } else if let Some(arg) = generics.as_ref().and_then(|args| args.get_single_argument(ty)) { - is_pseudo_continous((arg, generics), types) - } else { - let ty = types.get_type_by_id(ty); - if let Type::Or(left, right) = ty { - is_pseudo_continous((*left, generics), types) - || is_pseudo_continous((*right, generics), types) - } else if let Type::And(left, right) = ty { - is_pseudo_continous((*left, generics), types) - && is_pseudo_continous((*right, generics), types) - } else if let Type::RootPolyType(PolyNature::MappedGeneric { extends, .. }) = ty { - is_pseudo_continous((*extends, generics), types) - } else { - false - } - } - } - - #[must_use] - pub fn is_inferrable_type(ty: TypeId) -> bool { - matches!(ty, TypeId::ANY_TO_INFER_TYPE | TypeId::OBJECT_TYPE) - } - - /// For quick checking - #[must_use] - 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: true }, - 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 - } - } - - /// Temp fix for equality of narrowing stuff - pub fn is_not_of_constant(ty: TypeId, types: &TypeStore) -> bool { - if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: TypeId::NOT_RESTRICTION, - arguments, - }) = types.get_type_by_id(ty) - { - let inner = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap(); - types.get_type_by_id(inner).is_constant() - } else { - false - } - } -} diff --git a/checker/src/types/printing.rs b/checker/src/types/printing.rs index 63cf693a..81a2f1ca 100644 --- a/checker/src/types/printing.rs +++ b/checker/src/types/printing.rs @@ -9,7 +9,8 @@ use crate::{ calling::ThisValue, functions::{FunctionBehavior, FunctionEffect}, generics::generic_type_arguments::GenericArguments, - get_array_length, get_constraint, get_simple_value, + get_constraint, get_simple_property_value, + helpers::get_array_length, properties::{get_properties_on_single_type, AccessMode, PropertyKey, Publicity}, Constructor, GenericChainLink, ObjectNature, PartiallyAppliedGenerics, TypeExtends, }, @@ -57,24 +58,6 @@ pub fn print_type_with_type_arguments( buf } -pub fn print_inner_template_literal_type_into_buf<C: InformationChain>( - ty: TypeId, - buf: &mut String, - cycles: &mut HashSet<TypeId>, - args: GenericChain, - types: &TypeStore, - info: &C, - debug: bool, -) { - if let Type::Constant(cst) = types.get_type_by_id(ty) { - buf.push_str(&cst.as_js_string()); - } else { - buf.push_str("${"); - print_type_into_buf(ty, buf, cycles, args, types, info, debug); - buf.push('}'); - } -} - /// Recursion safe + reuses buffer pub fn print_type_into_buf<C: InformationChain>( ty: TypeId, @@ -96,27 +79,35 @@ pub fn print_type_into_buf<C: InformationChain>( let r#type = types.get_type_by_id(ty); match r#type { Type::And(a, b) => { + // let value = crate::types::intrinsics::get_range_and_mod_class(ty, types); + // if value.0.is_some() || value.1.is_some() { + // crate::utilities::notify!("{:?}", value); + // } + print_type_into_buf(*a, buf, cycles, args, types, info, debug); buf.push_str(" & "); print_type_into_buf(*b, buf, cycles, args, types, info, debug); } Type::Or(a, b) => { + // let value = crate::types::intrinsics::get_range_and_mod_class(ty, types); + // if value.0.is_some() || value.1.is_some() { + // crate::utilities::notify!("{:?}", value); + // } + print_type_into_buf(*a, buf, cycles, args, types, info, debug); buf.push_str(" | "); print_type_into_buf(*b, buf, cycles, args, types, info, debug); } Type::Narrowed { narrowed_to, from } => { if debug { - buf.push_str("(narrowed from "); - print_type_into_buf(*from, buf, cycles, args, types, info, debug); - buf.push_str(") "); + write!(buf, "(narrowed from {from:?}) ").unwrap(); } print_type_into_buf(*narrowed_to, buf, cycles, args, types, info, debug); } Type::RootPolyType(nature) => match nature { PolyNature::MappedGeneric { name, extends } => { if debug { - write!(buf, "[mg {} {}] ", ty.0, name).unwrap(); + write!(buf, "(mg {} {}) ", ty.0, name).unwrap(); } crate::utilities::notify!("args={:?}", args); if let Some(crate::types::CovariantContribution::String(property)) = @@ -131,13 +122,13 @@ pub fn print_type_into_buf<C: InformationChain>( | PolyNature::StructureGeneric { name, extends: _ } => { if debug { if let PolyNature::FunctionGeneric { .. } = nature { - write!(buf, "[fg {} {}] ", ty.0, name).unwrap(); + write!(buf, "(fg {} {}) ", ty.0, name).unwrap(); } } if let Some(arg) = args.and_then(|args| args.get_argument_covariant(ty)) { use crate::types::CovariantContribution; if debug { - buf.push_str(" (specialised with) "); + buf.push_str("(specialised with) "); } match arg { CovariantContribution::TypeId(id) => { @@ -159,7 +150,7 @@ pub fn print_type_into_buf<C: InformationChain>( if let PolyNature::FunctionGeneric { extends, .. } = nature { print_type_into_buf(*extends, buf, cycles, args, types, info, debug); } else { - write!(buf, "[sg {}] ", ty.0).unwrap(); + write!(buf, "(sg {})", ty.0).unwrap(); } } buf.push_str(name); @@ -167,7 +158,7 @@ pub fn print_type_into_buf<C: InformationChain>( } PolyNature::InferGeneric { name, extends } => { if debug { - write!(buf, "[IG {}] @ ", ty.0).unwrap(); + write!(buf, "(IG {}) @ ", ty.0).unwrap(); } buf.push_str("infer "); buf.push_str(name); @@ -179,19 +170,19 @@ pub fn print_type_into_buf<C: InformationChain>( PolyNature::FreeVariable { based_on: to, .. } => { if debug { // FV = free variable - write!(buf, "[FV {}] @ ", ty.0).unwrap(); + write!(buf, "(FV {}) @ ", ty.0).unwrap(); } print_type_into_buf(*to, buf, cycles, args, types, info, debug); } PolyNature::Parameter { fixed_to: to } => { if debug { - write!(buf, "[param {}] @ ", ty.0).unwrap(); + write!(buf, "(param {}) @ ", ty.0).unwrap(); } print_type_into_buf(*to, buf, cycles, args, types, info, debug); } PolyNature::Open(to) => { if debug { - write!(buf, "[open {}] ", ty.0).unwrap(); + write!(buf, "(open {}) ", ty.0).unwrap(); } print_type_into_buf(*to, buf, cycles, args, types, info, debug); } @@ -212,31 +203,30 @@ pub fn print_type_into_buf<C: InformationChain>( } PolyNature::CatchVariable(constraint) => { if debug { - write!(buf, "[catch variable {ty:?}] ").unwrap(); + write!(buf, "(CV {ty:?}) ").unwrap(); } print_type_into_buf(*constraint, buf, cycles, args, types, info, debug); } }, Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on, arguments }) => { - // TypeId::INCLUSIVE_RANGE | - if let TypeId::EXCLUSIVE_RANGE = *on { - // let inclusive = *on == TypeId::INCLUSIVE_RANGE; - let floor = - arguments.get_structure_restriction(TypeId::NUMBER_FLOOR_GENERIC).unwrap(); - let ceiling = - arguments.get_structure_restriction(TypeId::NUMBER_CEILING_GENERIC).unwrap(); - if let TypeId::NEG_INFINITY = floor { - buf.push_str("LessThan<"); - print_type_into_buf(ceiling, buf, cycles, args, types, info, debug); - buf.push('>'); - return; - } else if let TypeId::INFINITY = ceiling { - buf.push_str("GreaterThan<"); - print_type_into_buf(floor, buf, cycles, args, types, info, debug); - buf.push('>'); - return; - } - } + // if let TypeId::Great = *on { + // // let inclusive = *on == TypeId::INCLUSIVE_RANGE; + // let floor = + // arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(); + // let ceiling = + // arguments.get_structure_restriction(TypeId::NUMBER_CEILING_GENERIC).unwrap(); + // if let TypeId::NEG_INFINITY = floor { + // buf.push_str("LessThan<"); + // print_type_into_buf(ceiling, buf, cycles, args, types, info, debug); + // buf.push('>'); + // return; + // } else if let TypeId::INFINITY = ceiling { + // buf.push_str("GreaterThan<"); + // print_type_into_buf(floor, buf, cycles, args, types, info, debug); + // buf.push('>'); + // return; + // } + // } if debug { write!(buf, "SG({:?})(", ty.0).unwrap(); @@ -428,6 +418,18 @@ pub fn print_type_into_buf<C: InformationChain>( print_type_into_buf(*result, buf, cycles, args, types, info, debug); } } + Constructor::BinaryOperator { + lhs: _, + operator: crate::features::operations::MathematicalOrBitwiseOperation::Modulo, + rhs, + result: _, + } if matches!(types.get_type_by_id(*rhs), Type::Constant(_)) => { + if let Type::Constant(crate::Constant::Number(num)) = types.get_type_by_id(*rhs) { + write!(buf, "ExclusiveRange<-{num}, {num}>").unwrap(); + } else { + unreachable!() + } + } constructor if debug => match constructor { Constructor::BinaryOperator { lhs, operator, rhs, result: _ } => { print_type_into_buf(*lhs, buf, cycles, args, types, info, debug); @@ -456,7 +458,7 @@ pub fn print_type_into_buf<C: InformationChain>( } Constructor::Image { on: _, with: _, result } => { // TODO arguments - write!(buf, "[func result {}] (*args*)", ty.0).unwrap(); + write!(buf, "(func result {}) (*args*)", ty.0).unwrap(); buf.push_str(" -> "); print_type_into_buf(*result, buf, cycles, args, types, info, debug); } @@ -488,23 +490,26 @@ pub fn print_type_into_buf<C: InformationChain>( } }, constructor => { - if let Constructor::BinaryOperator { result: result_ty, lhs, rhs, .. } = constructor + if let Constructor::BinaryOperator { result: TypeId::STRING_TYPE, .. } = constructor { - if *result_ty != TypeId::NUMBER_TYPE - && !matches!( - types.get_type_by_id(*result_ty), - Type::PartiallyAppliedGenerics(_) | Type::RootPolyType(_) - ) { + let slice = + crate::types::helpers::TemplatelLiteralExpansion::from_type(ty, types); + if let Some(single) = slice.as_single_string() { + buf.push('"'); + buf.push_str(single); + buf.push('"'); + } else { buf.push('`'); - print_inner_template_literal_type_into_buf( - *lhs, buf, cycles, args, types, info, debug, - ); - print_inner_template_literal_type_into_buf( - *rhs, buf, cycles, args, types, info, debug, - ); + for (s, ty) in &slice.parts { + buf.push_str(s); + buf.push_str("${"); + print_type_into_buf(*ty, buf, cycles, args, types, info, debug); + buf.push('}'); + } + buf.push_str(&slice.rest); buf.push('`'); - return; } + return; } let base = get_constraint(ty, types).unwrap(); print_type_into_buf(base, buf, cycles, args, types, info, debug); @@ -514,12 +519,12 @@ pub fn print_type_into_buf<C: InformationChain>( | Type::Interface { name, parameters: _, .. } | Type::AliasTo { to: _, name, parameters: _ }) => { if debug { - write!(buf, "{name}#{}", ty.0).unwrap(); + write!(buf, "{name}#{} ", ty.0).unwrap(); if let Type::AliasTo { to, .. } = t { - buf.push_str(" = "); + buf.push_str("= "); print_type_into_buf(*to, buf, cycles, args, types, info, debug); } else if let Type::Class { .. } = t { - buf.push_str(" (class)"); + buf.push_str("(class)"); } } else { buf.push_str(name); @@ -567,7 +572,7 @@ pub fn print_type_into_buf<C: InformationChain>( if debug { let kind = if matches!(r#type, Type::FunctionReference(_)) { "ref" } else { "" }; - write!(buf, "[func{kind} #{}, kind {:?}, effect ", ty.0, func.behavior).unwrap(); + write!(buf, "(func{kind} #{}, kind {:?}, effect ", ty.0, func.behavior).unwrap(); if let FunctionEffect::SideEffects { events: _, free_variables, @@ -585,7 +590,7 @@ pub fn print_type_into_buf<C: InformationChain>( buf.push_str(", this "); print_type_into_buf(*p, buf, cycles, args, types, info, debug); } - buf.push_str("] = "); + buf.push_str(") = "); } if let Some(ref parameters) = func.type_parameters { buf.push('<'); @@ -639,9 +644,9 @@ pub fn print_type_into_buf<C: InformationChain>( Type::Object(kind) => { if debug { if let ObjectNature::RealDeal = kind { - write!(buf, "[obj {}] ", ty.0).unwrap(); + write!(buf, "(obj {}) ", ty.0).unwrap(); } else { - write!(buf, "[aol {}] ", ty.0).unwrap(); + write!(buf, "(anom {}) ", ty.0).unwrap(); } } let prototype = @@ -655,8 +660,12 @@ pub fn print_type_into_buf<C: InformationChain>( if i != 0 { buf.push_str(", "); } - let value = - get_simple_value(info, ty, &PropertyKey::from_usize(i), types); + let value = get_simple_property_value( + info, + ty, + &PropertyKey::from_usize(i), + types, + ); if let Some(value) = value { print_type_into_buf(value, buf, cycles, args, types, info, debug); diff --git a/checker/src/types/properties/access.rs b/checker/src/types/properties/access.rs index 84d52df1..6b17a5c7 100644 --- a/checker/src/types/properties/access.rs +++ b/checker/src/types/properties/access.rs @@ -10,7 +10,7 @@ use crate::{ contributions::CovariantContribution, generic_type_arguments::GenericArguments, }, get_constraint, - helpers::{get_conditional, is_inferrable_type, is_pseudo_continous}, + helpers::{get_type_as_conditional, is_inferrable_type, is_pseudo_continous}, logical::{ BasedOnKey, Invalid, Logical, LogicalOrValid, NeedsCalculation, PossibleLogical, PropertyOn, @@ -336,7 +336,8 @@ pub(crate) fn get_property_unbound( Type::Constructor(crate::types::Constructor::ConditionalResult { .. }) | Type::Or(..) => { let (condition, truthy_result, otherwise_result) = - get_conditional(on, types).expect("case above != get_conditional"); + get_type_as_conditional(on, types) + .expect("case above != get_type_as_conditional"); if require_both_logical { let left = get_property_on_type_unbound( @@ -372,14 +373,16 @@ pub(crate) fn get_property_unbound( types, ); if left.is_err() && right.is_err() { + crate::utilities::notify!( + "One side invalid {:?}", + (left.is_err(), right.is_err()) + ); Err(Invalid(on)) } else { - let left = left.unwrap_or(LogicalOrValid::Logical(Logical::Pure( - PropertyValue::Deleted, - ))); - let right = right.unwrap_or(LogicalOrValid::Logical(Logical::Pure( - PropertyValue::Deleted, - ))); + const DELETED_PROPERTY: LogicalOrValid<PropertyValue> = + LogicalOrValid::Logical(Logical::Pure(PropertyValue::Deleted)); + let left = left.unwrap_or(DELETED_PROPERTY); + let right = right.unwrap_or(DELETED_PROPERTY); Ok(Logical::Or { condition, left: Box::new(left), right: Box::new(right) } .into()) } @@ -421,7 +424,7 @@ pub(crate) fn get_property_unbound( } Type::Object(ObjectNature::RealDeal) => { let object_constraint_structure_generics = - crate::types::get_structure_arguments_based_on_object_constraint( + crate::types::helpers::get_structure_arguments_based_on_object_constraint( on, info_chain, types, ); @@ -430,17 +433,6 @@ pub(crate) fn get_property_unbound( .find_map(|facts| facts.prototypes.get(&on)) .copied(); - let generics = if let Some(generics) = object_constraint_structure_generics { - // TODO clone - Some(generics.clone()) - } else if prototype - .is_some_and(|prototype| types.lookup_generic_map.contains_key(&prototype)) - { - Some(GenericArguments::LookUp { on }) - } else { - None - }; - let on_self = resolver( (on, on_type_arguments), (publicity, under, under_type_arguments), @@ -448,31 +440,39 @@ pub(crate) fn get_property_unbound( types, ); - let result = if let (Some(prototype), None) = (prototype, &on_self) { - resolver( + if let Some(property) = on_self { + let generics = if let Some(generics) = object_constraint_structure_generics { + // TODO clone + Some(generics.clone()) + } else if prototype + .is_some_and(|prototype| types.lookup_generic_map.contains_key(&prototype)) + { + Some(GenericArguments::LookUp { on }) + } else { + None + }; + + let property = wrap(property); + let property = if let Some(ref generics) = generics { + // TODO clone + Logical::Implies { on: Box::new(property), antecedent: generics.clone() } + } else { + property + }; + Ok(LogicalOrValid::Logical(property)) + } else if let Some(prototype) = prototype { + crate::utilities::notify!("{:?}", types.get_type_by_id(prototype)); + + get_property_on_type_unbound( (prototype, on_type_arguments), (publicity, under, under_type_arguments), + require_both_logical, info_chain, types, ) } else { - on_self - }; - - // crate::utilities::notify!("result={:?}", result); - - result - .map(wrap) - .map(|result| { - if let Some(ref generics) = generics { - // TODO clone - Logical::Implies { on: Box::new(result), antecedent: generics.clone() } - } else { - result - } - }) - .map(LogicalOrValid::Logical) - .ok_or(Invalid(on)) + Err(Invalid(on)) + } } Type::Interface { extends, .. } => resolver( (on, on_type_arguments), @@ -543,14 +543,12 @@ pub(crate) fn get_property_unbound( } } Type::Constant(Constant::String(s)) if under.is_equal_to("length") => { - // TODO temp TypeId::NUMBER_FLOOR_GENERIC for slice member + // TODO temp TypeId::NUMBER_GENERIC for slice member let count = s.chars().count(); Ok(Logical::BasedOnKey(BasedOnKey::Left { - value: Box::new(Logical::Pure(PropertyValue::Value( - TypeId::NUMBER_FLOOR_GENERIC, - ))), + value: Box::new(Logical::Pure(PropertyValue::Value(TypeId::NUMBER_GENERIC))), key_arguments: crate::Map::from_iter([( - TypeId::NUMBER_FLOOR_GENERIC, + TypeId::NUMBER_GENERIC, (CovariantContribution::Number(count as f64), 0), )]), }) @@ -604,7 +602,9 @@ pub(crate) fn get_property_unbound( // if *key == TypeId::ERROR_TYPE { // return Err(MissingOrToCalculate::Error); // } else - if let Some((condition, truthy_result, otherwise_result)) = get_conditional(key, types) { + if let Some((condition, truthy_result, otherwise_result)) = + get_type_as_conditional(key, types) + { let left = get_property_unbound( (on, on_type_arguments), (publicity, &PropertyKey::Type(truthy_result), under_type_arguments), @@ -736,9 +736,9 @@ pub(crate) fn get_property<B: CallCheckingBehavior>( types, ); - { - crate::utilities::notify!("Access {:?} result {:?}", under, result); - } + // { + // crate::utilities::notify!("Access {:?} result {:?}", under, result); + // } match result { Ok(LogicalOrValid::Logical(logical)) => { @@ -1096,7 +1096,7 @@ fn resolve_property_on_logical<B: CallCheckingBehavior>( ) } BasedOnKey::Right(property_on) => { - crate::utilities::notify!("{:?}", generics); + crate::utilities::notify!("generics are {:?}", generics); let result = property_on.get_on(generics, environment, types)?; // { diff --git a/checker/src/types/properties/assignment.rs b/checker/src/types/properties/assignment.rs index 31538e68..14eb510a 100644 --- a/checker/src/types/properties/assignment.rs +++ b/checker/src/types/properties/assignment.rs @@ -9,8 +9,9 @@ use crate::{ types::{ calling::{CallingDiagnostics, CallingOutput, SynthesisedArgument}, get_constraint, + helpers::tuple_like, logical::{BasedOnKey, Logical, LogicalOrValid, NeedsCalculation}, - tuple_like, Constructor, GenericChain, PartiallyAppliedGenerics, TypeStore, + Constructor, GenericChain, PartiallyAppliedGenerics, TypeStore, }, Environment, Type, TypeId, }; @@ -129,7 +130,10 @@ pub fn set_property<B: CallCheckingBehavior>( environment, types, ); + if let SubTypeResult::IsNotSubType(reason) = result { + crate::utilities::notify!("Here {:?} {:?}", property_constraint, new); + let is_modifying_tuple_length = under.is_equal_to("length") && tuple_like(object_constraint, types, environment); @@ -203,7 +207,7 @@ pub fn set_property<B: CallCheckingBehavior>( result_union: _, }) = types.get_type_by_id(on) { - crate::utilities::notify!("Here"); + crate::utilities::notify!("Cascading assigment bc of conditional result"); let truthy = *truthy_result; let otherwise_result = *otherwise_result; @@ -226,6 +230,13 @@ pub fn set_property<B: CallCheckingBehavior>( ); } + // Important that this goes below the condition above + let on = if let Type::Narrowed { narrowed_to, .. } = types.get_type_by_id(on) { + *narrowed_to + } else { + on + }; + // IMPORTANT: THIS ALSO CAPTURES POLY CONSTRAINTS let current_property = get_property_unbound((on, None), (publicity, under, None), false, environment, types); @@ -263,12 +274,13 @@ pub fn set_property<B: CallCheckingBehavior>( types, ), } - } else if get_constraint(on, types).is_some() { - Err(SetPropertyError::AssigningToNonExistent { - property: PropertyKeyRepresentation::new(under, environment, types), - position, - }) } else { + if get_constraint(on, types).is_some() { + return Err(SetPropertyError::AssigningToNonExistent { + property: PropertyKeyRepresentation::new(under, environment, types), + position, + }); + } // Sealed & no extensions check for NEW property (frozen case covered above) { if object_protection.is_some() { diff --git a/checker/src/types/properties/mod.rs b/checker/src/types/properties/mod.rs index dd608419..804c740b 100644 --- a/checker/src/types/properties/mod.rs +++ b/checker/src/types/properties/mod.rs @@ -186,7 +186,7 @@ impl<'a> PropertyKey<'a> { } /// For getting `length` and stuff -pub(crate) fn get_simple_value( +pub(crate) fn get_simple_property_value( ctx: &impl InformationChain, on: TypeId, property: &PropertyKey, @@ -508,10 +508,10 @@ pub(crate) fn key_matches( ); let contributions = state.contributions.unwrap(); - crate::utilities::notify!( - "Here contributions {:?}", - &contributions.staging_contravariant - ); + // crate::utilities::notify!( + // "Here contributions {:?}", + // &contributions.staging_contravariant + // ); (result.is_subtype(), contributions.staging_contravariant) } diff --git a/checker/src/types/store.rs b/checker/src/types/store.rs index f6bcb3bb..263ced34 100644 --- a/checker/src/types/store.rs +++ b/checker/src/types/store.rs @@ -148,30 +148,20 @@ impl Default for TypeStore { name: "T".into(), extends: TypeId::NUMBER_TYPE, }), - Type::RootPolyType(PolyNature::StructureGeneric { - name: "U".into(), - extends: TypeId::NUMBER_TYPE, - }), Type::AliasTo { + name: "GreaterThan".into(), + parameters: Some(vec![TypeId::NUMBER_GENERIC]), to: TypeId::NUMBER_TYPE, - name: "InclusiveRange".into(), - parameters: Some(vec![ - TypeId::NUMBER_FLOOR_GENERIC, - TypeId::NUMBER_CEILING_GENERIC, - ]), }, Type::AliasTo { + name: "LessThan".into(), + parameters: Some(vec![TypeId::NUMBER_GENERIC]), to: TypeId::NUMBER_TYPE, - name: "ExclusiveRange".into(), - parameters: Some(vec![ - TypeId::NUMBER_FLOOR_GENERIC, - TypeId::NUMBER_CEILING_GENERIC, - ]), }, Type::AliasTo { - to: TypeId::NUMBER_TYPE, name: "MultipleOf".into(), - parameters: Some(vec![TypeId::NUMBER_FLOOR_GENERIC]), + parameters: Some(vec![TypeId::NUMBER_GENERIC]), + to: TypeId::NUMBER_TYPE, }, // Intermediate for the below Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { @@ -182,26 +172,25 @@ impl Default for TypeStore { )])), }), Type::And(TypeId::NUMBER_TYPE, TypeId::NOT_NOT_A_NUMBER), - // TODO WIP Type::AliasTo { name: "Literal".into(), - to: TypeId::T_TYPE, parameters: Some(vec![TypeId::T_TYPE]), + to: TypeId::T_TYPE, }, Type::AliasTo { name: "Exclusive".into(), - to: TypeId::T_TYPE, parameters: Some(vec![TypeId::T_TYPE]), + to: TypeId::T_TYPE, }, Type::AliasTo { name: "Not".into(), - to: TypeId::ANY_TYPE, parameters: Some(vec![TypeId::T_TYPE]), + to: TypeId::ANY_TYPE, }, Type::AliasTo { name: "CaseInsensitive".into(), - to: TypeId::STRING_TYPE, parameters: Some(vec![TypeId::STRING_GENERIC]), + to: TypeId::STRING_TYPE, }, Type::RootPolyType(PolyNature::Open(TypeId::BOOLEAN_TYPE)), Type::RootPolyType(PolyNature::Open(TypeId::NUMBER_TYPE)), @@ -302,46 +291,39 @@ impl TypeStore { 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<TypeId, ()> { + // intersection. Does not calculate disjoint + pub fn new_and_type(&mut self, lhs: TypeId, rhs: TypeId) -> TypeId { + // string & string = string if lhs == rhs { - return Ok(lhs); - } - - let left_ty = self.get_type_by_id(lhs); - let right_ty = self.get_type_by_id(rhs); - - // TODO more cases - if let (Type::Constant(l), Type::Constant(r)) = (left_ty, right_ty) { - if l != r { - return Err(()); - } - } else if left_ty.is_nominal() && right_ty.is_nominal() { - return Err(()); + return lhs; } - // (left and right) distributivity. - let result = if let Type::Or(or_lhs, or_rhs) = left_ty { + // (left and right) distributivity & some other reductions on singleton types bc why not + // TODO sort intrinsics? + let lhs_type = self.get_type_by_id(lhs); + let rhs_type = self.get_type_by_id(rhs); + if let Type::Or(or_lhs, or_rhs) = lhs_type { let (or_lhs, or_rhs) = (*or_lhs, *or_rhs); - let new_lhs = self.new_and_type(or_lhs, rhs)?; - let new_rhs = self.new_and_type(or_rhs, rhs)?; + let new_lhs = self.new_and_type(or_lhs, rhs); + let new_rhs = self.new_and_type(or_rhs, rhs); self.new_or_type(new_lhs, new_rhs) - } else if let Type::Or(or_lhs, or_rhs) = right_ty { + } else if let Type::Or(or_lhs, or_rhs) = rhs_type { let (or_lhs, or_rhs) = (*or_lhs, *or_rhs); - let new_lhs = self.new_and_type(lhs, or_lhs)?; - let new_rhs = self.new_and_type(lhs, or_rhs)?; + let new_lhs = self.new_and_type(lhs, or_lhs); + let new_rhs = self.new_and_type(lhs, or_rhs); self.new_or_type(new_lhs, new_rhs) + } else if let Type::Constant(_) = lhs_type { + lhs + } else if let Type::Constant(_) = rhs_type { + rhs + } else if let Type::And(rhs_lhs, rhs_rhs) = rhs_type { + let (rhs_lhs, rhs_rhs) = (*rhs_lhs, *rhs_rhs); + let lhs = self.new_and_type(lhs, rhs_lhs); + self.new_and_type(lhs, rhs_rhs) } else { let ty = Type::And(lhs, rhs); self.register_type(ty) - }; - - Ok(result) - } - - /// TODO temp - #[must_use] - pub fn into_vec_temp(self) -> Vec<(TypeId, Type)> { - self.types.into_iter().enumerate().map(|(idx, ty)| (TypeId(idx as u16), ty)).collect() + } } /// From something like: let a: number => string. Rather than a actual function @@ -554,7 +536,34 @@ impl TypeStore { self.register_type(Type::RootPolyType(PolyNature::Open(base))) } + /// Will provide origin rewriting as well pub fn new_narrowed(&mut self, from: TypeId, narrowed_to: TypeId) -> TypeId { + let from_ty = self.get_type_by_id(from); + let new_constraint = self.get_type_by_id(narrowed_to); + let (from, existing) = if let Type::Narrowed { from, narrowed_to } = from_ty { + (*from, Some(*narrowed_to)) + } else { + (from, None) + }; + // temp fix for adding things. + let narrowed_to = if let ( + Some(existing), + Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: + TypeId::GREATER_THAN + | TypeId::LESS_THAN + | TypeId::MULTIPLE_OF + | TypeId::NOT_RESTRICTION, + arguments: _, + }), + ) = (existing, new_constraint) + { + self.new_and_type(existing, narrowed_to) + } else { + // crate::utilities::notify!("{:?}", from_ty); + narrowed_to + }; + self.register_type(Type::Narrowed { from, narrowed_to }) } @@ -633,4 +642,13 @@ impl TypeStore { pub(crate) fn new_key_of(&mut self, of: TypeId) -> TypeId { self.register_type(Type::Constructor(Constructor::KeyOf(of))) } + + /// TODO temp for debugging + pub fn user_types(&self) -> impl Iterator<Item = (TypeId, &Type)> + '_ { + self.types + .iter() + .enumerate() + .skip(TypeId::INTERNAL_TYPE_COUNT) + .map(|(idx, ty)| (TypeId(idx as u16), ty)) + } } diff --git a/checker/src/types/subtyping.rs b/checker/src/types/subtyping.rs index b1ae45e0..1fe9fa0c 100644 --- a/checker/src/types/subtyping.rs +++ b/checker/src/types/subtyping.rs @@ -6,7 +6,7 @@ use crate::{ context::{GeneralContext, InformationChain}, features::{ objects::{self, SpecialObject}, - operations::MathematicalAndBitwise, + operations::MathematicalOrBitwiseOperation, }, Constant, Environment, PropertyValue, TypeId, }; @@ -267,12 +267,11 @@ pub(crate) fn type_is_subtype_with_generics( types, ); // Temp fix for narrowing constants - crate::utilities::notify!("{:?}", super::helpers::is_not_of_constant(*right, types)); + // crate::utilities::notify!("{:?}", super::helpers::is_not_of_constant(*right, types)); // SubTypeResult::IsNotSubType(_) return if let (Type::Narrowed { from, .. }, _, true) = (subtype, &result, super::helpers::is_not_of_constant(*right, types)) { - crate::utilities::notify!("Here"); type_is_subtype_with_generics( (base_type, base_type_arguments), (*from, ty_structure_arguments), @@ -308,61 +307,15 @@ pub(crate) fn type_is_subtype_with_generics( left_result }; } - // Type::And(left, right) => { - // let left_is_operator_right_is_not = - // supertype.is_operator(); - - // // edge cases on edge cases - // // If any of these are true. Then do not perform constraint argument lookup - // let edge_case = left_is_operator_right_is_not; - - // if !edge_case { - - // let right = *right; - // let left_result = type_is_subtype_with_generics( - // (base_type, base_type_arguments), - // (*left, ty_structure_arguments), - // state, - // information, - // types, - // ); - - // return if let SubTypeResult::IsSubType = left_result { - // left_result - // } else { - // type_is_subtype_with_generics( - // (base_type, base_type_arguments), - // (right, ty_structure_arguments), - // state, - // information, - // types, - // ) - // }; - // } - // } - // TODO others Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on: on @ TypeId::NOT_RESTRICTION, - arguments, + arguments: _, }) => { match *on { TypeId::NOT_RESTRICTION => { - let inner = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap(); - // https://leanprover-community.github.io/mathlib4_docs/Mathlib/Data/Set/Basic.html#Set.subset_compl_comm -> https://leanprover-community.github.io/mathlib4_docs/Mathlib/Data/Set/Basic.html#Set.subset_compl_iff_disjoint_left - - let result = super::disjoint::types_are_disjoint( - base_type, - inner, - &mut state.already_checked, - information, - types, - ); - // crate::utilities::notify!("Here {:?}", (&result, inner)); - return if result { - SubTypeResult::IsSubType - } else { - SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) - }; + // This only happens when subtype ∪ supertype = `any`. This is only true when + // one is `any`. `Not<any>` is already `never` and `supertype = any` is handled above + return SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch); } _ => unreachable!(), } @@ -378,7 +331,7 @@ pub(crate) fn type_is_subtype_with_generics( }; } - crate::utilities::notify!("Looking for {:?} with {:?}", ty, ty_structure_arguments); + // crate::utilities::notify!("Looking for {:?} with {:?}", ty, ty_structure_arguments); if let Some(arg) = ty_structure_arguments.and_then(|tas| tas.get_argument_covariant(ty)) { @@ -438,20 +391,20 @@ pub(crate) fn type_is_subtype_with_generics( // If lhs is not operator unless argument is operator // if !T::INFER_GENERICS && ty_structure_arguments.is_none() { - let right_arg = get_constraint(ty, types).unwrap(); + let right_constraint = get_constraint(ty, types).unwrap(); - crate::utilities::notify!( - "edge case {:?}", - ( - types.get_type_by_id(ty), - types.get_type_by_id(right_arg), - types.get_type_by_id(right_arg).is_operator() - ) - ); + // crate::utilities::notify!( + // "RHS is parameter, edge case results to {:?}", + // ( + // types.get_type_by_id(ty), + // types.get_type_by_id(right_constraint), + // types.get_type_by_id(right_constraint).is_operator() + // ) + // ); // This is important that LHS is not operator let left_is_operator_right_is_not = - supertype.is_operator() && !types.get_type_by_id(right_arg).is_operator(); + supertype.is_operator() && !types.get_type_by_id(right_constraint).is_operator(); // edge cases on edge cases // If any of these are true. Then do not perform constraint argument lookup @@ -460,12 +413,14 @@ pub(crate) fn type_is_subtype_with_generics( supertype, Type::RootPolyType(rpt) if rpt.is_substitutable() - ) || matches!(supertype, Type::Constructor(..)); - + ) || matches!(supertype, Type::Constructor(..)) + || base_type_arguments + .and_then(|args| args.get_argument_covariant(base_type)) + .is_some(); if !edge_case { let result = type_is_subtype_with_generics( (base_type, base_type_arguments), - (right_arg, ty_structure_arguments), + (right_constraint, ty_structure_arguments), state, information, types, @@ -505,7 +460,7 @@ pub(crate) fn type_is_subtype_with_generics( } } else { // TODO what about if LHS has inferred constraint - crate::utilities::notify!("Constant {:?} against RHS {:#?}", lhs, subtype); + // crate::utilities::notify!("Constant {:?} against RHS {:#?}", lhs, subtype); SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) } } @@ -692,10 +647,8 @@ pub(crate) fn type_is_subtype_with_generics( // TODO temp fix if let Type::Constructor(c) = subtype { crate::utilities::notify!("TODO right hand side maybe okay"); - if let Some(to) = c.get_base() { - if to == base_type { - return SubTypeResult::IsSubType; - } + if c.get_constraint() == base_type { + return SubTypeResult::IsSubType; } } if let PolyNature::FunctionGeneric { .. } = nature { @@ -845,10 +798,13 @@ pub(crate) fn type_is_subtype_with_generics( // Ezno intrinsic TypeId::LITERAL_RESTRICTION => { let inner = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap(); - return if let Type::Constant(rhs_constant) = subtype { + return if let Type::Constant(_) + | Type::Object(ObjectNature::RealDeal) + | Type::SpecialObject(..) = subtype + { type_is_subtype_with_generics( (inner, base_type_arguments), - (rhs_constant.get_backing_type(), ty_structure_arguments), + (ty, ty_structure_arguments), state, information, types, @@ -881,7 +837,7 @@ pub(crate) fn type_is_subtype_with_generics( } TypeId::MULTIPLE_OF => { let argument = - arguments.get_structure_restriction(TypeId::NUMBER_FLOOR_GENERIC).unwrap(); + arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(); let right_multiple = crate::types::intrinsics::get_multiple(ty, types); return if let ( @@ -901,18 +857,93 @@ pub(crate) fn type_is_subtype_with_generics( SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) }; } - TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE => { - return if let (super_range, Some(sub_range)) = ( - intrinsics::get_range(base_type, types).unwrap(), - intrinsics::get_range(ty, types), - ) { - if sub_range.contained_in(super_range) { + TypeId::GREATER_THAN => { + let argument = + arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(); + let argument_type = types.get_type_by_id(argument); + return if let ( + Type::Constant(Constant::Number(value)), + Type::Constant(Constant::Number(subtype_number)), + ) = (argument_type, subtype) + { + if subtype_number > value { + SubTypeResult::IsSubType + } else { + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + } + } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::GREATER_THAN, + arguments, + }) = subtype + { + let subtype_argument = + arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(); + // Transitivity + if argument == subtype_argument { + SubTypeResult::IsSubType + } else { + let subtype_argument = types.get_type_by_id(subtype_argument); + if let ( + Type::Constant(Constant::Number(subtype_number)), + Type::Constant(Constant::Number(value)), + ) = (argument_type, subtype_argument) + { + if subtype_number > value { + SubTypeResult::IsSubType + } else { + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + } + } else { + crate::utilities::notify!("Here"); + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + } + } + } else { + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + }; + } + TypeId::LESS_THAN => { + let argument = + arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(); + let argument_type = types.get_type_by_id(argument); + return if let ( + Type::Constant(Constant::Number(value)), + Type::Constant(Constant::Number(subtype_number)), + ) = (argument_type, subtype) + { + if subtype_number < value { SubTypeResult::IsSubType } else { SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) } + } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::GREATER_THAN, + arguments, + }) = subtype + { + let subtype_argument = + arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(); + // Transitivity + if argument == subtype_argument { + SubTypeResult::IsSubType + } else { + let subtype_argument = types.get_type_by_id(subtype_argument); + if let ( + Type::Constant(Constant::Number(subtype_number)), + Type::Constant(Constant::Number(value)), + ) = (argument_type, subtype_argument) + { + if subtype_number < value { + SubTypeResult::IsSubType + } else { + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + } + } else { + crate::utilities::notify!("Here"); + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + } + } } else { - crate::utilities::notify!("TODO"); SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) }; } @@ -1113,7 +1144,8 @@ pub(crate) fn type_is_subtype_with_generics( Type::Constructor(cst) => match cst { // For template literal types Constructor::BinaryOperator { - operator: crate::types::MathematicalAndBitwise::Add, + operator: crate::types::MathematicalOrBitwiseOperation::Add, + result: TypeId::STRING_TYPE, .. } => { if let Type::Constant(Constant::String(rs)) = subtype { @@ -1131,17 +1163,26 @@ pub(crate) fn type_is_subtype_with_generics( // TODO clear contributions SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) } + } else if let Type::Constructor(Constructor::BinaryOperator { + operator: crate::types::MathematicalOrBitwiseOperation::Add, + result: TypeId::STRING_TYPE, + .. + }) = subtype + { + crate::utilities::notify!("TODO test prefixes"); + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) } else { - crate::utilities::notify!("RHS not string"); - type_is_subtype_with_generics( - (TypeId::NUMBER_TYPE, base_type_arguments), - (ty, ty_structure_arguments), - state, - information, - types, - ) + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) } } + Constructor::BinaryOperator { + operator: crate::types::MathematicalOrBitwiseOperation::Add, + result: TypeId::NUMBER_TYPE, + .. + } => { + crate::utilities::notify!("TODO here!"); + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + } Constructor::BinaryOperator { .. } | Constructor::CanonicalRelationOperator { .. } => { unreachable!("invalid constructor on LHS") } @@ -1415,9 +1456,15 @@ pub(crate) fn type_is_subtype_with_generics( if *prototype == base_type { SubTypeResult::IsSubType } else { + crate::utilities::notify!( + "Mismatched prototype {:?} != {:?}", + prototype, + base_type + ); SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) } } else { + crate::utilities::notify!("No prototype"); SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) } } @@ -1433,13 +1480,26 @@ pub(crate) fn type_is_subtype_with_generics( types, ) } - Type::And(a, b) => { - // TODO more - crate::utilities::notify!("Here LHS class, RHS and"); - if *a == base_type || *b == base_type { - SubTypeResult::IsSubType + Type::And(left, right) => { + // This only happens in predicate edge cases (with numbers) + let left_result = type_is_subtype_with_generics( + (base_type, base_type_arguments), + (*left, ty_structure_arguments), + state, + information, + types, + ); + + if let SubTypeResult::IsSubType = left_result { + left_result } else { - SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + type_is_subtype_with_generics( + (base_type, base_type_arguments), + (*right, ty_structure_arguments), + state, + information, + types, + ) } } Type::SpecialObject(SpecialObject::Function(..)) | Type::FunctionReference(..) @@ -1447,8 +1507,8 @@ pub(crate) fn type_is_subtype_with_generics( { SubTypeResult::IsSubType } - ty => { - crate::utilities::notify!("Does {:?} not match class", ty); + _ty => { + // crate::utilities::notify!("{:?} does not match class", base_type); SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) } }, @@ -2588,7 +2648,7 @@ pub(crate) fn slice_matches_type( allow_casts, ) } else if let Some(contributions) = contributions { - assert!(rpt.is_substitutable()); + // assert!(rpt.is_substitutable(), "{:?}", rpt); let constraint = rpt.get_constraint(); let res = slice_matches_type( (constraint, base_type_arguments), @@ -2735,23 +2795,6 @@ pub(crate) fn slice_matches_type( allow_casts, ) } - Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: TypeId::MULTIPLE_OF | TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE, - arguments: _, - }) if allow_casts => { - // Special behavior here to treat numerical property keys (which are strings) as numbers - if let Ok(value) = slice.parse::<f64>() { - number_matches_type( - (base, base_type_arguments), - value, - contributions, - information, - types, - ) - } else { - false - } - } Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on: TypeId::NOT_RESTRICTION, arguments, @@ -2770,6 +2813,22 @@ pub(crate) fn slice_matches_type( // crate::utilities::notify!("negated slice arguments={:?}", _k); !matches } + Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on, arguments: _ }) + if allow_casts && intrinsics::is_ezno_number_intrinsic(*on) => + { + // Special behavior here to treat numerical property keys (which are strings) as numbers + if let Ok(value) = slice.parse::<f64>() { + number_matches_type( + (base, base_type_arguments), + value, + contributions, + information, + types, + ) + } else { + false + } + } Type::Constructor(super::Constructor::KeyOf(on)) => { let argument = (Publicity::Public, &PropertyKey::String(std::borrow::Cow::Borrowed(slice)), None); @@ -2836,10 +2895,18 @@ pub(crate) fn slice_matches_type( false } } + Type::Constant(Constant::Number(base)) => { + crate::utilities::notify!("Here"); + if let Ok(slice_as_float) = slice.parse::<f64>() { + *base == slice_as_float + } else { + false + } + } Type::Constructor(super::Constructor::BinaryOperator { lhs, rhs, - operator: MathematicalAndBitwise::Add, + operator: MathematicalOrBitwiseOperation::Add, result: _, }) => { let lhs = base_type_arguments @@ -2922,8 +2989,7 @@ pub(crate) fn number_matches_type( on: TypeId::MULTIPLE_OF, arguments, }) => { - let argument = - arguments.get_structure_restriction(TypeId::NUMBER_FLOOR_GENERIC).unwrap(); + let argument = arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(); if let Type::Constant(Constant::Number(argument)) = types.get_type_by_id(argument) { let number: ordered_float::NotNan<f64> = number.try_into().unwrap(); (number % argument) == 0. @@ -2933,11 +2999,20 @@ pub(crate) fn number_matches_type( } } Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE, + on: TypeId::LESS_THAN, + arguments: _, + }) => { + todo!() + // let lhs_range = intrinsics::get_range(base, types).unwrap(); + // intrinsics::FloatRange::new_single(number.try_into().unwrap()).contained_in(lhs_range) + } + Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::GREATER_THAN, arguments: _, }) => { - let lhs_range = intrinsics::get_range(base, types).unwrap(); - intrinsics::FloatRange::single(number.try_into().unwrap()).contained_in(lhs_range) + todo!() + // let lhs_range = intrinsics::get_range(base, types).unwrap(); + // intrinsics::FloatRange::new_single(number.try_into().unwrap()).contained_in(lhs_range) } Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on: TypeId::NOT_RESTRICTION, diff --git a/checker/src/utilities/debugging.rs b/checker/src/utilities/debugging.rs index 6ef42907..2a19cee2 100644 --- a/checker/src/utilities/debugging.rs +++ b/checker/src/utilities/debugging.rs @@ -17,7 +17,7 @@ pub(crate) fn is_debug_mode() -> bool { value } else { let new_value = std::env::var("EZNO_DEBUG") - .map(|value| !value.is_empty() || value == "0") + .map(|value| !(value.is_empty() || value == "0")) .unwrap_or_default(); IS_DEBUG_MODE.set(Some(new_value)); new_value diff --git a/checker/src/utilities/float_range.rs b/checker/src/utilities/float_range.rs index 133e226e..c0870212 100644 --- a/checker/src/utilities/float_range.rs +++ b/checker/src/utilities/float_range.rs @@ -1,18 +1,97 @@ type BetterF64 = ordered_float::NotNan<f64>; -// TODO #[derive(Debug, Clone, Copy, PartialEq)] -pub enum FloatRange { - /// yes or `===` - Inclusive { floor: BetterF64, ceiling: BetterF64 }, - /// but not necessarily `===` - Exclusive { floor: BetterF64, ceiling: BetterF64 }, +pub enum InclusiveExclusive { + Inclusive, + Exclusive, } +use InclusiveExclusive::{Exclusive, Inclusive}; + +impl InclusiveExclusive { + #[must_use] + pub fn mix(self, other: Self) -> Self { + if let (Inclusive, Inclusive) = (self, other) { + Inclusive + } else { + Exclusive + } + } + + #[must_use] + pub fn is_inclusive(self) -> bool { + matches!(self, Inclusive) + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct FloatRange { + pub floor: (InclusiveExclusive, BetterF64), + pub ceiling: (InclusiveExclusive, BetterF64), +} + +impl Default for FloatRange { + fn default() -> Self { + Self { + floor: (Exclusive, f64::NEG_INFINITY.try_into().unwrap()), + ceiling: (Exclusive, f64::INFINITY.try_into().unwrap()), + } + } +} + +// TODO try_from (assert ceiling > floor etc) impl FloatRange { #[must_use] - pub fn single(on: BetterF64) -> Self { - Self::Inclusive { floor: on, ceiling: on } + pub fn new_single(on: BetterF64) -> Self { + Self { floor: (Inclusive, on), ceiling: (Inclusive, on) } + } + + #[must_use] + pub fn as_single(self) -> Option<BetterF64> { + if let FloatRange { floor: (Inclusive, floor), ceiling: (Inclusive, ceiling) } = self { + (floor == ceiling).then_some(floor) + } else { + None + } + } + + #[must_use] + pub fn new_greater_than(greater_than: BetterF64) -> Self { + FloatRange { + floor: (Exclusive, greater_than), + ceiling: (Exclusive, f64::INFINITY.try_into().unwrap()), + } + } + + #[must_use] + pub fn get_greater_than(self) -> Option<BetterF64> { + (self.floor.1 != f64::NEG_INFINITY).then_some(self.floor.1) + } + + #[must_use] + pub fn new_less_than(less_than: BetterF64) -> Self { + FloatRange { + floor: (Exclusive, f64::NEG_INFINITY.try_into().unwrap()), + ceiling: (Exclusive, less_than), + } + } + + #[must_use] + pub fn get_less_than(self) -> Option<BetterF64> { + (self.ceiling.1 != f64::INFINITY).then_some(self.ceiling.1) + } + + #[must_use] + pub fn contains(self, value: BetterF64) -> bool { + if self.floor.1 < value && value < self.ceiling.1 { + true + } else if self.floor.1 == value { + self.floor.0.is_inclusive() + } else if self.ceiling.1 == value { + self.ceiling.0.is_inclusive() + } else { + false + } } /// For disjointness. TODO Think this is correct @@ -20,30 +99,23 @@ impl FloatRange { pub fn overlaps(self, other: Self) -> bool { crate::utilities::notify!("{:?} ∩ {:?} != ∅", self, other); - if let ( - Self::Inclusive { floor: l_floor, ceiling: l_ceiling }, - Self::Inclusive { floor: r_floor, ceiling: r_ceiling }, - ) = (self, other) - { - if l_floor <= r_floor { - l_ceiling >= r_floor - } else if l_ceiling >= r_ceiling { - l_floor <= r_ceiling - } else { - false - } + // TODO more with inclusivity etc + other.floor.1 <= self.ceiling.1 || other.ceiling.1 <= self.floor.1 + } + + pub fn intersection(self, other: Self) -> Result<Self, ()> { + crate::utilities::notify!("{:?} ∩ {:?}", self, other); + + let max_floor = self.floor.1.max(other.floor.1); + let min_ceiling = self.ceiling.1.min(other.ceiling.1); + + if max_floor <= min_ceiling { + Ok(Self { + floor: (self.floor.0.mix(other.floor.0), max_floor), + ceiling: (self.ceiling.0.mix(other.ceiling.0), min_ceiling), + }) } else { - let (Self::Inclusive { floor: l_floor, ceiling: l_ceiling } - | Self::Exclusive { floor: l_floor, ceiling: l_ceiling }) = self; - let (Self::Inclusive { floor: r_floor, ceiling: r_ceiling } - | Self::Exclusive { floor: r_floor, ceiling: r_ceiling }) = other; - if l_floor < r_floor { - l_ceiling > r_floor - } else if l_ceiling > r_ceiling { - l_floor < r_ceiling - } else { - false - } + Err(()) } } @@ -52,37 +124,27 @@ impl FloatRange { pub fn contained_in(self, other: Self) -> bool { crate::utilities::notify!("{:?} ⊆ {:?}", self, other); // Edge case - if let ( - Self::Inclusive { floor: l_floor, ceiling: l_ceiling }, - Self::Exclusive { floor: r_floor, ceiling: r_ceiling }, - ) = (self, other) - { - l_floor > r_floor && l_ceiling < r_ceiling + let lhs = if let (Inclusive, Exclusive) = (self.floor.0, other.floor.0) { + self.floor.1 > other.floor.1 } else { - let (Self::Inclusive { floor: l_floor, ceiling: l_ceiling } - | Self::Exclusive { floor: l_floor, ceiling: l_ceiling }) = self; - let (Self::Inclusive { floor: r_floor, ceiling: r_ceiling } - | Self::Exclusive { floor: r_floor, ceiling: r_ceiling }) = other; - l_floor >= r_floor && l_ceiling <= r_ceiling - } + self.floor.1 >= other.floor.1 + }; + let rhs = if let (Inclusive, Exclusive) = (self.ceiling.0, other.ceiling.0) { + self.ceiling.1 < other.ceiling.1 + } else { + self.ceiling.1 <= other.ceiling.1 + }; + lhs && rhs } /// ∀ a in self, ∀ b in other: a > b #[must_use] pub fn above(self, other: Self) -> bool { crate::utilities::notify!("{:?} > {:?}", self, other); - if let ( - Self::Inclusive { floor: l_floor, ceiling: _ }, - Self::Inclusive { floor: _, ceiling: r_ceiling }, - ) = (self, other) - { - l_floor > r_ceiling + if let (Inclusive, Inclusive) = (self.ceiling.0, other.floor.0) { + self.floor.1 > other.ceiling.1 } else { - let (Self::Inclusive { floor: l_floor, ceiling: _ } - | Self::Exclusive { floor: l_floor, ceiling: _ }) = self; - let (Self::Inclusive { floor: _, ceiling: r_ceiling } - | Self::Exclusive { floor: _, ceiling: r_ceiling }) = other; - l_floor >= r_ceiling + self.floor.1 >= other.ceiling.1 } } @@ -90,118 +152,147 @@ impl FloatRange { #[must_use] pub fn below(self, other: Self) -> bool { crate::utilities::notify!("{:?} < {:?}", self, other); - if let ( - Self::Inclusive { floor: _, ceiling: l_ceiling }, - Self::Inclusive { floor: r_floor, ceiling: _ }, - ) = (self, other) - { - l_ceiling < r_floor + if let (Inclusive, Inclusive) = (self.ceiling.0, other.floor.0) { + self.ceiling.1 < other.floor.1 } else { - let (Self::Inclusive { floor: _, ceiling: l_ceiling } - | Self::Exclusive { floor: _, ceiling: l_ceiling }) = self; - let (Self::Inclusive { floor: r_floor, ceiling: _ } - | Self::Exclusive { floor: r_floor, ceiling: _ }) = other; - l_ceiling <= r_floor + self.ceiling.1 <= other.floor.1 } } #[must_use] pub fn space_addition(self, other: Self) -> Self { - if let ( - Self::Inclusive { floor: l_floor, ceiling: l_ceiling }, - Self::Inclusive { floor: r_floor, ceiling: r_ceiling }, - ) = (self, other) - { - Self::Inclusive { floor: l_floor + r_floor, ceiling: l_ceiling + r_ceiling } - } else { - let (Self::Inclusive { floor: l_floor, ceiling: l_ceiling } - | Self::Exclusive { floor: l_floor, ceiling: l_ceiling }) = self; - let (Self::Inclusive { floor: r_floor, ceiling: r_ceiling } - | Self::Exclusive { floor: r_floor, ceiling: r_ceiling }) = other; - Self::Exclusive { floor: l_floor + r_floor, ceiling: l_ceiling + r_ceiling } + let floor_bound = self.floor.0.mix(other.floor.0); + let ceiling_bound = self.ceiling.0.mix(other.ceiling.0); + Self { + floor: (floor_bound, self.floor.1 + other.floor.1), + ceiling: (ceiling_bound, self.ceiling.1 + other.ceiling.1), } } #[must_use] pub fn space_multiplication(self, other: Self) -> Self { - let inclusive = matches!((self, other), (Self::Inclusive { .. }, Self::Inclusive { .. })); - let (Self::Inclusive { floor: l_floor, ceiling: l_ceiling } - | Self::Exclusive { floor: l_floor, ceiling: l_ceiling }) = self; - let (Self::Inclusive { floor: r_floor, ceiling: r_ceiling } - | Self::Exclusive { floor: r_floor, ceiling: r_ceiling }) = other; - // being lazy + let (l_floor, l_ceiling, r_floor, r_ceiling) = + (self.floor.1, self.ceiling.1, other.floor.1, other.ceiling.1); + // there may be a faster way but being lazy let corners = [l_floor * r_floor, l_floor * r_ceiling, r_floor * l_ceiling, l_ceiling * r_ceiling]; let floor = *corners.iter().min().unwrap(); let ceiling = *corners.iter().max().unwrap(); - if inclusive { - Self::Inclusive { floor, ceiling } + + let floor_bound = self.floor.0.mix(other.floor.0); + let ceiling_bound = self.ceiling.0.mix(other.ceiling.0); + Self { floor: (floor_bound, floor), ceiling: (ceiling_bound, ceiling) } + } + + #[must_use] + pub fn contains_multiple_of(self, multiple_of: BetterF64) -> bool { + let (floor, ceiling) = (self.floor.1, self.ceiling.1); + + let floor = floor / multiple_of; + let ceiling = ceiling / multiple_of; + + // TODO >= ? + ceiling.floor() > *floor + } + + // This will try to get cover + // A union like above might create gaps. aka if try_get_cover (0, 1) (3, 4) = (0, 4) then it implies 2 + // exists is in one of the ranges. Thus in this case it returns None + #[must_use] + pub fn try_get_cover(self, other: Self) -> Option<Self> { + if self.contained_in(other) { + Some(other) + } else if other.contained_in(self) { + Some(self) } else { - Self::Exclusive { floor, ceiling } + None } } // TODO more :) } +impl From<std::ops::Range<BetterF64>> for FloatRange { + fn from(range: std::ops::Range<BetterF64>) -> FloatRange { + FloatRange { floor: (Exclusive, range.start), ceiling: (Exclusive, range.end) } + } +} +impl TryFrom<std::ops::Range<f64>> for FloatRange { + type Error = ordered_float::FloatIsNan; + + fn try_from(range: std::ops::Range<f64>) -> Result<Self, Self::Error> { + let floor = ordered_float::NotNan::new(range.start)?; + let ceiling = ordered_float::NotNan::new(range.end)?; + Ok(FloatRange { floor: (Exclusive, floor), ceiling: (Exclusive, ceiling) }) + } +} + // TODO more #[cfg(test)] mod tests { - use super::{BetterF64, FloatRange}; + use super::{BetterF64, FloatRange, InclusiveExclusive}; + + fn e(a: f64) -> (InclusiveExclusive, BetterF64) { + (InclusiveExclusive::Exclusive, a.try_into().unwrap()) + } + + fn i(a: f64) -> (InclusiveExclusive, BetterF64) { + (InclusiveExclusive::Inclusive, a.try_into().unwrap()) + } #[test] fn contained_in() { - assert!(FloatRange::single(2.into()) - .contained_in(FloatRange::Exclusive { floor: 0.into(), ceiling: 5.into() })); + let zero_to_four: FloatRange = FloatRange::try_from(0f64..4f64).unwrap(); + assert!(FloatRange::new_single(2.into()).contained_in(zero_to_four)); } #[test] fn overlaps() { - assert!(FloatRange::Exclusive { floor: 0.into(), ceiling: 4.into() } - .overlaps(FloatRange::Exclusive { floor: 2.into(), ceiling: 5.into() })); - assert!(!FloatRange::Exclusive { floor: 0.into(), ceiling: 1.into() } - .overlaps(FloatRange::Exclusive { floor: 2.into(), ceiling: 5.into() })); + assert!(FloatRange { floor: e(0.), ceiling: e(4.) } + .overlaps(FloatRange { floor: e(2.), ceiling: e(5.) })); + + assert!(!FloatRange { floor: e(0.), ceiling: e(1.) } + .overlaps(FloatRange { floor: e(2.), ceiling: e(5.) })); } #[test] fn above() { - assert!(FloatRange::Exclusive { floor: 8.into(), ceiling: 10.into() } - .above(FloatRange::Exclusive { floor: 6.into(), ceiling: 7.into() })); - assert!(!FloatRange::Exclusive { floor: 0.into(), ceiling: 1.into() } - .above(FloatRange::Exclusive { floor: 0.into(), ceiling: 5.into() })); + assert!(FloatRange { floor: e(8.), ceiling: e(10.) } + .above(FloatRange { floor: e(6.), ceiling: e(7.) })); + assert!(!FloatRange { floor: e(0.), ceiling: e(1.) } + .above(FloatRange { floor: e(0.), ceiling: e(5.) })); } #[test] fn below() { - assert!(FloatRange::Exclusive { floor: 0.into(), ceiling: 4.into() } - .below(FloatRange::Exclusive { floor: 6.into(), ceiling: 7.into() })); - assert!(!FloatRange::Exclusive { floor: 0.into(), ceiling: 1.into() } - .below(FloatRange::Exclusive { floor: 0.into(), ceiling: 5.into() })); + assert!(FloatRange { floor: e(0.), ceiling: e(4.) } + .below(FloatRange { floor: e(6.), ceiling: e(7.) })); + assert!(!FloatRange { floor: e(0.), ceiling: e(1.) } + .below(FloatRange { floor: e(0.), ceiling: e(5.) })); } #[test] fn space_addition() { - assert_eq!( - FloatRange::Exclusive { floor: 0.into(), ceiling: 4.into() } - .space_addition(FloatRange::Exclusive { floor: 6.into(), ceiling: 7.into() }), - FloatRange::Exclusive { floor: 6.into(), ceiling: 11.into() } - ); + let lhs = FloatRange { floor: e(0.), ceiling: e(4.) } + .space_addition(FloatRange { floor: e(6.), ceiling: e(7.) }); + assert_eq!(lhs, FloatRange { floor: e(6.), ceiling: e(11.) }); } #[test] fn space_multiplication() { - assert_eq!( - FloatRange::Exclusive { floor: 0.into(), ceiling: 4.into() } - .space_multiplication(FloatRange::Exclusive { floor: 6.into(), ceiling: 7.into() }), - FloatRange::Exclusive { floor: 0.into(), ceiling: 28.into() } - ); - assert_eq!( - FloatRange::Exclusive { floor: BetterF64::from(-2i32), ceiling: 4.into() } - .space_multiplication(FloatRange::Exclusive { - floor: BetterF64::from(-10i32), - ceiling: 1.into() - }), - FloatRange::Exclusive { floor: BetterF64::from(-40i32), ceiling: 20.into() } - ); + let lhs = FloatRange { floor: e(0.), ceiling: e(4.) } + .space_multiplication(FloatRange { floor: e(6.), ceiling: e(7.) }); + assert_eq!(lhs, FloatRange { floor: e(0.), ceiling: e(28.) }); + + let lhs = FloatRange { floor: e(-2.), ceiling: e(4.) } + .space_multiplication(FloatRange { floor: e(-10.), ceiling: e(1.) }); + assert_eq!(lhs, FloatRange { floor: e(-40.), ceiling: e(20.) }); + } + + #[test] + fn multiple_of() { + let lhs = FloatRange { floor: e(30.), ceiling: e(34.) }; + assert!(lhs.contains_multiple_of(4.into())); // 32 + assert!(!lhs.contains_multiple_of(7.into())); // 28 -- 35 } } diff --git a/checker/src/utilities/mod.rs b/checker/src/utilities/mod.rs index 222c487a..4e59012d 100644 --- a/checker/src/utilities/mod.rs +++ b/checker/src/utilities/mod.rs @@ -3,6 +3,7 @@ mod debugging; pub mod float_range; pub mod map; +pub mod modulo_class; pub mod range_map; pub mod serialization; diff --git a/checker/src/utilities/modulo_class.rs b/checker/src/utilities/modulo_class.rs new file mode 100644 index 00000000..e5753d50 --- /dev/null +++ b/checker/src/utilities/modulo_class.rs @@ -0,0 +1,162 @@ +type BetterF64 = ordered_float::NotNan<f64>; + +/// x ≡ *class* [mod *modulo*] +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct ModuloClass { + pub modulo: BetterF64, + pub offset: BetterF64, +} + +// TODO more operations +impl ModuloClass { + #[must_use] + pub fn new(modulo: BetterF64, offset: BetterF64) -> Self { + debug_assert!(modulo != 0.); + if modulo > 0f64.try_into().unwrap() { + Self { offset: offset % modulo, modulo } + } else { + // TODO think this is correct. [1]_{-3} = [2]_{3} + let modulo = -modulo; + Self { offset: modulo - (offset % modulo), modulo } + } + } + + #[must_use] + pub fn contains(self, value: BetterF64) -> bool { + // Note -0. = 0. + (value - self.offset) % self.modulo == 0. + } + + /// WIP + #[must_use] + pub fn disjoint(self, other: Self) -> bool { + if let Ok(gcd) = gcd_of_float(self.modulo, other.modulo) { + crate::utilities::notify!("{:?}", gcd); + (self.offset - other.offset) % gcd != 0. + } else { + crate::utilities::notify!("Here"); + true + } + } + + #[must_use] + pub fn intersection(self, _other: Self) -> Option<Self> { + todo!() + } + + #[must_use] + pub fn get_cover(self, _other: Self) -> Option<Self> { + todo!() + } + + #[must_use] + pub fn offset(self, offset: BetterF64) -> Self { + // TODO temp fix + if self.is_default() { + self + } else { + Self::new(self.modulo, self.offset + offset) + } + } + + #[must_use] + pub fn multiply(self, multiple: BetterF64) -> Self { + // TODO temp fix + if self.is_default() { + self + } else { + Self::new(self.modulo * multiple, self.offset) + } + } + + #[must_use] + pub fn negate(self) -> Self { + // TODO temp fix + if self.is_default() { + self + } else { + Self::new(self.modulo, self.modulo - self.offset) + } + } + + #[must_use] + pub fn is_default(self) -> bool { + self.modulo == f64::EPSILON + } +} + +/// Using Farey algoirthm +/// TODO is there a faster way implemntation +/// Note that numerator and denominator are coprime +fn try_get_numerator_denominator(input: BetterF64) -> Result<(i32, i32), ()> { + const STEPS: usize = 50; + const MARGIN: f64 = 1e-4; + + let integer_part = input.trunc() as i32; + let fractional_part = input.fract(); + if fractional_part == 0. { + return Ok((integer_part, 1)); + } + + let (mut a, mut b, mut c, mut d) = (0, 1, 1, 1); + + for _ in 0..STEPS { + let mediant_float = (f64::from(a) + f64::from(b)) / (f64::from(c) + f64::from(d)); + if (fractional_part - mediant_float).abs() < MARGIN { + let numerator = a + b + integer_part * (c + d); + let denominator = c + d; + return Ok((numerator, denominator)); + } else if fractional_part > mediant_float { + a += b; + c += d; + } else { + b += a; + d += c; + } + } + + Err(()) +} + +fn gcd_of_float(n1: BetterF64, n2: BetterF64) -> Result<BetterF64, ()> { + fn gcd(mut n1: i32, mut n2: i32) -> i32 { + while n2 != 0 { + let t = n2; + n2 = n1 % n2; + n1 = t; + } + n1 + } + + /// n1*n2 = gcd(n1, n2)*lcm(n1, n2) + fn lcm(n1: i32, n2: i32) -> i32 { + (n1 * n2) / gcd(n1, n2) + } + + let (a, b) = try_get_numerator_denominator(n1)?; + let (c, d) = try_get_numerator_denominator(n2)?; + + // gcd(a / b, c / d) = gcd(a, c) / lcm(b, d) + Ok(BetterF64::new(f64::from(gcd(a, c)) / f64::from(lcm(b, d))).unwrap()) +} + +// hmmm +impl Default for ModuloClass { + fn default() -> Self { + Self { modulo: f64::EPSILON.try_into().unwrap(), offset: BetterF64::new(0.).unwrap() } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // TODO test negatives etc + #[test] + fn gcd() { + assert_eq!( + gcd_of_float(BetterF64::new(1. / 3.).unwrap(), BetterF64::new(3. / 2.).unwrap()), + Ok(BetterF64::new(0.16666666666666666).unwrap()) + ); + } +} diff --git a/parser/examples/code_blocks_to_script.rs b/parser/examples/code_blocks_to_script.rs index 4f0a4fea..4abf44f4 100644 --- a/parser/examples/code_blocks_to_script.rs +++ b/parser/examples/code_blocks_to_script.rs @@ -13,6 +13,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { let replace_satisfies_with_as = args.iter().any(|item| item == "--satisfies-with-as"); let add_headers_as_comments = args.iter().any(|item| item == "--comment-headers"); + // let declare_to_function = args.iter().any(|item| item == "--declare-to-function"); let into_files_directory_and_extension = args.windows(3).find_map(|item| { matches!(item[0].as_str(), "--into-files").then_some((item[1].clone(), item[2].clone())) @@ -35,7 +36,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { let content = std::fs::read_to_string(&path)?; - let filters: Vec<&str> = vec!["import", "export", "declare"]; + let filters: Vec<&str> = vec!["import", "export"]; let blocks = if path.ends_with(".md") { let mut blocks = Vec::new(); @@ -98,104 +99,193 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { } } } - return Ok(()); - } - - // Else bundle into one, bound in arrow functions to prevent namespace collision - let mut final_blocks: Vec<(HashSet<String>, String)> = Vec::new(); - for (header, code) in blocks { - let module = Module::from_string(code.clone(), Default::default()).map_err(Box::new)?; - - let mut names = HashSet::new(); - - let mut visitors = Visitors { - expression_visitors: Default::default(), - statement_visitors: Default::default(), - variable_visitors: vec![Box::new(NameFinder)], - block_visitors: Default::default(), - }; - - module.visit::<HashSet<String>>( - &mut visitors, - &mut names, - &VisitOptions { visit_nested_blocks: false, reverse_statements: false }, - source_map::Nullable::NULL, - ); - - // TODO quick fix to also register interface and type alias names to prevent conflicts - for item in module.items { - match item { - StatementOrDeclaration::Declaration(Declaration::TypeAlias(TypeAlias { - name: StatementPosition { identifier: VariableIdentifier::Standard(s, _), .. }, - .. - })) => { - names.insert(s.clone()); + } else { + // Else bundle into one, bound in arrow functions to prevent namespace collision + let mut final_blocks: Vec<(HashSet<String>, String)> = Vec::new(); + for (header, mut code) in blocks { + // TODO clone + let module = match Module::from_string(code.clone(), Default::default()) { + Ok(module) => module, + Err(err) => { + return Err(From::from(format!("Parse error on {code}: {err:?}"))); } - StatementOrDeclaration::Declaration(Declaration::Interface(Decorated { - on: - InterfaceDeclaration { - name: - StatementPosition { - identifier: VariableIdentifier::Standard(s, _), .. - }, - .. - }, - .. - })) => { - names.insert(s.clone()); + }; + + let mut names = HashSet::new(); + + let mut visitors = Visitors { + expression_visitors: Default::default(), + statement_visitors: Default::default(), + variable_visitors: vec![Box::new(NameFinder)], + block_visitors: Default::default(), + }; + + module.visit::<HashSet<String>>( + &mut visitors, + &mut names, + &VisitOptions { visit_nested_blocks: false, reverse_statements: false }, + source_map::Nullable::NULL, + ); + + let mut declare_lets = Vec::new(); + + // TODO quick fix to also register interface and type alias names to prevent conflicts + for item in &module.items { + match item { + StatementOrDeclaration::Declaration(Declaration::TypeAlias(TypeAlias { + name: + StatementPosition { identifier: VariableIdentifier::Standard(s, _), .. }, + .. + })) => { + names.insert(s.clone()); + } + StatementOrDeclaration::Declaration(Declaration::Interface(Decorated { + on: + InterfaceDeclaration { + name: + StatementPosition { + identifier: VariableIdentifier::Standard(s, _), + .. + }, + .. + }, + .. + })) => { + names.insert(s.clone()); + } + StatementOrDeclaration::Declaration(Declaration::DeclareVariable( + declare_variable, + )) => { + for declaration in &declare_variable.declarations { + declare_lets.push(( + declaration.name.clone(), + declaration.type_annotation.clone(), + )); + } + } + _ => {} } - _ => {} } - } - // If available block add to that, otherwise create a new one - if let Some((items, block)) = - final_blocks.iter_mut().find(|(uses, _)| uses.is_disjoint(&names)) - { - items.extend(names.into_iter()); - if add_headers_as_comments { - block.push_str("\n\t// "); - block.push_str(&header); - } - for line in code.lines() { - block.push_str("\n\t"); - block.push_str(&line); - } - // If the block is not terminated, it can change the parsing of the next one - if block.ends_with(')') { - block.push(';'); - } - block.push('\n'); - } else { - let mut block = String::new(); - if add_headers_as_comments { - block.push_str("\t// "); - block.push_str(&header); + if !declare_lets.is_empty() { + use source_map::{Nullable, Span}; + let (mut top_level, mut inside) = (Vec::new(), Vec::new()); + for item in module.items { + match item { + StatementOrDeclaration::Declaration(Declaration::TypeAlias( + TypeAlias { .. }, + )) => { + top_level.push(item); + } + StatementOrDeclaration::Declaration(Declaration::Interface( + Decorated { .. }, + )) => { + top_level.push(item); + } + StatementOrDeclaration::Declaration(Declaration::DeclareVariable(..)) => {} + item => { + inside.push(item); + } + } + } + + use ezno_parser::{ast, expressions::operators, functions, Expression, Statement}; + + let parameters = declare_lets + .into_iter() + .map(|(name, type_annotation)| functions::Parameter { + visibility: (), + name, + type_annotation, + additionally: None, + position: Span::NULL, + }) + .collect(); + + let function = Expression::ArrowFunction(ast::ArrowFunction { + // TODO maybe async + header: false, + name: (), + parameters: functions::FunctionParameters { + parameters, + rest_parameter: Default::default(), + position: Span::NULL, + leading: (), + }, + return_type: None, + type_parameters: None, + position: Span::NULL, + body: ast::ExpressionOrBlock::Block(ast::Block(inside, Span::NULL)), + }); + + // void is temp fix + top_level.push( + Statement::Expression( + Expression::UnaryOperation { + operator: operators::UnaryOperator::Void, + operand: Box::new(function), + position: Span::NULL, + } + .into(), + ) + .into(), + ); + + let module = Module { hashbang_comment: None, items: top_level, span: Span::NULL }; + + code = module.to_string(&ezno_parser::ToStringOptions::typescript()); } - for line in code.lines() { - block.push_str("\n\t"); - block.push_str(&line); + + // If available block add to that, otherwise create a new one + if let Some((items, block)) = + final_blocks.iter_mut().find(|(uses, _)| uses.is_disjoint(&names)) + { + items.extend(names.into_iter()); + if add_headers_as_comments { + block.push_str("\n\t// "); + block.push_str(&header); + } + for line in code.lines() { + block.push_str("\n\t"); + block.push_str(&line); + } + // If the block is not terminated, it can change the parsing of the next one + if block.ends_with(')') { + block.push(';'); + } + block.push('\n'); + } else { + let mut block = String::new(); + if add_headers_as_comments { + block.push_str("\t// "); + block.push_str(&header); + } + for line in code.lines() { + block.push_str("\n\t"); + block.push_str(&line); + } + block.push('\n'); + final_blocks.push((names, block)); } - block.push('\n'); - final_blocks.push((names, block)); } - } - // eprintln!("Generated {:?} blocks", final_blocks.len()); + // eprintln!("Generated {:?} blocks", final_blocks.len()); - if let Some(out) = out_file { - let mut out = std::fs::File::create(out)?; - for (_items, block) in final_blocks { - writeln!(out, "() => {{\n{block}}};\n")?; - } - } else { - let mut out = std::io::stdout(); - for (_items, block) in final_blocks { - // eprintln!("block includes: {items:?}\n{block}\n---"); - writeln!(out, "() => {{\n{block}}};\n")?; + eprintln!("Bundled into {} functions", final_blocks.len()); + + if let Some(out) = out_file { + let mut out = std::fs::File::create(out)?; + for (_items, block) in final_blocks { + writeln!(out, "() => {{\n{block}}};\n")?; + } + } else { + let mut out = std::io::stdout(); + for (_items, block) in final_blocks { + // eprintln!("block includes: {items:?}\n{block}\n---"); + writeln!(out, "() => {{\n{block}}};\n")?; + } } } - Ok(()) } diff --git a/parser/src/expressions/arrow_function.rs b/parser/src/expressions/arrow_function.rs index 043ebc85..e30a80b1 100644 --- a/parser/src/expressions/arrow_function.rs +++ b/parser/src/expressions/arrow_function.rs @@ -91,17 +91,19 @@ impl FunctionBased for ArrowFunctionBase { local: crate::LocalToStringInformation, ) { // Use shorthand if one parameter with no declared type - if let (true, [Parameter { name, .. }]) = - (parameters.rest_parameter.is_none(), parameters.parameters.as_slice()) + if let ([Parameter { name, type_annotation, .. }], None) = + (parameters.parameters.as_slice(), ¶meters.rest_parameter) { - if let VariableField::Name(name, ..) = name.get_ast_ref() { - name.to_string_from_buffer(buf, options, local); - } else { - parameters.to_string_from_buffer(buf, options, local); + let is_printing_type_annotation = + options.include_type_annotations && type_annotation.is_some(); + if !is_printing_type_annotation { + if let VariableField::Name(name, ..) = name.get_ast_ref() { + name.to_string_from_buffer(buf, options, local); + return; + } } - } else { - parameters.to_string_from_buffer(buf, options, local); } + parameters.to_string_from_buffer(buf, options, local); } fn parameter_body_boundary_token_to_string_from_buffer<T: source_map::ToString>( diff --git a/src/cli.rs b/src/cli.rs index a62dab08..fbdd1de4 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -121,6 +121,9 @@ pub(crate) struct CheckArguments { /// compact diagnostics #[argh(switch)] pub compact_diagnostics: bool, + /// more behavior for numbers + #[argh(switch)] + pub advanced_numbers: bool, /// maximum diagnostics to print (defaults to 30, pass `all` for all and `0` to count) #[argh(option, default = "MaxDiagnostics::default()")] pub max_diagnostics: MaxDiagnostics, @@ -262,12 +265,17 @@ pub fn run_cli<T: crate::ReadFromFS, U: crate::WriteToFS>( timings, compact_diagnostics, max_diagnostics, + advanced_numbers, } = check_arguments; let type_check_options: TypeCheckOptions = if cfg!(target_family = "wasm") { Default::default() } else { - TypeCheckOptions { measure_time: timings, ..TypeCheckOptions::default() } + TypeCheckOptions { + measure_time: timings, + advanced_numbers, + ..TypeCheckOptions::default() + } }; let entry_points = match get_entry_points(input) { @@ -293,7 +301,7 @@ pub fn run_cli<T: crate::ReadFromFS, U: crate::WriteToFS>( new_debouncer(Duration::from_millis(200), None, tx).unwrap(); for e in &entry_points { - _ = debouncer.watcher().watch(e, notify::RecursiveMode::Recursive).unwrap(); + debouncer.watcher().watch(e, notify::RecursiveMode::Recursive).unwrap(); } let _ = run_checker( @@ -359,7 +367,7 @@ pub fn run_cli<T: crate::ReadFromFS, U: crate::WriteToFS>( source_maps: build_config.source_maps, type_definition_module: build_config.definition_file, // TODO not sure - output_path: PathBuf::from(output_path), + output_path, other_transformers: None, lsp_mode: false, }; diff --git a/src/playground/main.js b/src/playground/main.js index 9a7065b5..0aa7f379 100644 --- a/src/playground/main.js +++ b/src/playground/main.js @@ -5,7 +5,7 @@ import { defaultKeymap, indentWithTab, toggleLineComment } from "@codemirror/com import { parser as jsParser } from "@lezer/javascript"; import { tags } from "@lezer/highlight"; import { HighlightStyle, syntaxHighlighting, LanguageSupport, LRLanguage } from "@codemirror/language"; -import { init as init_ezno, check_with_options, get_version } from "ezno"; +import { init as init_ezno, check, get_version } from "ezno"; const diagnosticsEntry = document.querySelector(".diagnostics"); const editorParent = document.querySelector("#editor"); @@ -65,6 +65,14 @@ if (id) { let currentState = null; const ROOT_PATH = "index.tsx"; +const options = { + // Allow partial syntax + lsp_mode: true, + // For hover + store_type_mappings: true, + // For showing off + number_intrinsics: true +}; async function setup() { await init_ezno(); @@ -74,7 +82,7 @@ async function setup() { text = args.state.doc.text.join("\n"); try { const start = performance.now(); - currentState = check_with_options(ROOT_PATH, (_) => text, { lsp_mode: true, store_type_mappings: true }); + currentState = check(ROOT_PATH, (_) => text, options); const elapsed = performance.now() - start; timeOutput.innerText = `Parsed & checked in ${Math.trunc(elapsed)}ms`;