diff --git a/.github/workflows/performance-and-size.yml b/.github/workflows/performance-and-size.yml index 9ce0f5b3..2b88e15d 100644 --- a/.github/workflows/performance-and-size.yml +++ b/.github/workflows/performance-and-size.yml @@ -29,18 +29,6 @@ jobs: with: packages: hyperfine - # For displaying line count in file - - name: Download scc - run: | - mkdir scc - cd scc - gh release download v3.1.0 -R boyter/scc -p '*Linux_x86_64.tar.gz' -O scc.tar.gz - tar -xf scc.tar.gz - chmod +x scc - pwd >> $GITHUB_PATH - env: - GH_TOKEN: ${{ github.token }} - - name: Build Ezno run: cargo build --release env: @@ -52,8 +40,6 @@ jobs: # Generate a file which contains everything that Ezno currently implements cargo run -p ezno-parser --example code_blocks_to_script ./checker/specification/specification.md --comment-headers --out ./demo.tsx - LINES_OF_CODE=$(scc -c --no-cocomo -f json demo.tsx | jq ".[0].Code") - echo "### Checking \`\`\`shell $(hyperfine -i './target/release/ezno check demo.tsx') @@ -62,10 +48,8 @@ jobs: echo "
Input + > Code generated from specification.md. this is not meant to accurately represent a program but instead give an idea for how it scales across all the type checking features \`\`\`tsx - // $LINES_OF_CODE lines of TypeScript generated from specification.md - // this is not meant to accurately represent a program but instead give an idea - // for how it scales across all the type checking features $(cat ./demo.tsx) \`\`\`
@@ -73,22 +57,65 @@ jobs: echo "::info::Wrote code to summary" + command_output=$(./target/release/ezno check demo.tsx --timings --max-diagnostics all 2>&1 || true) + diagnostics=""; statistics=""; found_splitter=false; + + while IFS= read -r line; do + if [[ "$line" == "---"* ]]; then found_splitter=true; + elif [[ "$found_splitter" == false ]]; then diagnostics+="$line"$'\n'; + else statistics+="$line"$'\n'; fi + done <<< "$command_output" + echo "
Diagnostics - + \`\`\` - $(./target/release/ezno check demo.tsx --timings --max-diagnostics all 2>&1 || true) + $diagnostics + \`\`\` +
+ +
+ Statistics + + \`\`\` + $statistics \`\`\`
" >> $GITHUB_STEP_SUMMARY + - name: Run checker performance w/staging + shell: bash + if: github.ref_name != 'main' + run: | + echo "::group::Running all" + + cat ./checker/specification/specification.md ./checker/specification/staging.md > all.md + cargo run -p ezno-parser --example code_blocks_to_script all.md --comment-headers --out ./all.tsx + + ./target/release/ezno check all.tsx --timings || true + echo "::endgroup::" + + - name: Run checker performance on large file + shell: bash + run: | + echo "::group::Running large" + + cat ./checker/specification/specification.md > main.md + cargo run -p ezno-parser --example code_blocks_to_script all.md --comment-headers --out ./code.tsx + for i in {1..10}; do + cat ./code.tsx >> large.tsx + done + + ./target/release/ezno check large.tsx --timings --max-diagnostics 0 || true + echo "::endgroup::" + - name: Run parser, minfier/stringer performance shell: bash run: | strings=( "https://esm.sh/v128/react-dom@18.2.0/es2022/react-dom.mjs" - "https://esm.sh/v135/typescript@5.3.3/es2022/typescript.mjs" ) + # Currently broken "https://esm.sh/v135/typescript@5.3.3/es2022/typescript.mjs" for url in "${strings[@]}"; do curl -sS $url > input.js diff --git a/.gitignore b/.gitignore index 4ba56a16..0dcdeb02 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ node_modules /TODO.txt checker/specification/Cargo.lock checker/definitions/es5.d.ts +.grit \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 56803504..baddb3f1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,7 +25,7 @@ Now in the `ezno` directory, `cargo run` should show the CLI. You can run just the checker with ```shell -cargo run -p ezno-checker --example run-checker path/to/file.ts +cargo run -p ezno-checker --example run_checker path/to/file.ts ``` > [!TIP] diff --git a/Cargo.lock b/Cargo.lock index 38bb9718..c11fef7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -308,6 +308,7 @@ dependencies = [ "ezno-parser", "iterator-endiate", "levenshtein", + "match_deref", "ordered-float", "path-absolutize", "serde", diff --git a/Cargo.toml b/Cargo.toml index bec01db9..7e2350c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,16 +79,20 @@ js-sys = "0.3" tsify = "0.4.5" [workspace.lints.clippy] -all = "deny" -pedantic = "deny" +all = { level = "deny", priority = -1 } +pedantic = { level = "deny", priority = -1 } cast_precision_loss = "warn" cast_possible_truncation = "warn" cast_sign_loss = "warn" default_trait_access = "allow" missing_errors_doc = "allow" missing_panics_doc = "allow" -implicit_hasher = "allow" module_name_repetitions = "allow" too_many_lines = "allow" new_without_default = "allow" result_unit_err = "allow" +thread_local_initializer_can_be_made_const = "allow" +implicit_hasher = "allow" + +[profile.dev] +debug = false diff --git a/README.md b/README.md index 32b4c50b..1a174c96 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ A JavaScript compiler and TypeScript checker written in Rust with a focus on static analysis and runtime performance. > [!IMPORTANT] -> Ezno is in active development and **does not currently support enough features to check existing projects**. Check out the [getting started guide](./checker/documentation/getting-started.md) for experimenting with what it [currently supports](./checker/specification/specification.md). +> Ezno is in active development and **does not currently support enough features to check existing projects** (see [blocking issues](https://github.com/kaleidawave/ezno/labels/blocking)). Check out the [getting started guide](./checker/documentation/getting-started.md) for experimenting with what it [currently supports](./checker/specification/specification.md). @@ -21,7 +21,7 @@ What Ezno is not - Be on parity with TSC or 1:1, it has some different behaviors **but** should work in existing projects using TSC - 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. +- 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. Read more about Ezno (in chronological order) - [Introducing Ezno](https://kaleidawave.github.io/posts/introducing-ezno/) @@ -40,7 +40,6 @@ This project is a workspace consisting of a few crates: - ## Help contribute Check out [good first issues]((https://github.com/kaleidawave/ezno/issues?q=is%3Aopen+label%3Agood-first-issue%2Cfeedback-needed)) and comment on discussions! Feel free to ask questions on parts of the code of the checking implementation. diff --git a/checker/Cargo.toml b/checker/Cargo.toml index 87e52760..6800f69d 100644 --- a/checker/Cargo.toml +++ b/checker/Cargo.toml @@ -48,10 +48,12 @@ tsify = "0.4.5" # TODO needs unfixed change in source-map wasm-bindgen = "=0.2.89" - [dependencies.parser] path = "../parser" optional = true version = "0.1.5" features = ["extras"] package = "ezno-parser" + +[dev-dependencies] +match_deref = "0.1.1" diff --git a/checker/definitions/internal.ts.d.bin b/checker/definitions/internal.ts.d.bin index 8cfb2cd1..69dd2cab 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 83d4736d..abb1be71 100644 --- a/checker/definitions/overrides.d.ts +++ b/checker/definitions/overrides.d.ts @@ -1,10 +1,7 @@ -@Constant -declare function debug_type_independent(t: any): void; - // Eventually this will be merged with existing TS es5.d.ts files but for now is the standalone see #121 interface ImportEnv { - [key: string]: string | undefined; + [key: string]: string; } interface ImportMeta { @@ -14,7 +11,7 @@ interface ImportMeta { } declare class Array { - [index: number]: T | undefined; + [index: number]: T; length: number; @@ -33,8 +30,7 @@ declare class Array { return undefined } else { const value = this[--this.length]; - // TODO this currently breaks value? - // delete this[this.length]; + delete this[this.length]; return value } } @@ -101,35 +97,6 @@ declare class Array { return false } - // fill(value: T, start: number = 0, end = this.length): this { - // // TODO - // return this - // } - - // reduce(cb: (acc: U, t: T, i?: number) => U, initial?: U): U { - // const { length } = this; - // let acc = initial ?? this[0]; - // let i: number = typeof initial === "undefined" ? 1 : 0;; - // while (i < length) { - // const value = this[i]; - // acc = cb(acc, value, i++); - // } - // return acc; - // } - - // includes(searchElement: T, fromIndex?: number): boolean { - // const { length } = this; - // // TODO this is currently broken - // let i: number = fromIndex ?? 0; - // while (i < length) { - // const value = this[i++]; - // if (value === searchElement) { - // return true - // } - // } - // return false - // } - join(joiner: string = ","): string { const { length } = this; let i: number = 1; @@ -173,22 +140,28 @@ declare class Math { static sqrt(x: number): number; @Constant static cbrt(x: number): number; + @Constant + static log(x: number): number; // TODO newer method @Constant static trunc(x: number): number; static PI: 3.141592653589793 + static E: 2.718281828459045 + + @InputOutput + static random(): number; } @Primitive("string") declare class String { - [index: number]: string | undefined; + [index: number]: string; @Constant - toUpperCase(): string; + toUpperCase(this: string): string; @Constant - toLowerCase(): string; + toLowerCase(this: string): string; get length(): number; @@ -294,11 +267,11 @@ declare class SyntaxError extends Error { declare class JSON { // TODO any temp - @Constant("json:parse", SyntaxError) + @Constant("JSON:parse", SyntaxError) static parse(input: string): any; // TODO any temp - @Constant("json:stringify") + @Constant("JSON:stringify") static stringify(input: any): string; } @@ -308,7 +281,7 @@ declare class Function { declare class Symbols { // TODO temp - iterator: 199 + static iterator: unique symbol "iterator" } declare class Proxy { @@ -316,6 +289,18 @@ declare class Proxy { constructor(obj: any, cb: any); } +// Copied from `es5.d.ts`. Could this be an or +// TODO string keys temp because parser broke +interface PropertyDescriptor { + value?: any; + ["get" ? (): any; + ["set" ? (v: any): void; + + writable?: boolean; + configurable?: boolean; + enumerable?: boolean; +} + declare class Object { @Constant static setPrototypeOf(on: object, to: object): object; @@ -323,11 +308,19 @@ declare class Object { @Constant static getPrototypeOf(on: object): object | null; - // static create(prototype: object): object { - // const n = {}; - // Object.setProtoTypeOf(n, prototype); - // return n - // } + @Constant + static freeze(on: object): object; + + @Constant + static isFrozen(on: object): boolean; + + // TODO defineProperties via body (not constant) + @Constant + static defineProperty(on: object, property: string, discriminator: PropertyDescriptor): boolean; + + // TODO getOwnPropertyDescriptors via body (not constant) + @Constant + static getOwnPropertyDescriptor(on: object, property: string): PropertyDescriptor; static keys(on: { [s: string]: any }): Array { const keys: Array = []; @@ -353,6 +346,14 @@ declare class Object { return entries } + // TODO multiple arguments + static assign(target: object, source: object): object { + for (const key in source) { + target[key] = source[key] + } + return target + } + // static fromEntries(iterator: any): object { // const obj = {}; // for (const item of iterator) { @@ -415,6 +416,8 @@ declare function debug_context(): void; declare function context_id(): void; @Constant declare function context_id_chain(): void; +@Constant +declare function debug_type_independent(t: any): void; // A function, as it should be! @Constant diff --git a/checker/definitions/simple.d.ts b/checker/definitions/simple.d.ts index deb1120b..d5814b3d 100644 --- a/checker/definitions/simple.d.ts +++ b/checker/definitions/simple.d.ts @@ -5,6 +5,8 @@ declare function print_type(...args: Array): void @Constant declare function debug_type(...args: Array): void @Constant +declare function debug_type_independent(...args: Array): void +@Constant declare function print_and_debug_type(...args: Array): void @Constant declare function print_constraint(t: any): void @@ -38,7 +40,7 @@ declare function satisfies(t: T): T declare function compile_type_to_object(): any interface ImportEnv { - [key: string]: string | undefined; + [key: string]: string; } interface ImportMeta { @@ -48,13 +50,14 @@ interface ImportMeta { } declare class Array { - [index: number]: T | undefined; + [index: number]: T; length: number; push(item: T) { - this[this.length] = item; - return ++this.length + let at = this.length++; + this[at] = item; + return at + 1 } pop(): T | undefined { @@ -62,62 +65,83 @@ declare class Array { return undefined } else { const value = this[--this.length]; - // TODO this breaks things - // delete this[this.length]; - // debug_type_rust_independent(value); + delete this[this.length]; return value } } - // // TODO this argument + // map(cb: (t: T, i?: number) => U): Array { + // TODO this argument map(cb: (t: T, i?: number) => U): Array { const { length } = this, mapped: Array = []; let i: number = 0; while (i < length) { - const value = this[i]; - mapped.push(cb(value, i++)) + const value = this?.[i]; + const newValue = cb(value, i++); + mapped.push(newValue) } return mapped; } + // copy(): Array { + // const { length } = this, mapped: Array = []; + // let i: number = 0; + // while (i < length) { + // mapped.push(this?.[i]) + // } + // return mapped; + // } + + // // // TODO any is debatable + // filter(cb: (t: T, i?: number) => any): Array { + // const { length } = this, filtered: Array = []; + // let i: number = 0; + // while (i < length) { + // const value = this[i]; + // if (cb(value, i++)) { + // filtered.push(value) + // } + // } + // return filtered; + // } + // // TODO any is debatable - filter(cb: (t: T, i?: number) => any): Array { - const { length } = this, filtered: Array = []; - let i: number = 0; - while (i < length) { - const value = this[i]; - if (cb(value, i++)) { - filtered.push(value) - } - } - return filtered; - } + // find(cb: (t: T, i?: number) => any): T | undefined { + // const { length } = this; + // let i: number = 0; + // while (i < length) { + // const value = this[i]; + // if (cb(value, i++)) { + // return value + // } + // } + // } - // TODO any is debatable - find(cb: (t: T, i?: number) => any): T | undefined { - const { length } = this; - let i: number = 0; - while (i < length) { - const value = this[i]; - if (cb(value, i++)) { - return value - } - } - } + // // TODO any is debatable + // every(cb: (t: T, i?: number) => any): boolean { + // const { length } = this; + // let i: number = 0; + // while (i < length) { + // const value = this[i]; + // if (!cb(value, i++)) { + // return false + // } + // } + // // Vacuous truth + // return true + // } - // TODO any is debatable - every(cb: (t: T, i?: number) => any): boolean { - const { length } = this; - let i: number = 0; - while (i < length) { - const value = this[i]; - if (!cb(value, i++)) { - return false - } - } - // Vacuous truth - return true - } + // includes(looking_for: T): boolean { + // const { length } = this; + // let i: number = 0; + // while (i < length) { + // const value = this[i++]; + // if (value === looking_for) { + // return true + // } + // } + // return false + // } // some(cb: (t: T, i?: number) => any): boolean { // const { length } = this; @@ -131,35 +155,66 @@ declare class Array { // return false // } - join(joiner: string = ","): string { - const { length } = this; - let i: number = 1; - if (length === 0) { - return "" - } - let s: string = "" + this[0]; - while (i < length) { - s += joiner; - s += this[i++]; - } - return s - } + // join(joiner: string = ","): string { + // const { length } = this; + // let i: number = 1; + // if (length === 0) { + // return "" + // } + // let s: string = "" + this[0]; + // while (i < length) { + // s += joiner; + // s += this[i++]; + // } + // return s + // } - at(index: number) { - if (index < 0) { - return this[index + this.length] - } else { - return this[index] - } + // at(index: number) { + // if (index < 0) { + // return this[index + this.length] + // } else { + // return this[index] + // } + // } +} + +declare class Map { + #keys: Array; + #values: Array; + + constructor() { + this.#keys = [] + this.#values = [] } + + // get(key: K): V | undefined { + // // return this.#keys; + // const { length } = this.#keys; + // for (let i = 0; i < length; i++) { + // const s = length - 1 - i; + // if (this.#keys[s] === key) { + // return this.#values[s] + // } + // } + // } + + // set(key: K, value: V) { + // this.#keys.push(key); + // this.#values.push(value); + // } } type Record = { [P in K]: T } -declare class Map { - #keys: Array = []; - #value: Array = []; -} +/** + * Exclude from T those types that are assignable to U + */ +type Exclude = T extends U ? never : T; + +/** + * Extract from T those types that are assignable to U + */ +type Extract = T extends U ? T : never; declare class Math { @Constant @@ -174,22 +229,28 @@ declare class Math { static sqrt(x: number): number; @Constant static cbrt(x: number): number; + @Constant + static log(x: number): number; // TODO newer method @Constant static trunc(x: number): number; static PI: 3.141592653589793 + static E: 2.718281828459045 + + @InputOutput + static random(): number; } @Primitive("string") declare class String { - [index: number]: string | undefined; + [index: number]: string; @Constant - toUpperCase(): string; + toUpperCase(this: string): string; @Constant - toLowerCase(): string; + toLowerCase(this: string): string; get length(): number; @@ -233,16 +294,18 @@ declare class Error { } declare class SyntaxError extends Error { - constructor() { super("syntax error") } + constructor() { + super("syntax error"); + } } declare class JSON { // TODO any temp - @Constant("json:parse", SyntaxError) + @Constant("JSON:parse", SyntaxError) static parse(input: string): any; // TODO any temp - @Constant("json:stringify") + @Constant("JSON:stringify") static stringify(input: any): string; } @@ -250,9 +313,9 @@ declare class Function { bind(this_ty: any): Function; } -declare class Symbols { +declare class Symbol { // TODO temp - iterator: 199 + static iterator: unique symbol "iterator" } declare class Proxy { @@ -260,6 +323,18 @@ declare class Proxy { constructor(obj: any, cb: any); } +// Copied from `es5.d.ts`. Could this be an or +// TODO string keys temp because parser broke +interface PropertyDescriptor { + value?: any; + ["get" ? (): any; + ["set" ? (v: any): void; + + writable?: boolean; + configurable?: boolean; + enumerable?: boolean; +} + declare class Object { @Constant static setPrototypeOf(on: object, to: object): object; @@ -267,46 +342,37 @@ declare class Object { @Constant static getPrototypeOf(on: object): object | null; + @Constant + static freeze(on: object): object; + + @Constant + static isFrozen(on: object): boolean; + + // TODO defineProperties via body (not constant) + @Constant + static defineProperty(on: object, property: string, discriminator: PropertyDescriptor): boolean; + + // TODO getOwnPropertyDescriptors via body (not constant) + @Constant + static getOwnPropertyDescriptor(on: object, property: string): PropertyDescriptor; + // static create(prototype: object): object { // const n = {}; // Object.setProtoTypeOf(n, prototype); // return n // } +} - static keys(on: { [s: string]: any }): Array { - const keys: Array = []; - for (const key in on) { - keys.push(key); - } - return keys - } - - static values(on: { [s: string]: any }): Array { - const values: Array = []; - for (const key in on) { - values.push(on[key]); - } - return values - } - - static entries(on: { [s: string]: any }): Array<[string, any]> { - const entries: Array<[string, any]> = []; - for (const key in on) { - entries.push([key, on[key]]); - } - return entries - } - - // static fromEntries(iterator: any): object { - // const obj = {}; - // for (const item of iterator) { - // const { 0: key, 1: value } = item; - // obj[key] = value; - // } - // return obj - // } +declare class RegExp { + @Constant("RegExp:constructor") + constructor(s: string) } +// WIP +// interface SymbolImplementations { +// [Symbol.iterator]: () => { next(): { value: any, done: boolean } } +// } + // TODO wip declare function JSXH(tag: string, attributes: any, children?: any) { return { tag, attributes, children } diff --git a/checker/documentation/checking.md b/checker/documentation/checking.md index 3e8ac1c8..45f18617 100644 --- a/checker/documentation/checking.md +++ b/checker/documentation/checking.md @@ -23,12 +23,20 @@ Rather than checking whether types are subtypes of some base type, these are con ## Checking a program Starts with a list of statements. -Before any of the statements are checked, they are first *hoisted*. +Before any of the statements and declarations are checked, they are first *hoisted*. + +After that the checker performs synthesis and checking. It only visits expressions once, the binding happens at the same time as checking. ### Hoisting Both types and variables can be used before their declaration position in the source. Before checking statements, a group of passes 'lifts' certain information. -There are three stages to hoisting. +There are three stages to hoisting an item + +- Declaring an item +- Registering it +- Finishing it? + +There are four stages to (1) Firstly - Interface and type alias **names** are declared diff --git a/checker/examples/calculate_subtypes.rs b/checker/examples/calculate_subtypes.rs index 6ef83f4d..66270eef 100644 --- a/checker/examples/calculate_subtypes.rs +++ b/checker/examples/calculate_subtypes.rs @@ -16,7 +16,7 @@ fn main() { let mut types = TypeStore::default(); basics(&mut environment, &mut types); - contributions(&mut environment, &mut types) + contributions(&mut environment, &mut types); } fn basics(environment: &mut Environment, types: &mut TypeStore) { @@ -29,22 +29,22 @@ fn basics(environment: &mut Environment, types: &mut TypeStore) { { let result = type_is_subtype_object(TypeId::NUMBER_TYPE, five, environment, types); - eprintln!("number :> 5 {result:?}") + eprintln!("number :> 5 {result:?}"); } { let result = type_is_subtype_object(TypeId::NUMBER_TYPE, TypeId::STRING_TYPE, environment, types); - eprintln!("number :> string {result:?}") + eprintln!("number :> string {result:?}"); } { let result = type_is_subtype_object(string_or_number, TypeId::STRING_TYPE, environment, types); - eprintln!("string | number :> string {result:?}") + eprintln!("string | number :> string {result:?}"); } - eprintln!("--------------\n") + eprintln!("--------------\n"); } fn contributions(environment: &mut Environment, types: &mut TypeStore) { @@ -60,7 +60,6 @@ fn contributions(environment: &mut Environment, types: &mut TypeStore) { Publicity::Public, inner.clone(), PropertyValue::Value(generic_parameter.type_id), - false, source_map::SpanWithSource::NULL, ); @@ -77,11 +76,11 @@ fn contributions(environment: &mut Environment, types: &mut TypeStore) { &mut environment.info, ); basis.append( - environment, Publicity::Public, inner, PropertyValue::Value(five), source_map::SpanWithSource::NULL, + &mut environment.info, ); basis.build_object() }; @@ -96,7 +95,7 @@ fn contributions(environment: &mut Environment, types: &mut TypeStore) { others: SubTypingOptions::default(), object_constraints: None, }; - let result = type_is_subtype(parameter, five_obj, &mut state, &environment, &types); + let result = type_is_subtype(parameter, five_obj, &mut state, environment, types); let contributions = state.contributions.as_ref().unwrap(); diff --git a/checker/examples/generate_cache.rs b/checker/examples/generate_cache.rs index 27f4bba0..d7ccc711 100644 --- a/checker/examples/generate_cache.rs +++ b/checker/examples/generate_cache.rs @@ -17,5 +17,5 @@ fn main() { let cache = generate_cache::<_, EznoParser>(PathBuf::from(input).as_path(), &reader, ()); write(output, cache).unwrap(); - eprintln!("Cache generated 🏧💵✅") + eprintln!("Cache generated 🏧💵✅"); } diff --git a/checker/examples/run-checker.rs b/checker/examples/run_checker.rs similarity index 79% rename from checker/examples/run-checker.rs rename to checker/examples/run_checker.rs index 89e816a1..eee0dc63 100644 --- a/checker/examples/run-checker.rs +++ b/checker/examples/run_checker.rs @@ -1,7 +1,7 @@ #[cfg(feature = "ezno-parser")] fn main() { use ezno_checker::{check_project, synthesis, Diagnostic, TypeCheckOptions}; - use std::{collections::HashSet, fs, path::Path, time::Instant}; + use std::{fs, path::Path, time::Instant}; let default_path = Path::new("private").join("tocheck").join("aaa.tsx"); let simple_dts_path = Path::new("checker").join("definitions").join("simple.d.ts"); @@ -12,34 +12,43 @@ fn main() { let path = args .first() .and_then(|arg| (!arg.starts_with("--")).then_some(arg)) - .map(Path::new) - .unwrap_or(default_path.as_path()); + .map_or(default_path.as_path(), Path::new); let use_simple = args.iter().any(|item| item == "--simple-dts"); let no_cache = args.iter().any(|item| item == "--no-cache"); let debug_types = args.iter().any(|item| item == "--debug-types"); + let no_lib = args.iter().any(|item| item == "--no-lib"); + let debug_dts = args.iter().any(|item| item == "--debug-dts"); let now = Instant::now(); let resolver = |path: &std::path::Path| fs::read(path).ok(); - let definition_file = if use_simple { - simple_dts_path.to_path_buf() - } else if no_cache { - overrides_dts_path.to_path_buf() + let type_definition_files = if no_lib { + Vec::new() } else { - ezno_checker::INTERNAL_DEFINITION_FILE_PATH.into() + let definition_file = if use_simple { + simple_dts_path.clone() + } else if no_cache { + overrides_dts_path.clone() + } else { + ezno_checker::INTERNAL_DEFINITION_FILE_PATH.into() + }; + vec![definition_file] }; - let type_definition_files = HashSet::from_iter([definition_file]); + + let entry_points = vec![path.to_path_buf()]; let options = TypeCheckOptions { debug_types, record_all_assignments_and_reads: true, + max_inline_count: 600, + debug_dts, ..Default::default() }; let result = check_project::<_, synthesis::EznoParser>( - vec![path.to_path_buf()], + entry_points, type_definition_files, resolver, options, @@ -49,7 +58,8 @@ fn main() { if args.iter().any(|arg| arg == "--types") { eprintln!("Types:"); - for (type_id, item) in result.types.into_vec_temp() { + let types = result.types.into_vec_temp(); + for (type_id, item) in &types[types.len().saturating_sub(60)..] { eprintln!("\t{type_id:?}: {item:?}"); } } diff --git a/checker/specification/README.md b/checker/specification/README.md index a2482f1b..17f841f6 100644 --- a/checker/specification/README.md +++ b/checker/specification/README.md @@ -9,6 +9,10 @@ These can all be ran as tests using the markdown to Rust test code transpiler in - Comments can be in block quotes to explain additional details in the tests - Sections are at level three headings (`###`), tests are at level four headings (`####`), the tested code goes a code block with the language tag `ts` and errors in a bullet list after in order - Blocks can be split into files with a `// in file.ts` comment, below which all code is in the `file.ts` file. Default is `main.tsx` +- **Code in blocks is indented with tabs not spaces** --- Other tests unrelated to specific checking features can be found in `checker/tests` + +> [!IMPORTANT] +> Any tests that throw or produce unreachables must be contained as it breaks the total concatenation. diff --git a/checker/specification/build.rs b/checker/specification/build.rs index b860a076..e31236a4 100644 --- a/checker/specification/build.rs +++ b/checker/specification/build.rs @@ -134,7 +134,8 @@ fn markdown_lines_append_test_to_rust( fn heading_to_rust_identifier(heading: &str) -> String { heading - .replace([' ', '-', '/', '&', '.', '+'], "_") - .replace(['*', '\'', '`', '"', '!', '(', ')', ','], "") + .replace("...", "") + .replace([' ', '-', '/', '.', '+'], "_") + .replace(['*', '\'', '`', '"', '&', '!', '(', ')', ','], "") .to_lowercase() } diff --git a/checker/specification/specification.md b/checker/specification/specification.md index f7838ac9..fc791100 100644 --- a/checker/specification/specification.md +++ b/checker/specification/specification.md @@ -46,10 +46,11 @@ a satisfies number #### Variable references does not exist ```ts -const a = c +const exists = 2; +nexists ``` -- Could not find variable 'c' in scope. Did you mean a? +- Could not find variable 'nexists' in scope #### Assigning before declaration @@ -107,6 +108,34 @@ b satisfies string; - Expected string, found undefined +#### TDZ in statements + +```ts +let first = second; +let second = 2; +``` + +- Variable 'second' used before declaration + +#### `var` can be reregistered + +```ts +{ + // Fine + var x = 2; + x satisfies 2; + var x = 3; + x satisfies 3; +} + +{ + let b = 2; + var b = 2; +} +``` + +- Cannot redeclare variable 'b' + ### Properties #### Property exists @@ -117,7 +146,7 @@ const a = my_obj.a const b = my_obj.b ``` -- No property 'b' on { a: 3 }. Did you mean a? +- No property 'b' on { a: 3 } #### Reading property (via accessor) @@ -149,7 +178,7 @@ const my_obj: { b: 3 } = { b: 4 } #### Getters ```ts -let global = 0; +let global: number = 0; const object = { // This getter has an impure side effect get value() { @@ -185,7 +214,7 @@ object.value satisfies string let a = 2; const obj = { x: 5, - set value(v) { + set value(this: { x: number }, v) { this.x = v; } } @@ -200,13 +229,15 @@ obj.x satisfies 5; ```ts const obj = { - set value(v: string) { } + set value(v: string) { } } obj.value = 5; ``` -- Type 5 does not meet property constraint string +> TODO could the error be better? + +- Argument of type 5 is not assignable to parameter of type string (in setter) #### Setter side effect @@ -253,9 +284,9 @@ obj2.a satisfies boolean; #### Set property with key ```ts -const obj = { a: 2 } +const obj: { a?: number, b?: number } = { a: 2 } -function setProperty(key: string, value) { +function setProperty(key: "a" | "b", value: number) { obj[key] = value; } @@ -273,7 +304,7 @@ delete x.b; const b = x.b; ``` -- No property 'b' on { a: 2 }. Did you mean a? +- No property 'b' on { a: 2 } #### `Object.keys`, `Object.values`, `Object.entries` @@ -291,11 +322,11 @@ Object.entries({ a: 1, b: 2 }) satisfies boolean; declare let condition: boolean; const obj = { - foo: 1, - ...(condition ? { - bar: 2, - non_existent: 3, - } : {}), + foo: 1, + ...(condition ? { + bar: 2, + non_existent: 3, + } : {}), }; obj.foo satisfies number; @@ -304,7 +335,305 @@ obj.bar satisfies string; - Expected string, found 2 | undefined -### Excess property +#### And on properties + +> Note that it keeps it as a `and`. It does not join the properties into a single typ + +```ts +declare type U = { a: 2 } & { b: 3 } +declare let x: U; +x.b satisfies 3; + +({ a: 2, b: 3 } satisfies U); +({ b: 3 } satisfies U); +``` + +- Expected U, found { b: 3 } + +#### Properties on or + +```ts +declare let key: "a" | "b"; + +const object = { a: "apple", b: "banana" }; +object[key] satisfies boolean; +``` + +- Expected boolean, found "apple" | "banana" + +#### Properties on big or + +> TODO this creates a fat or type + +```ts +const array = [1, 2, 3]; +array[Math.random()] satisfies string; +``` + +- Expected string, found 1 | 2 | 3 | undefined + +#### Properties matched against continous type become conditional + +> I think this is TSC behavior under indexed access + +```ts +declare let strings: { [a: string]: number }; +declare let record: Record; +declare let d: { [a: "a" | "b"]: number }; + +strings.a satisfies number | undefined; +record.a satisfies boolean; +d.a satisfies number; +``` + +- Expected boolean, found number | undefined + +#### Un-delete-able property + +> TODO in a function as well + +```ts +const x: { a?: number } = { a: 4 }; +// Fine +delete x.a; + +const y: { a: number } = { a: 4 }; +// Bad +delete y.a; + +const z = {}; +Object.defineProperty(z, "a", { value: 4 }); +delete z.a; +``` + +- Cannot delete from object constrained to { a: number } +- Cannot delete from non-configurable property + +#### Order of numerical properties + +> TODO test could be better using `for in` or `Object.keys` etc + +```ts +let x = {}; x.something = null; x[4] = null; x["eight"] = null; x["2"] = null; +x satisfies string; +``` + +- Expected string, found { 2: null, 4: null, something: null, eight: null } + +#### Order of properties after assignment + +> TODO this is because setting properties are simply appended. There are two straightforward fixes, but I am unsure which one is better... + +```ts +const obj = { a: 1, b: 2 }; +obj.a = 2; obj.c = 6; obj.b = 4; +obj satisfies boolean; +``` + +- Expected boolean, found { a: 2, b: 4, c: 6 } + +#### Assigning to getter + +```ts +const obj = { get prop() { return 2 } }; +obj.prop = "hi"; +obj.prop satisfies 2; +``` + +- Cannot write to property 'prop' as it is a getter + +#### Assinging to non existent property + +> Allowing this could break objects passed to functions + +```ts +const obj = { prop: 1 }; +// Fine +obj.non_existent = 6; + +function func(param: { prop: number }) { + param.notProp = 5; +} +``` + +- Cannot write to non-existent property 'notProp' + +#### Function and class name + +> TODO should also check that it is readonly + +```ts +function a() { } +class B { } +let c = class { } + +a.name satisfies "a" +B.name satisfies "B" +c.name satisfies "sea" +``` + +- Expected "sea", found "c" + +#### Getters AND setter + +> This involves property lookup skipping setter and getters + +```ts +let global: number = 0; +const object = { + get value() { + return global + }, + set value(newValue: number) { + global = newValue; + } +} + +object.value satisfies string; +object.value = 10; +object.value satisfies 10; +global satisfies 10 +``` + +- Expected string, found 0 + +#### Getters AND setter can be type via `Object.defineProperty` + +> TODO parameter checking as well + +```ts +function func(get: () => number) { + const obj = {}; + Object.defineProperty(obj, "value", { get }); + obj.value satisfies string +} +``` + +- Expected string, found number + +#### `enumerable` in for in + +```ts +const obj = { n: 1, b: 2 }; +Object.defineProperty(obj, "c", { value: 3, enumerable: false }); +Object.defineProperty(obj, "d", { value: 4, enumerable: true }); + +let keys: string = ""; +for (const key in obj) { + keys += key; +} +keys satisfies boolean +``` + +- Expected boolean, found "nbd" + +#### `Object.freeze` + +> TODO seal & preventExtensions + +```ts +const obj = {} +let result = Object.freeze(obj); +(obj === result) satisfies true; +obj.property = 2; +Object.isFrozen(obj) satisfies true; +``` + +> TODO maybe error should say that whole object is frozen + +- Cannot write to property 'property' + +#### `Object.defineProperty` writable + +> TODO defineProperties + +```ts +const obj = {}; +Object.defineProperty(obj, 'property', { + value: 42, + writable: false, +}); +obj.property satisfies string; +obj.property = 70; +``` + +- Expected string, found 42 +- Cannot write to property 'property' + +#### Descriptor carries across assignments + +> TODO defineProperties + +```ts +const obj = {}; +Object.defineProperty(obj, 'property', { + value: 42, + enumerable: false, + // needed as all properties default to false + writable: true, +}); +obj.property = 70; +Object.getOwnPropertyDescriptor(obj, 'property') satisfies string; +``` + +- Expected string, found { value: 70, writable: true, enumerable: false, configurable: false } + +#### `Object.defineProperty` getter and setter + +> TODO setter + +```ts +const obj = {}; +let b = 0; +Object.defineProperty(obj, 'property', { + get: () => b, +}); +obj.property satisfies 0; +b++; +obj.property satisfies string; +``` + +- Expected string, found 1 + +#### `Object.defineProperty` configurable + +```ts +const obj = {}; +Object.defineProperty(obj, 'property', { value: 6 }); +Object.defineProperty(obj, 'property', { value: "hi" }); +``` + +- Property 'property' not configurable + +#### `Object.getOwnPropertyDescriptor` + +> TODO getOwnPropertyDescriptors + +```ts +const obj = { a: "something" }; +Object.defineProperty(obj, 'b', { value: 42 }); + +Object.getOwnPropertyDescriptor(obj, 'a') satisfies string; +Object.getOwnPropertyDescriptor(obj, 'b').writable satisfies false; +``` + +> Order is also important + +- Expected string, found { value: "something", writable: true, enumerable: true, configurable: true } + +#### `Object.assign` + +> TODO multiple RHS + +```ts +const obj = { a: 1 }; +Object.assign(obj, { b: 2, c: 3 }); +obj satisfies string; +``` + +- Expected string, found { a: 1, b: 2, c: 3 } +s +### Excess properties > The following work through the same mechanism as forward inference > Thanks to pull request: #139 @@ -327,9 +656,14 @@ interface MyObject { property: string } function process(param: MyObject) {} process({ property: "hello", another: 2 }); + +function func(a: T) { } + +func({ property: "hello", "something else": 2 }) ``` - 'another' is not a property of MyObject +- 'something else' is not a property of MyObject #### Excess property at return type @@ -349,21 +683,21 @@ function returnNewObject(): MyObject { type MyObject = { foo: number; bar?: number }; const b: MyObject = { - foo: 1, - ...{ - bar: 2, - invalid: 3, - }, + foo: 1, + ...{ + bar: 2, + invalid: 3, + }, }; declare let condition: boolean; const c: MyObject = { - foo: 1, - ...(condition ? { - bar: 2, - non_existent: 3, - } : {}), + foo: 1, + ...(condition ? { + bar: 2, + non_existent: 3, + } : {}), }; ``` @@ -494,8 +828,8 @@ function func(a: number) { ```ts function outer(a: number) { - function inner(b: string = Math.floor(a)) { - } + function inner(b: string = Math.floor(a)) { + } } ``` @@ -511,30 +845,6 @@ function func(): string { - Cannot return 2 because the function is expected to return string -#### *Inferred* return type - -```ts -function func() { - return 2 -} -func satisfies () => string -``` - -- Expected () => string, found () => 2 - -#### Set property on dependent observed - -```ts -function add_property(obj: { prop: number }) { - obj.prop = 2; - (obj.prop satisfies 4); -} -``` - -> Not number - -- Expected 4, found 2 - #### Type checking basic function types ```ts @@ -549,18 +859,6 @@ func satisfies (a: number, b: number) => boolean; - Expected (a: string, b: number) => string, found (a: string, b: number) => boolean - Expected (a: number, b: number) => boolean, found (a: string, b: number) => boolean -#### Function that throws returns never - -```ts -function myThrow() { - throw "err!" -} - -myThrow satisfies string; -``` - -- Expected string, found () => never - #### Return generics mismatch ```ts @@ -611,7 +909,7 @@ function createObject2(a: T, b: U): { a: U, b: U } { const x: (a: string) => number = a => a.to; ``` -- No property 'to' on string. +- No property 'to' on string #### Expected argument from parameter declaration @@ -623,7 +921,7 @@ function map(a: (a: number) => number) {} map(a => a.t) ``` -- No property 't' on number. +- No property 't' on number #### Assignment to parameter @@ -678,11 +976,11 @@ myFunction({ a: 6 }) satisfies string; ```ts function getNumber1(): number { - return 4 + return 4 } function getNumber2() { - return 6 + return 6 } getNumber1 satisfies () => 4; @@ -701,20 +999,106 @@ getNumber2() satisfies 6; ```ts function x() { - getString(3) + getString(3) } function y() { - getString("something") satisfies string; + getString("something") satisfies string; } function getString(param: string): string { - return "hi" + return "hi" } ``` - Argument of type 3 is not assignable to parameter of type string +### Inferred return types + +#### Simple + +```ts +function id(a: number) { + return a +} + +function simple() { + return "hello world" +} + +id satisfies (n: number) => number; +simple satisfies () => number; +``` + +- Expected () => number, found () => "hello world" + +#### Conditional + +```ts +function func(condition: T) { + if (condition) { + return 4 + } else { + return 3 + } +} + +func satisfies string; +``` + +> There are some issues around printing here, when to include the generic etc + +- Expected string, found \(condition: T) => T ? 4 : 3 + +#### Early return + +```ts +function func(value: number) { + if (value === 3) { + return "is three" + } + console.log("hi") + return "another" +} + +loop satisfies (a: number) => "is three" | "another"; + +function loop(value: number) { + for (let i = 0; i < 10; i++) { + if (value === i) { + return "something" + } + } + return "another" +} + +loop satisfies (a: number) => "something" | "another"; + +function sometimes(a: boolean) { + if (a) { + return "sometimes" + } +} + +sometimes satisfies (a: boolean) => string; +``` + +- Expected (a: boolean) => string, found (a: boolean) => "sometimes" | undefined + +#### `throw` in body + +```ts +function throwSomething() { + throw "to implement!" +} + +throwSomething satisfies string; +``` + +- Expected string, found () => never + +> #TODO try-catch, Promise + ### Function calling #### Argument type against parameter @@ -913,13 +1297,13 @@ const m = new MyClass(); ```ts function myRestFunction(...r: string[]) { - return r[0] + r[1] + return r } myRestFunction("hello ", "world") satisfies number; ``` -- Expected number, found "hello world" +- Expected number, found ["hello ", "world"] #### Default parameter @@ -938,11 +1322,11 @@ withDefault(3) satisfies 3; ```ts let b: number = 0 -function doThing(a = (b += 2)) { +function doThing(a: number = (b += 2)) { return a } -doThing("hello"); +doThing(7); b satisfies 0; doThing(); b satisfies 1; @@ -956,7 +1340,7 @@ b satisfies 1; ```ts function optionally(p?: number) { - p satisfies string; + p satisfies string; } ``` @@ -966,7 +1350,7 @@ function optionally(p?: number) { ```ts function optionally(p?: number) { - return p + return p } // Fine @@ -978,23 +1362,10 @@ optionally("hello world"); - Argument of type "hello world" is not assignable to parameter of type number | undefined -#### Tagged template literal - -```ts -function myTag(static_parts: Array, name: string) { - return static_parts[0] + name -} - -const name = "Ben"; -myTag`${name}Hello ` satisfies "Hi Ben" -``` - -- Expected "Hi Ben", found "Hello Ben" - #### Default parameter side effect on parameter ```ts -function doThing(a, b = (a += 2)) { +function doThing(a: number, b: number = (a += 2)) { return a } @@ -1027,6 +1398,64 @@ myTag`Count is ${"not a number!!"}`; - Argument of type \"not a number!!\" is not assignable to parameter of type number (in template literal) +#### Generics on dependent type + +> Weird annotation here is to work around [#165](https://github.com/kaleidawave/ezno/issues/165) + +```ts +function createNew(cb: { f(t: T): { a: T }}["f"]) { + return cb(4) +} + +createNew satisfies string; +``` + +- Expected string, found (cb: \(t: T) => { a: T }) => { a: 4 } + +#### Builder pattern + +> Testing for `this` returning + +```ts +class StringBuilder { + s: string = "" + + append(s: string) { + this.s += s; + return this + } + + finish() { + return this.s + } +} + +(new StringBuilder).append("Hello ").append("Ben").finish() satisfies number +``` + +- Expected number, found "Hello Ben" + +#### Dependent operations + +```ts +function isFive(a: number): boolean { + return a === 5 +} + +isFive(5) satisfies true; +isFive(6) satisfies string; + +function hasPropertyX(obj: object): boolean { + return "x" in obj; +} + +hasPropertyX({ a: 2 }) satisfies false; +hasPropertyX({ x: 5 }) satisfies number; +``` + +- Expected string, found false +- Expected number, found true + ### Effects > Side effects of functions. Registered internally as `Event`s @@ -1130,6 +1559,20 @@ let x: number = 5; > Not shown in the example but thanks to [#69](https://github.com/kaleidawave/ezno/pull/69) for adding the position of the error +#### TDZ errors through nested getter + +```ts +function func(obj: { prop: number }) { + return obj.prop +} + +func({ get prop() { return b } }); + +let b: number = 0; +``` + +- Variable 'b' used before declaration + #### Assignment to union > Solves the common subtyping issue between read and write properties @@ -1151,7 +1594,7 @@ setAtoString({ a: 6 }); setAtoString(myObject); ``` -- Invalid assignment to parameter +- Invalid assignment through parameter > Error message could be better. Full diagnostic contains labels with more information > `readA` is allowed, which is disallowed in Hegel, but here is allowed to preserve TSC compatibility (and because how structural subtyping is implemented) @@ -1201,6 +1644,118 @@ const c = a; - Expected false, found true - Expected string, found false +#### Unconditional throw warning + +```ts +function safeDivide(num: number, denom: number) { + if (denom === 0) { + throw new Error("Cannot divide by zero"); + } + return num / denom +} + +function func() { + safeDivide(8, 4) satisfies 2; + // ahh + safeDivide(10, 0); +} +``` + +- Conditional '[Error] { message: \"Cannot divide by zero\" }' was thrown in function + +#### Unreachable statement + +```ts +function throwGreeting() { + throw "Hello"; + return "Unreachable!" +} + +function doSomething() { + throwGreeting() + const unreachable = 2; +} +``` + +> One is for `return 5` the other is for `const x = 2;` + +- Unreachable statement +- Unreachable statement + +#### `throw` short-circuit + +```ts +let x: number = 2; + +function func(cb: () => void) { + try { + cb(); + x = 10; + return "not-thrown" + } catch { + return "thrown" + } +} + +func(() => { throw "error" }) satisfies "thrown"; +x satisfies string; +``` + +- Expected string, found 2 + +#### `delete` as an effect + +```ts +function dewete(param: { prop?: string }) { + const { prop } = param; + delete param.prop; + return prop +} + +const obj = { prop: "hi" }; +dewete(obj); + +obj.prop; +``` + +> This error *should* be from the last statement + +- No property 'prop' on {} + +#### Optional effect key + +```ts +let i: number = 0; +({ a: true})?.[i++, "a"] satisfies true; +i satisfies 1; + +null?.[i++, "a"]; +i satisfies string; +``` + +- Expression is always false +- Expression is always true +- Expected string, found 1 + +#### Effects across functions + +```ts +let value: number = 2; +function a() { value = 8; } +function b() { a() } + +let func = () => {}; + +function c() { b() } +function d(newCb: () => void, then: () => void) { func = newCb; then() } + +value satisfies 2; +d(a, c); +value satisfies boolean; +``` + +- Expected boolean, found 8 + ### Closures #### Reading variable @@ -1268,6 +1823,46 @@ value.getValue() satisfies 6 - Expected 6, found 10 +#### Class constructors + +```ts +function func(a: number, b: number) { + return class { + value: number; + + constructor() { + this.value = a; + } + + plusB() { + return this.value + b + } + } +} + +const c1 = new (func(1, 2)); +c1.plusB() satisfies 3; + +const c2 = new (func(6, 8)); +c2.plusB() satisfies string; +``` + +- Expected string, found 14 + +#### Getters closures + +```ts +function Closure(n: string) { + return { get value() { return n }, set value(newValue: string) { n = newValue; } }; +} + +let b = Closure("hi"); +b.value = "something"; +b.value satisfies number; +``` + +- Expected number, found "something" + ### Control flow #### Resolving conditional @@ -1363,9 +1958,9 @@ b satisfies string; let i = 0; declare let b: boolean; if (b) { - i = 1 + i = 1 } else { - i = 2 + i = 2 } i satisfies string; @@ -1379,10 +1974,10 @@ i satisfies string; declare let string: string; function stringIsHi(s: string) { - if (s === "hi") { - return true - } - return false + if (s === "hi") { + return true + } + return false } stringIsHi(string) satisfies number; @@ -1395,8 +1990,7 @@ stringIsHi(string) satisfies number; #### While loop unrolling ```ts -let a = 1; -let i = 0; +let a: number = 1, i: number = 0; while (i < 5) { a *= 2; i++; @@ -1410,8 +2004,7 @@ a satisfies 8; #### While loop event in the condition ```ts -let a = 1; -let i = 0; +let a: number = 1, i: number = 0; while (i++ < 5) { a *= 2; } @@ -1424,7 +2017,7 @@ a satisfies 8; #### Do while loop ```ts -let a = 0; +let a: number = 0; do { a++ } while (a < 3) @@ -1497,8 +2090,7 @@ loop(10, "!") satisfies number; #### Break in a while loop ```ts -let a = 2; -let i = 0; +let a: number = 2, i: number = 0; while (i++ < 10) { a *= 2; if (a > 5) { @@ -1516,8 +2108,7 @@ a satisfies 2; > With the continue the update to `a` only happens on even runs (5 times) ```ts -let a = 2; -let i = 0; +let a: number = 2, i: number = 0; while (i++ < 10) { if (i % 2) { continue; @@ -1601,7 +2192,7 @@ console.log("Error caught!") try { throw 3 } catch (err: string) { - console.log(err) + console.log(err) } console.log("Error caught!") @@ -1613,11 +2204,11 @@ console.log("Error caught!") ```ts function exceptionToResult(cb: () => number) { - try { - return cb() - } catch (e) { - return e - } + try { + return cb() + } catch (e) { + return e + } } exceptionToResult(() => 6) satisfies 6; @@ -1631,11 +2222,11 @@ console.log("Error caught!") ```ts function exceptionToResult(cb: () => number) { - try { - cb() - } catch (e: number) { - return e - } + try { + cb() + } catch (e: number) { + return e + } } exceptionToResult(() => { throw "not a number" }); @@ -1648,17 +2239,58 @@ console.log("Error caught!") ```ts function exceptionToResult(s: string) { - try { - return JSON.parse(s) - } catch (e: number) { - return e - } + try { + return JSON.parse(s) + } catch (e: number) { + return e + } } console.log("Error caught!") ``` - Cannot catch type number because the try block throws SyntaxError +#### Conditional throw + +> This emits a warning if a throw was created in a conditional branch + +```ts +// no complex numbers :( +function checkedLn(x: number) { + if (x > 0) { + return Math.log(x) + } else { + throw new Error("Cannot log") + } +} + +// Fine +try { checkedLn(Math.E ** 3) satisfies 3 } catch {} +// Will throw +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. @@ -1667,9 +2299,9 @@ console.log("Error caught!") #### Array push ```ts -const x = [1] -x.push("hi") -x[1] satisfies 3 +const x = [1]; +x.push("hi"); +x[1] satisfies 3; x.length satisfies 4; ``` @@ -1695,18 +2327,31 @@ x.push("hi"); - Argument of type \"hi\" is not assignable to parameter of type number -#### `map` and `filter` +#### Array map > TODO other arguments (index and `this`) ```ts [6, 8, 10].map(x => x + 1) satisfies [7, 8, 11]; - -[1, 2, 3].filter(x => x % 2 === 0) satisfies [2]; ``` - Expected [7, 8, 11], found [7, 9, 11] +#### Mutation + +> This is part of [assignment mismatch](https://github.com/kaleidawave/ezno/issues/18) + +```ts +function fakeRead(a: Array) { + a.push(2) +} + +const array1: Array = [] +fakeRead(array1) +``` + +- Invalid assignment through parameter + ### Statements, declarations and expressions > Some of these are part of synthesis, rather than checking @@ -1856,7 +2501,7 @@ y.not_a_key ``` - Expected 3, found 7 -- No property 'not_a_key' on { ezno: 7 }. +- No property 'not_a_key' on { ezno: 7 } #### Shorthand object literal @@ -1925,15 +2570,34 @@ type X = { a: string } - Type { b: "NaN" } is not assignable to type X -#### TDZ in statements +#### As casts + +> Disabled normally, allowed for these tests. Provides TSC compatibility and because narrowing not implemented (including secret feature) ```ts -let first = second; +declare let global: any; -let second = 2; +5 as boolean; +global satisfies boolean; +(global as string) satisfies number; ``` -- Variable 'second' used before declaration +- Cannot cast 5 to boolean +- Expected boolean, found any +- Expected number, found string + +#### Non-null assertions + +> TODO this currently only works on conditionals + +```ts +declare const global: { property?: string }; + +global.property satisfies string | undefined; +global.property! satisfies number; +``` + +- Expected number, found string #### `typeof` operator @@ -1968,16 +2632,16 @@ s satisfies number; ```ts const o = { a: 1, b: { c: 3 } }; -let a, b, c; +let a, b, d; ({ - c = o.a++, - b: { c: b = 7 }, - a, + d = o.a++, + b: { c: b = 7 }, + a, } = o); a satisfies string; b satisfies boolean; -c satisfies 3; +d satisfies 3; ``` - Expected string, found 2 @@ -1986,7 +2650,7 @@ c satisfies 3; #### `import.meta` -> Unfortunately because of bundling `url` and `resolve` cannot have known results so just `string`. +> Unfortunately because of bundling `url` and `resolve` cannot have known results so just `string` ```ts import.meta.url satisfies number; @@ -2054,9 +2718,9 @@ x.value satisfies string ```ts class X { - method() { - return this; - } + method() { + return this; + } } const { method } = new X(); @@ -2178,15 +2842,26 @@ class X { } - Variable 'X' used before declaration +#### Called without new + +> Declared same as `let` and `const` + +```ts +class X { } +const x = X(); +``` + +- Class constructor must be called with new + #### Class type `extends` ```ts class BaseClass { - b: boolean = false + b: boolean = false } class Class extends BaseClass { - a: number = 2 + a: number = 2 } new Class().b satisfies 5 @@ -2198,11 +2873,11 @@ new Class().b satisfies 5 ```ts class X { - static x = 2; + static x = 2; - static { - const property: 4 = ++this.x; - } + static { + const property: 4 = ++this.x; + } } X.x satisfies 3; @@ -2214,19 +2889,19 @@ X.x satisfies 3; ```ts function doThingWithClass(instance: Class) { - instance.prop satisfies string; - instance.parent_prop satisfies boolean; - instance.method(4); + instance.prop satisfies string; + instance.parent_prop satisfies boolean; + instance.method(4); } class BaseClass { - parent_prop: number + parent_prop: number } class Class extends BaseClass { - prop: number + prop: number - method(s: string) {} + method(s: string) {} } ``` @@ -2238,16 +2913,16 @@ class Class extends BaseClass { ```ts function doThingWithClass(instance: Class) { - instance.a satisfies number; - instance.b satisfies string; + instance.a satisfies number; + instance.b satisfies string; } class BaseClass { - b: boolean + b: boolean } class Class extends BaseClass { - a: number + a: number } ``` @@ -2258,16 +2933,16 @@ class Class extends BaseClass { ```ts let b: number = 0; class Y { - constructor(a) { - this.a = a; - b++; - } + constructor(a) { + this.a = a; + b++; + } } class X extends Y { - constructor(a) { - super(a); - } + constructor(a) { + super(a); + } } const x = new X("hi"); @@ -2300,7 +2975,7 @@ type X = number; const a: Y = 2; ``` -- Cannot find type Y. Did you mean T, U or X? +- Could not find type 'Y' #### Type shadowing @@ -2348,14 +3023,16 @@ const b: X = 2; #### Property on an or type ```ts -function getProp(obj: { prop: 3 } | { prop: 2 }) { +function getProp(obj: { prop: 3, prop2: 6 } | { prop: 2 }) { + obj.prop2; return obj.prop } getProp satisfies string ``` -- Expected string, found (obj: { prop: 3 } | { prop: 2 }) => 3 | 2 +- No property 'prop2' on { prop: 3, prop2: 6 } | { prop: 2 } +- Expected string, found (obj: { prop: 3, prop2: 6 } | { prop: 2 }) => 3 | 2 #### Generic extends @@ -2384,10 +3061,23 @@ const y: (a: number | string) => string = (p: number) => "hi" > I think reasons contains more information +#### Function parameter excess allowed + +```ts +// Perfectly fine +const x: (a: number, b: string) => string = (p: number) => "hi" +// Bad +const y: (a: string) => string = (p: number, q: string) => "hi" +``` + +- Type (p: number, q: string) => "hi" is not assignable to type (a: string) => string + +> I think reasons contains more information + #### Function return type subtyping ```ts -const x: (a: number) => number = p => 4 +const x: (a: number) => number = p => 4; const y: (a: number) => number = p => "a number" ``` @@ -2468,22 +3158,6 @@ getSecondCharacter("string") satisfies "b"; - Expected boolean, found (s: string) => string | undefined - Expected "b", found "t" -#### As casts - -> Disabled normally, allowed for these tests. Provides TSC compatibility and because narrowing not implemented (including secret feature) - -```ts -declare let global: any; - -5 as boolean; -global satisfies boolean; -(global as string) satisfies number; -``` - -- Cannot cast 5 to boolean -- Expected boolean, found any -- Expected number, found string - #### Symmetric or ```ts @@ -2531,13 +3205,13 @@ function get(obj: {a: 2} | { b: 3 }) { > `Cannot read property "a" from { b: 3 }` -- No property 'a' on { a: 2 } | { b: 3 }. +- No property 'a' on { a: 2 } | { b: 3 } #### Optional interface member ```ts interface Optional { - a?: "hi" + a?: "hi" } const op1: Optional = {} @@ -2556,19 +3230,43 @@ type Y = string & number; - No intersection between types 2 and "hi" - No intersection between types string and number +#### Cyclic type alias + +```ts +type Node = { parent: Node, value: T } | null; + +null satisfies Node; +({ parent: { parent: { parent: null, value: 2 }, value: 6 }, value: 2 } satisfies Node); +({ parent: { parent: { parent: null, value: "hi" }, value: 6 }, value: "hi" } satisfies Node); +``` + +- Expected { parent: Node\, value: string } | null, found { parent: { parent: { parent: null, value: "hi" }, value: 6 }, value: "hi" } + +#### Cyclic type alias check + +```ts +type X = Y; +type Y = X; + +// test usage doesn't blow up subtyping +const x: X = 2; +``` + +- Circular type reference + #### Interface extends ```ts interface X { - a: string + a: string } interface Y { - b: string + b: string } interface Z extends X, Y { - c: string + c: string } ({ a: "", b: "", c: "hello" }) satisfies Z; @@ -2579,6 +3277,18 @@ interface Z extends X, Y { - Expected Z, found { a: "", b: 4, c: "hello" } - Expected Z, found { c: "hi" } +#### `never` subtyping + +```ts +function getSpecialNumber(): number { + throw "to implement!" +} + +getSpecialNumber satisfies string; +``` + +- Expected string, found () => number + #### Specialisation of return for declare functions ```ts @@ -2632,14 +3342,14 @@ func() satisfies boolean; ```ts type ElementOf = T extends Array ? U : never; -declare let y: ElementOf>; -declare let z: ElementOf | string>; +declare let elementOfNumber: ElementOf>; +declare let elementOfNumberOrString: ElementOf<"not array" | Array>; -y satisfies number; -z satisfies string; +elementOfNumber satisfies number; +elementOfNumberOrString satisfies string; declare let n: never; -n satisfies ElementOf; +n satisfies ElementOf<"not array">; ``` - Expected string, found number @@ -2648,13 +3358,11 @@ n satisfies ElementOf; ```ts interface X { - a: string, - b: string + a: string, + b: string } -"a" satisfies keyof X; -"b" satisfies keyof X; -"c" satisfies keyof X; +"a" satisfies keyof X; "b" satisfies keyof X; "c" satisfies keyof X; ``` - Expected keyof X, found "c" @@ -2673,11 +3381,67 @@ const second: Introduction = "Hi Ben"; #### Assigning to types as keys ```ts -const x: { [s: string]: number } = { a: 1, b: 2, c: 3 } -const y: { [s: string]: boolean } = { a: 1, b: 2, c: 3 }; +const obj = { a: 1, b: 2, c: 3 }; +obj satisfies { [s: string]: number }; +obj satisfies { [s: string]: boolean }; +``` + +- Expected { [string]: boolean }, found { a: 1, b: 2, c: 3 } + +#### String slice matching pattern + +```ts +type GetPrefix = S extends `${infer T} ${End}` ? T : false; + +4 satisfies GetPrefix<"Hello Ben", "Ben">; +``` + +- Expected "Hello", found 4 + +#### `infer ... extends ...` + +```ts +type X = T extends { a: infer I extends string } ? I : string; + +declare let a: X<{ a: 4 }>; +declare let b: X<{ a: "hello" }>; + +a satisfies number; +b satisfies "hello"; +``` + +- Expected number, found string + +#### TSC string intrinsics + +```ts +const a: Uppercase<"something" |"hi"> = "HI"; +const b: Uppercase = "hi" +``` + +- Type \"hi\" is not assignable to type Uppercase\ + +#### `NoInfer` + +```ts +declare function func(a: T, b: NoInfer): T; + +func("hi", "hello") satisfies number; +``` + +> but not `| "hello"` !!! + +- Expected number, found "hi" + +#### Subtyping edge cases + +```ts +"hi" satisfies { length: 3 }; +"hi" satisfies { length: 2 }; +(() => {}) satisfies Function; ``` -- Type { a: 1, b: 2, c: 3 } is not assignable to type { [string]: boolean } +- Expected { length: 3 }, found "hi" ### Generic types @@ -2688,17 +3452,18 @@ interface Wrapper { internal: T } -const my_wrapped: Wrapper = { internal: "hi" } +({ internal: "hi" } satisfies Wrapper); +({ internal: "hi" } satisfies Wrapper); ``` -- Type { internal: "hi" } is not assignable to type Wrapper\ +- Expected Wrapper\, found { internal: \"hi\" } #### Array property checking ```ts const numbers1: Array = [1, 2, "3"], - numbers2: Array = ["hi", "3"], - numbers3: Array = 4; + numbers2: Array = ["hi", "3"], + numbers3: Array = 4; ``` - Type [1, 2, "3"] is not assignable to type Array\ @@ -2811,14 +3576,14 @@ myRecord.hello; ``` - Expected string, found number -- No property 'hello' on { [\"hi\"]: number }. +- No property 'hello' on { [\"hi\"]: number } #### Assignment ```ts const x: Record<"test", boolean> = { no: false }, - y: Record<"test", boolean> = { test: 6 }, - z: Record<"test", boolean> = { test: false }; + y: Record<"test", boolean> = { test: 6 }, + z: Record<"test", boolean> = { test: false }; ``` - 'no' is not a property of { [\"test\"]: boolean } @@ -2834,14 +3599,149 @@ obj1.hi satisfies boolean; obj1.hello satisfies boolean; obj1.bye; +``` + +- No property 'bye' on { ["hi" | "hello"]: boolean } + +#### Readonly and optionality carries through + +```ts +type Mapped = { + [P in keyof T]: T[P] +} + +interface Y { readonly a: string, b?: number } +declare let x: Mapped; + +x.a = "hi"; +x.b satisfies number; +``` + +- Cannot write to property 'a' +- Expected number, found number | undefined + +#### Specialisation + +```ts +type Pick = { + [P in K]: T[P]; +}; + +interface X { a: number, b: string, c: string } + +({ a: 5 } satisfies Pick); +({ a: 5 } satisfies Pick); +({ a: 5, b: "hi" } satisfies Pick); +({ a: 5, b: 6 } satisfies Pick); + +declare let y: Pick; +y.b satisfies string; +y.a; +``` + +- Expected { ["a" | "b"]: X["a" | "b"] }, found { a: 5 } +- Expected { ["a" | "b"]: X["a" | "b"] }, found { a: 5, b: 6 } +- No property 'a' on { ["b" | "c"]: X["b" | "c"] } + +#### Optional + +```ts +type Partial = { + [P in keyof T]?: T[P]; +}; + +({ a: 3 } satisfies Partial<{ a: number, b: string }>); +({ a: "hi" } satisfies Partial<{ a: number, b: string }>) +``` + +- Expected { [keyof { a: number, b: string }]?: { a: number, b: string }[keyof { a: number, b: string }] }, found { a: \"hi\" } + +#### Negated optionality + +```ts +type Required = { + [P in keyof T]-?: T[P]; +}; + +({ a: 3 } satisfies Required<{ a?: number }>); +// Bad +({ } satisfies Required<{ a?: number }>); +``` + +- Expected { [keyof { a?: number }]: { a?: number }[keyof { a?: number }] }, found {} + +#### Readonly + +```ts +type Mutable = { + readonly [P in keyof T]: T[P]; +}; -declare let obj2: Record; -obj2.fine satisfies boolean; -obj2[2]; +interface Y { a: string } +declare let x: Mutable; +x.a = "hi"; ``` -- No property 'bye' on { ["hi" | "hello"]: boolean }. -- No property '2' on { [string]: boolean }. +- Cannot write to property 'a' + +#### Negated readonly + +```ts +type Mutable = { + -readonly [P in keyof T]: T[P]; +}; + +interface Y { readonly a: string } +declare let x: Mutable; +x.a = "hi"; +x.a = 4; +``` + +- Type 4 does not meet property constraint "hi" + +#### `as` rewrite + +```ts +type PrefixKeys = { + [P in ((keyof T) & string) as `property_${P}`]: T[P]; +}; + +interface X { a: number }; + +declare let x: PrefixKeys; +x.property_a satisfies number; +x.property_b +``` + +- No property 'property_b' on { [string]: X[keyof X & string] } + +### Readonly and `as const` + +> TODO constrained inference + +#### Readonly parameter + +```ts +function x(p: readonly { a: string }) { + p.a = "hi"; +} +``` + +- Cannot write to property 'a' + +#### Readonly to readonly + +```ts +function func1(p: { a: string, b: string }) { + func2(p) +} +function func2(p: readonly { a: string }) { } + +const obj = Object.freeze({ a: "hi" }); +func2(obj) +``` + +- Argument of type { a: string, b: string } is not assignable to parameter of type Readonly<{ a: string }> ### Forward inference @@ -2853,13 +3753,13 @@ obj2[2]; ```ts interface MyObject { - a(b: string): any; + a(b: string): any; } const obj: MyObject = { - a(b) { - b satisfies number; - } + a(b) { + b satisfies number; + } } ``` @@ -2869,11 +3769,11 @@ const obj: MyObject = { ```ts function callFunction(fn: (p: T) => void) { - // ... + // ... } callFunction(a => { - a satisfies number; + a satisfies number; }) ``` @@ -2917,20 +3817,20 @@ obj1.a = "hello"; ```ts { - const obj = { a: true, b: false }; - const x: { a: boolean } = obj, y: { b: boolean } = obj; + const obj = { a: true, b: false }; + const x: { a: boolean } = obj, y: { b: boolean } = obj; - obj.a = "yo"; - obj.b = "wassup"; + obj.a = "yo"; + obj.b = "wassup"; } { - // and in the same assignment through a cycle - const obj = { a: 2, b: 3 }; obj.c = obj; - const something: { a: number, c: { b: number } } = obj; + // and in the same assignment through a cycle + const obj = { a: 2, b: 3 }; obj.c = obj; + const something: { a: number, c: { b: number } } = obj; - obj.a = "hi"; - obj.b = "hello"; + obj.a = "hi"; + obj.b = "hello"; } ``` @@ -2996,6 +3896,88 @@ const p_of_x = Object.getPrototypeOf(x); - Expected string, found true +### `Proxy` + +#### Proxy get + +```ts +const proxy1 = new Proxy({ a: 2 }, { get(target: { a: number }, prop: string, receiver) { + if (prop === "a") { + return target["a"] + 1 + } +} } ); + +proxy1.a satisfies string; +``` + +- Expected string, found 3 + +#### Proxy set + +```ts +let lastSet: string = ""; +const proxy1 = new Proxy({ a: 2 }, { + set(target: { a: number }, prop: string, value: number, receiver) { + lastSet = prop; + } +}); + +proxy1.a = 6; +lastSet satisfies boolean; +``` + +- Expected boolean, found "a" + +#### Proxy handler fallthrough + +```ts +const obj = { a: 2 }; +const proxy1 = new Proxy(obj, { }); + +proxy1.a = 6; +obj.a satisfies 6; +proxy1.a satisfies string; +``` + +- Expected string, found 6 + +#### Proxy subtyping + +```ts +const proxy1 = new Proxy({}, { get(_target, prop, _recivier) { return prop } }); + +proxy1 satisfies { a: "a", b: "b" }; +proxy1 satisfies { c: "d" }; +``` + +- Expected { c: "d" }, found Proxy [ {}, { get: (_target: any, prop: any, _recivier: any) => any } ] + +#### Proxy across functions + +```ts +function makeObservable(obj, cb: (kind: string, prop: string, value: any) => void) { + return new Proxy(obj, { + get(on, prop: string, _rec) { + cb("get", prop, on[prop]) + }, + set(on, prop: string, _value, _rec) { + cb("set", prop, on[prop]) + }, + }) +} + +let r = null; +const value = makeObservable({ a: 1 }, (k, p, v) => { + r = { k, p, v }; +}); + +r satisfies null; +value.a = 2; +r satisfies string; +``` + +- Expected string, found { k: "set", p: "a", v: 1 } + ### Imports and exports #### Import and export named @@ -3086,7 +4068,7 @@ incrementCounter(); counter satisfies string; // in mutable.ts -export let counter = 2; +export let counter: number = 2; export function incrementCounter() { counter++ } @@ -3119,7 +4101,7 @@ console.log(a.prop); export const a = 2; ``` -- Cannot find ./two. +- Cannot find ./two #### Import where export does not exist @@ -3190,7 +4172,7 @@ export const x = 2; const y = "122LH" ``` -- Could not find variable 'y' in scope. Did you mean x? +- Could not find variable 'y' in scope #### Import side effect @@ -3227,7 +4209,7 @@ mean_gravity satisfies 2; // in node_modules/earth/package.json { - "main": "constants.js" + "main": "constants.js" } // in node_modules/earth/constants.js @@ -3236,9 +4218,9 @@ export const mean_gravity = 9.806; - Expected 2, found 9.806 -### Extras +### Stability -> This contains new features. Most are WIP +> What to do in the occurance of a type error #### Use type annotation in the presence of error @@ -3249,7 +4231,7 @@ const x: string = 5; const y: string = h; function getString(a: number): string { - return a + return a } x satisfies string; @@ -3260,50 +4242,36 @@ const z: number = getString(2); - Cannot return number because the function is expected to return string - Type 5 is not assignable to type string -- Could not find variable 'h' in scope. Did you mean x, y or z? +- Could not find variable 'h' in scope - Type (error) string is not assignable to type number -#### Unconditional throw - -```ts -function safeDivide(num: number, denom: number) { - if (denom === 0) { - throw new Error("Cannot divide by zero"); - } - return num / denom -} - -safeDivide(8, 4) satisfies 2; - -safeDivide(10, 0); -``` - -- Conditional '[Error] { message: \"Cannot divide by zero\" }' was thrown in function +#### Errors carries -#### Unreachable statement +> Note only one error raised. This prevents the compiler presenting loads of errors if an origin is invalid ```ts -function throwGreeting() { - throw "Hello"; - return "Unreachable!" -} +const obj = { prop: 2 }; +console.log(obj.a.b.c); -function doSomething() { - throwGreeting() - const unreachable = 2; +function x() { + return y } + +x().nothing ``` -> One is for `return 5` the other is for `const x = 2;` +- Could not find variable 'y' in scope +- No property 'a' on { prop: 2 } -- Unreachable statement -- Unreachable statement +### Extras + +> This contains new features. Most are WIP #### JSX type ```tsx function JSXH(tag_name: string, attributes: any, children: any) { - return { tag_name, attributes, children } + return { tag_name, attributes, children } } const x =

Hello World

satisfies string; @@ -3315,7 +4283,7 @@ const x =

Hello World

satisfies string; ```ts function x(a /** string */) { - a satisfies number + a satisfies number } const c /** number */ = "hello" @@ -3328,7 +4296,7 @@ const c /** number */ = "hello" ```ts function register(a: Literal) { - // ... + // ... } register("something") @@ -3338,20 +4306,68 @@ register(document.title) - Argument of type string is not assignable to parameter of type Literal\ -#### Errors carries +#### Number intrinsics -> Note only one error raised. This prevents the compiler presenting loads of errors if an origin is invalid +```ts +5 satisfies MultipleOf<2>; +4 satisfies MultipleOf<2>; + +6 satisfies GreaterThan<2>; +-4 satisfies GreaterThan<2>; + +6 satisfies LessThan<2>; +-4 satisfies LessThan<2>; +``` + +- Expected MultipleOf\<2\>, found 5 +- Expected GreaterThan\<2\>, found -4 +- Expected LessThan\<2\>, found 6 + +#### `Not` ```ts -const obj = { prop: 2 }; -console.log(obj.a.b.c); +declare let a: number; +4 satisfies Not<4>; +6 satisfies Not<4>; +a satisfies Not<8>; +2 satisfies Not; +"hi" satisfies Not; -function x() { - return y -} +declare let b: Not<5> & number; +b satisfies number; +b satisfies string; +b satisfies 5; +``` -x().nothing +- Expected Not<4>, found 4 +- Expected Not<8>, found number +- Expected Not\, found "hi" +- Expected string, found Not<5> & number +- Expected 5, found Not<5> & number + +#### `Exclusive` + +```ts +interface X { a: number } +const x = { a: 1, b: 2 }; + +x satisfies Exclusive; +({ a: 6 } satisfies Exclusive); +``` + +- Expected Exclusive\, found { a: 1, b: 2 } + +#### `CaseInsensitive` + +```ts +"Hi" satisfies CaseInsensitive<"hi">; +"Hello" satisfies CaseInsensitive<"hi">; + +// yeah +type CIWord = "WORD" extends Uppercase ? T : never; +"wOrd" satisfies CIWord; +"wood" satisfies CIWord; ``` -- Could not find variable 'y' in scope. Did you mean x? -- No property 'a' on { prop: 2 }. +- Expected CaseInsensitive<"hi">, found "Hello" +- Expected CIWord, found "wood" diff --git a/checker/specification/test.rs b/checker/specification/test.rs index c597d974..85166a18 100644 --- a/checker/specification/test.rs +++ b/checker/specification/test.rs @@ -34,6 +34,8 @@ mod specification { // const SIMPLE_DTS: Option<&str> = Some(include_str!("../definitions/simple.d.ts")); const SIMPLE_DTS: Option<&str> = None; +const IN_CI: bool = option_env!("CI").is_some(); + /// Called by each test fn check_errors( heading: &'static str, @@ -63,31 +65,37 @@ fn check_errors( // eprintln!("{:?}", code); // let result = panic::catch_unwind(|| { - eprintln!("{:?}", std::env::current_dir()); + + if IN_CI { + eprintln!("::group::Running {heading}"); + } + let definition_file_name: PathBuf = if SIMPLE_DTS.is_some() { "./checker/definitions/simple.d.ts".into() } else { checker::INTERNAL_DEFINITION_FILE_PATH.into() }; - let type_definition_files = std::iter::once(definition_file_name.clone()).collect(); + let type_definition_files = vec![definition_file_name.clone()]; + + let resolver = |path: &Path| -> Option> { + if path == definition_file_name.as_path() { + Some(SIMPLE_DTS.unwrap().to_owned().into_bytes()) + } else if code.len() == 1 { + Some(code[0].1.to_owned().into()) + } else { + code.iter() + .find_map(|(code_path, content)| { + (std::path::Path::new(code_path) == path) + .then_some(content.to_owned().to_owned()) + }) + .map(Into::into) + } + }; let result = checker::check_project::<_, EznoParser>( vec![PathBuf::from("main.tsx")], type_definition_files, - |path: &Path| -> Option> { - if path == definition_file_name.as_path() { - Some(SIMPLE_DTS.unwrap().to_owned().into_bytes()) - } else if code.len() == 1 { - Some(code[0].1.to_owned().into()) - } else { - code.iter() - .find_map(|(code_path, content)| { - (std::path::Path::new(code_path) == path) - .then_some(content.to_owned().to_owned()) - }) - .map(Into::into) - } - }, + resolver, type_check_options, (), None, @@ -111,6 +119,10 @@ fn check_errors( }) .collect(); + if IN_CI { + eprintln!("::endgroup::"); + } + if diagnostics != expected_diagnostics { panic!( "{}", diff --git a/checker/specification/to_implement.md b/checker/specification/to_implement.md index a4bbfa75..2cd6dc0f 100644 --- a/checker/specification/to_implement.md +++ b/checker/specification/to_implement.md @@ -3,19 +3,18 @@ ### Types -#### Resolving value by property on dependent +#### Array slice matching pattern ```ts -function getProperty(property: "a" | "b" | "c") { - return { a: 1, b: 2, c: 3 }[property] -} +type Head = T extends [infer H, ...Array] ? H : never; + +type Tail = T extends [any, ...infer Tail] ? Tail : []; -getProperty("d") -getProperty("c") satisfies 2 +1 satisfies Head<[1, 2, 3, 4, 5]>; +[2, 3, 4, 5] satisfies Tail<[1, 2, 3, 4, 5]>; ``` -- Expected "a" | "b" | "c" found "d" -- Expected 2 found 3 +- Expected 1, found 2 #### Generic type argument restriction @@ -56,21 +55,31 @@ print_type(mapper) - TODO -#### Function calling +#### Calling or -#### Calling on or type +> *Calling* is distributive `(A | B)()` -> `A() | B()` ```ts -type Func1 = () => 3; -type Func2 = () => 2; -function callFunc(func: (() => T) | (() => U)): 3 | 2 { - return func() +function a(p: string) { return 2 } +function b(p: string) { return 4 } + +function c(c: boolean) { + const func = c ? a : b; + const result = func() + result satisfies string; + return result } +``` + +- Expected string, found 2 | 4 -print_type(callFunc) +#### Setter or + +```ts +TODO ``` -- Expected "a" | "b" | "c" found "d" +- Expected string, found 2 | 4 #### Getter and setter through function @@ -93,6 +102,29 @@ x({ get b() { return 2 } }) - Expected string, found 5 +#### Spread object constraint + +```ts +let { a, ...x }: { a: number, b: string } = { a: "hi", b: "hello" }; +x = 1; +``` + +- 1 is not assignable to { b: string } + +#### Known symbol inference & checking + +> #TODO extra + +```ts +class X { + [Symbol.iterator]() { + return {} + } +} +``` + +- ? + ### Imports #### Import package with definition file @@ -177,22 +209,6 @@ function func(array: Array) { - Expected number found string -#### Order of properties - -> TODO this is because setting properties are simply appended. There are two straightforward fixes, but I am unsure which one is better... - -```ts -const obj = { a: 1, b: 2 }; -obj.a = 2; obj.c = 6; obj.b = 4; -let properties: string = ""; -for (const property in obj) { - properties += property; -} -properties satisfies boolean; -``` - -- Expected boolean, found "abc" - ### Inference #### Parameter property @@ -334,10 +350,6 @@ function x*() { - TODO -### `Proxy` and `Object` - -> TODO effects, different traps and `Object.defineProperty` - ### Collections #### `Array.fill` @@ -367,6 +379,144 @@ declare let aNumber: number; - Expected string, found boolean +#### Array filter + +```ts +[1, 2, 3].filter(x => x % 2 == 0) satisfies [2]; +``` + +- ? + +#### Array slice + +```ts +[1, 2, 3, 4, 5].slice(3) satisfies something; +``` + +- ? + +#### Array splice + +```ts +const array = [1, 2, 3, 4, 5]; +array.splice(2); +array satisfies something; +``` + +- ? + +#### Array shift and unshift + +```ts +const array = [1, 2, 3]; +array.shift() satisfies not1; +``` + +- ? + +#### Array copying methods + +```ts +// toReversed +// with +``` + +- ? + +#### Array find & index of + +```ts +// indexOf +// lastIndexOf +// find +// findIndexOf +``` + +- ? + +#### Array constructor + +```ts +new Array({ length: 5 }, (_, i) => i) satisfies string +``` + +- ? + +#### Array `concat` and spread push + +```ts +// concat() +// push(...x) +``` + +- ? + +#### Array values and entries + +> Iterators + +```ts +const x =[1, 2, 3].values(); +``` + +- ? + +#### Array flat + +```ts +// flatten +// flatMap +``` + +- ? + +#### Array reducers + +> May be hard bc reversed generics order + +```ts +// reduce +// reduceRight +``` + +- ? + +#### Map `set` and `get` + +```ts +const x = new Map(); +x.set(4, 2); +x.set(4, 3); +x.get(4) satisfies 2; +x.get(2) satisfies string; +``` + +- Expected 2, found 3 +- Expected string, found undefined + +#### Map `items` + +```ts +const x = new Map(); +x.items() +``` + +- ? + +#### Map generics + +```ts +const x: Map = new Map(); +x.set(4, false); + +const y = new Map(); +y.set(6, 2); +y.set(4, "hi"); +y satisfies string; +``` + +- Expected string, found Map<6 | 4, 2 | "hi"> + ### Expressions #### Bad arithmetic operator @@ -417,33 +567,6 @@ declare const global: { a?: string }; - Expected string, found boolean - Expected 2, found string | undefined -#### Delete from required propertied - -```ts -declare let global: { a?: string, b: string }; - -// Fine -delete global.a; -// Bad -delete global.b; -``` - -- Cannot delete property "b" off { a?: string, b: string } - -#### Optional property access - -```ts -interface X { - possibly?: string -} - -declare let x: X; - -x?.possibly satisfies number; -``` - -- Expected string, found string | undefined - ### Runtime #### Free variable + anytime calls @@ -517,6 +640,21 @@ class Rectangle implements Draw { - Class "MyNumber", does not implement draw - Expected string, found CanvasRenderingContext2D +#### Via effect + +```ts +function newClass(property, value) { + return class { + [property] = value + } +} + +new (newClass("hello", 2)).hello satisfies 2; +new (newClass("hi", 6)).hi satisfies string; +``` + +- Expected string, found 6 + ### Recursion #### Application @@ -571,18 +709,6 @@ a satisfies 3; b satisfies string; ### Functions -#### No generics - -```ts -function id(a) { return a } - -id<5>(4) -``` - -- Cannot pass generic arguments to function without generic arguments - -> Or at least explicit generic arguments - #### Method overloading ```ts @@ -671,63 +797,173 @@ if (b === "hi") { - Expected "hello", found "hi" -### Mapped types +#### Optional property access -#### Specialisation +```ts +interface X { + a: string + b: string +} + +declare let x: X | null; + +x.a; +x?.b satisfies number; +``` + +- Cannot get 'a' on null +- Expected number, found string + +### Generics + +#### Out of order generics ```ts -type Pick = { - [P in K]: T[P]; -}; +function func(cb: (t: T) => number, value: T) { } -interface X { a: number, b: string, c: string } +func(cb => { cb satisfies boolean }, "hi") +``` -const x: Pick = { a: 5 }; +- Expected boolean, found "hi" -({ b: "string" }) satisfies Pick; +### Broken + +> Was working, now broken (or removed) + +#### Readonly property + +> Should be working but parser current wraps `a` as `Readonly` :( + +```ts +function x(p: { readonly a: string, b: string }) { + p.a = "hi"; + p.b = "hi"; +} ``` -- TODO +- Cannot write to property 'a' -#### Optional +#### Destructuring using iterator ```ts -type Partial = { - [P in keyof T]?: T[P]; -}; +const [a, b, c] = { + [Symbol.iterator]() { + return { + count: 0, + next(this: { count: number }) { + return { value: this.count++, done: false } + } + } + } +} -const x: Partial<{ a: number, b: string }> = { a: 3 }, - y: Partial<{ a: number, b: string }> = { a: "hi" } +a satisfies 0; b satisfies string; ``` -- Cannot assign { a: "hi" } +- Expected string, found 1 -#### Negated +#### Always known math ```ts -type Required = { - [P in keyof T]-?: T[P]; -}; +function func(a: number) { return a ** 0 } + +print_type(func) -const x: Required<{ a?: number }> = { a: 3 }, - y: Required<{ a?: number }> = { }; +declare let x: NotNotANumber; + +print_type(x ** 1 === x) ``` -- Cannot assign { } to required +- Expected string, found 1 +- True -### Readonly and `as const` +#### Less than checks -> TODO constrained inference +```ts +function x(a: GreaterThan<4>) { + (a < 3) satisfies false; + (a < 10) satisfies string; +} +``` -#### Readonly parameter +- Expected string, found boolean + +#### Tagged template literal + +> Waiting for parser definition updated to make this easier + +```ts +function myTag(static_parts: Array, other: string) { + return { static_parts, other } +} + +const name = "Ben"; +myTag`${name}Hello ` satisfies string +``` + +- Expected string, found { static_parts: ["", "Hello "], other: "Ben" } + +### Control flow + +#### Conditional break + +```ts +function getNumber(a: number) { + for (let i = 0; i < 10; i++) { + if (i === a) { + return "found" + } + } + return "not-found" +} + +getNumber(4) satisfies "found"; +getNumber(100) satisfies boolean; +``` + +- Expected boolean, found "not-found" + +#### *Inconclusive* conditional update ```ts -function x(p: readonly { a: string }) { - p.a = 5; +declare var value: string; +let a: string | number = 0; + +function conditional(v: string) { + if (v === "value") { + a = "hi" + } } +conditional(value); +a satisfies string; ``` -- Cannot assign to immutable property +- Expected string, found "hi" | 0 + +#### Break with label + +> Note the numbers here, if they are larger they break over the `max_inline` limit and get different results below + +```ts +let a: number = 0; +let result; + +top: while (a++ < 8) { + let b: number = 0; + while (b++ < 8) { + if (a === 3 && b === 2) { + result = a * b; + break top + } + } +} + +a satisfies string; +result satisfies boolean; +``` + +- Expected string, found 3 +- Expected boolean, found 6 ### Closures @@ -802,97 +1038,46 @@ x.a = "hi" - Cannot assign. Restricted to number -### Exceptions and `try-catch-finally` - -#### Conditional throw +### Others -> This emits a warning if a throw was created in a conditional branch +#### Pure getter assignment ```ts -// no complex numbers :( -function checkedLn(x: number) { - if (x > 0) { - return Math.log(x) - } else { - throw new Exception("Cannot log long string") - } +function func(p: Pure<{ prop: number }>) { + p.prop = 2; + p.prop satisfies string; } - -// Fine -try { checkedLn(Math.E ** 3) satisfies 3 } catch {} -// Will throw -try { checkedLn(-5) } catch {} ``` -- Conditional 'Exception' was thrown in function - -### Broken - -> Was working, now not +- Expected string, found 2 -#### `find` and `includes` +#### Deep readonly -> TODO other arguments (index and `this`). and poly +> TODO implement use mapped type `T & { [P in keyof T]: Readonly }` ```ts -[1, 2, 3].find(x => x % 2 === 0) satisfies 4; - -// [1, 2, 3].includes(6) satisfies string; +declare const obj: DeepReadonly<{ a: { b: { c: 2 } } }; +obj.a.b.c = 2; ``` -- Expected 4, found 2 - +- Cannot assign to readonly -#### Conditional return type inference +#### Conditionality destructuring from poly ```ts -function func(a: boolean) { - if (a) { - return 2 - } -} - -func satisfies (a: boolean) => 5; +declare let x: { a?: 1 }; // also { a: 1 } | { b: 2 } +let { a = 2 } = x; +a satisfies 3; ``` -- Expected (a: boolean) => 5, found (a: boolean) => 2 | undefined +- Expected 3, found 1 | 2 -#### *Inconclusive* conditional update - -```ts -declare var value: string; -let a: string | number = 0; - -function conditional(v: string) { - if (v === "value") { - a = "hi" - } -} -conditional(value); -a satisfies string; -``` - -- Expected string, found "hi" | 0 +### RegExp -#### Break with label +#### Regexp patterns ```ts -let a: number = 0; -let result; - -top: while (a++ < 10) { - let b: number = 0; - while (b++ < 10) { - if (a === 3 && b === 2) { - result = a * b; - break top - } - } -} - -a satisfies string; -result satisfies boolean; +new RegExp("x").group.string ``` -- Expected string, found 3 -- Expected boolean, found 6 +- ? diff --git a/checker/src/context/control_flow.rs b/checker/src/context/control_flow.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/checker/src/context/control_flow.rs @@ -0,0 +1 @@ + diff --git a/checker/src/context/environment.rs b/checker/src/context/environment.rs index b2a5743e..c7d019be 100644 --- a/checker/src/context/environment.rs +++ b/checker/src/context/environment.rs @@ -2,19 +2,19 @@ use source_map::{SourceId, Span, SpanWithSource}; use std::collections::{HashMap, HashSet}; use crate::{ - context::get_on_ctx, + context::{get_on_ctx, information::ReturnState}, diagnostics::{ - NotInLoopOrCouldNotFindLabel, PropertyRepresentation, TypeCheckError, + NotInLoopOrCouldNotFindLabel, PropertyKeyRepresentation, TypeCheckError, TypeStringRepresentation, TDZ, }, events::{Event, FinalEvent, RootReference}, features::{ assignments::{ Assignable, AssignableArrayDestructuringField, AssignableObjectDestructuringField, - AssignmentKind, Reference, + AssignmentKind, AssignmentReturnStatus, IncrementOrDecrement, Reference, }, modules::Exported, - objects::SpecialObjects, + objects::SpecialObject, operations::{ evaluate_logical_operation_with_expression, evaluate_pure_binary_operation_handle_errors, MathematicalAndBitwise, @@ -23,24 +23,31 @@ use crate::{ }, subtyping::{type_is_subtype, type_is_subtype_object, State, SubTypeResult, SubTypingOptions}, types::{ - printing, properties::{ - get_property_key_names_on_a_single_type, PropertyKey, PropertyKind, PropertyValue, + get_property_key_names_on_a_single_type, AccessMode, PropertyKey, PropertyKind, Publicity, }, PolyNature, Type, TypeStore, }, - CheckingData, Instance, RootContext, TypeCheckOptions, TypeId, + CheckingData, Instance, RootContext, TypeId, }; use super::{ - get_value_of_variable, information::InformationChain, invocation::CheckThings, AssignmentError, - ClosedOverReferencesInScope, Context, ContextType, Environment, GeneralContext, - SetPropertyError, + get_value_of_variable, invocation::CheckThings, AssignmentError, ClosedOverReferencesInScope, + Context, ContextType, Environment, GeneralContext, InformationChain, }; +/// For WIP contextual access of certain APIs pub type ContextLocation = Option; +/// Error type for something already being defined +pub struct AlreadyExists; + +pub enum DeclareInterfaceResult { + Merging { ty: TypeId, in_same_context: bool }, + New(TypeId), +} + #[derive(Debug)] pub struct Syntax<'a> { pub scope: Scope, @@ -239,19 +246,13 @@ impl<'a> Environment<'a> { /// Handles all assignments, including updates and destructuring /// /// Will evaluate the expression with the right timing and conditions, including never if short circuit - /// - /// TODO finish operator. Unify increment and decrement. The RHS span should be fine with [`Span::NULL ...?`] Maybe RHS type could be None to accommodate - pub fn assign_to_assignable_handle_errors< - 'b, - T: crate::ReadFromFS, - A: crate::ASTImplementation, - >( + pub fn assign_handle_errors<'b, T: crate::ReadFromFS, A: crate::ASTImplementation>( &mut self, lhs: Assignable, operator: AssignmentKind, // Can be `None` for increment and decrement expression: Option<&'b A::Expression<'b>>, - assignment_span: Span, + assignment_position: Span, checking_data: &mut CheckingData, ) -> TypeId { match lhs { @@ -265,17 +266,24 @@ impl<'a> Environment<'a> { checking_data, ); - self.assign_to_reference_assign_handle_errors( + let assignment_position = + assignment_position.with_source(self.get_source()); + self.set_reference_handle_errors( reference, rhs, + assignment_position, checking_data, - assignment_span, - ) + ); + rhs } AssignmentKind::PureUpdate(operator) => { // Order matters here let reference_position = reference.get_position(); - let existing = self.get_reference(reference.clone(), checking_data, true); + let existing = self.get_reference( + reference.clone(), + checking_data, + AccessMode::Regular, + ); let expression = expression.unwrap(); let expression_pos = @@ -294,39 +302,33 @@ impl<'a> Environment<'a> { checking_data, self, ); - let result = self.set_reference(reference, new, checking_data); - match result { - Ok(ty) => ty, - Err(error) => { - let error = set_property_error_to_type_check_error( - self, - error, - assignment_span.with_source(self.get_source()), - &checking_data.types, - new, - ); - checking_data.diagnostics_container.add_error(error); - TypeId::ERROR_TYPE - } - } + let assignment_position = + assignment_position.with_source(self.get_source()); + self.set_reference_handle_errors( + reference, + new, + assignment_position, + checking_data, + ); + new } AssignmentKind::IncrementOrDecrement(direction, return_kind) => { // let value = - // self.get_variable_or_error(&name, &assignment_span, checking_data); - let span = reference.get_position(); - let existing = self.get_reference(reference.clone(), checking_data, true); + // self.get_variable_or_error(&name, &assignment_position, checking_data); + let position = reference.get_position(); + let existing = self.get_reference( + reference.clone(), + checking_data, + AccessMode::Regular, + ); // TODO existing needs to be cast to number!! let new = evaluate_pure_binary_operation_handle_errors( - (existing, span), + (existing, position), match direction { - crate::features::assignments::IncrementOrDecrement::Increment => { - MathematicalAndBitwise::Add - } - crate::features::assignments::IncrementOrDecrement::Decrement => { - MathematicalAndBitwise::Subtract - } + IncrementOrDecrement::Increment => MathematicalAndBitwise::Add, + IncrementOrDecrement::Decrement => MathematicalAndBitwise::Subtract, } .into(), (TypeId::ONE, source_map::Nullable::NULL), @@ -334,30 +336,26 @@ impl<'a> Environment<'a> { self, ); - let result = self.set_reference(reference, new, checking_data); + let assignment_position = + assignment_position.with_source(self.get_source()); + self.set_reference_handle_errors( + reference, + new, + assignment_position, + checking_data, + ); - match result { - Ok(new) => match return_kind { - crate::features::assignments::AssignmentReturnStatus::Previous => { - existing - } - crate::features::assignments::AssignmentReturnStatus::New => new, - }, - Err(error) => { - let error = set_property_error_to_type_check_error( - self, - error, - assignment_span.with_source(self.get_source()), - &checking_data.types, - new, - ); - checking_data.diagnostics_container.add_error(error); - TypeId::ERROR_TYPE - } + match return_kind { + AssignmentReturnStatus::Previous => existing, + AssignmentReturnStatus::New => new, } } AssignmentKind::ConditionalUpdate(operator) => { - let existing = self.get_reference(reference.clone(), checking_data, true); + let existing = self.get_reference( + reference.clone(), + checking_data, + AccessMode::Regular, + ); let expression = expression.unwrap(); let new = evaluate_logical_operation_with_expression( (existing, reference.get_position().without_source()), @@ -365,25 +363,19 @@ impl<'a> Environment<'a> { expression, checking_data, self, + TypeId::ANY_TYPE, ) .unwrap(); - let result = self.set_reference(reference, new, checking_data); - - match result { - Ok(new) => new, - Err(error) => { - let error = set_property_error_to_type_check_error( - self, - error, - assignment_span.with_source(self.get_source()), - &checking_data.types, - new, - ); - checking_data.diagnostics_container.add_error(error); - TypeId::ERROR_TYPE - } - } + let assignment_position = + assignment_position.with_source(self.get_source()); + self.set_reference_handle_errors( + reference, + new, + assignment_position, + checking_data, + ); + new } } } @@ -400,9 +392,11 @@ impl<'a> Environment<'a> { self.assign_to_object_destructure_handle_errors( members, rhs, - assignment_span, + assignment_position.with_source(self.get_source()), checking_data, - ) + ); + + rhs } Assignable::ArrayDestructuring(members, _spread) => { debug_assert!(matches!(operator, AssignmentKind::Assign)); @@ -417,67 +411,43 @@ impl<'a> Environment<'a> { self.assign_to_array_destructure_handle_errors( members, rhs, - assignment_span, + assignment_position.with_source(self.get_source()), checking_data, - ) - } - } - } - - fn assign_to_reference_assign_handle_errors< - T: crate::ReadFromFS, - A: crate::ASTImplementation, - >( - &mut self, - reference: Reference, - rhs: TypeId, - checking_data: &mut CheckingData, - assignment_span: source_map::BaseSpan<()>, - ) -> TypeId { - let result = self.set_reference(reference, rhs, checking_data); - - match result { - Ok(ty) => ty, - Err(error) => { - let error = set_property_error_to_type_check_error( - self, - error, - assignment_span.with_source(self.get_source()), - &checking_data.types, - rhs, ); - checking_data.diagnostics_container.add_error(error); - TypeId::ERROR_TYPE + + rhs } } } - fn assign_to_assign_only_handle_errors( + fn assign_to_assignable_handle_errors( &mut self, lhs: Assignable, rhs: TypeId, - assignment_span: Span, + assignment_position: SpanWithSource, checking_data: &mut CheckingData, - ) -> TypeId { + ) { match lhs { - Assignable::Reference(reference) => self.assign_to_reference_assign_handle_errors( - reference, - rhs, - checking_data, - assignment_span, - ), + Assignable::Reference(reference) => { + self.set_reference_handle_errors( + reference, + rhs, + assignment_position, + checking_data, + ); + } Assignable::ObjectDestructuring(assignments, _spread) => self .assign_to_object_destructure_handle_errors( assignments, rhs, - assignment_span, + assignment_position, checking_data, ), Assignable::ArrayDestructuring(assignments, _spread) => self .assign_to_array_destructure_handle_errors( assignments, rhs, - assignment_span, + assignment_position, checking_data, ), } @@ -490,29 +460,38 @@ impl<'a> Environment<'a> { &mut self, assignments: Vec>, rhs: TypeId, - assignment_span: Span, + assignment_position: SpanWithSource, checking_data: &mut CheckingData, - ) -> TypeId { + ) { for assignment in assignments { match assignment { AssignableObjectDestructuringField::Mapped { - on, + key, name, default_value, position, } => { - let value = self.get_property( + // TODO conditionaly + let mut diagnostics = Default::default(); + let result = crate::types::properties::get_property( rhs, Publicity::Public, - &on, + &key, + self, + ( + &mut CheckThings { debug_types: checking_data.options.debug_types }, + &mut diagnostics, + ), &mut checking_data.types, - None, position, - &checking_data.options, - false, + AccessMode::DoNotBindThis, + ); + diagnostics.append_to( + crate::types::calling::CallingContext::Getter, + &mut checking_data.diagnostics_container, ); - let rhs_value = if let Some((_, value)) = value { + let rhs_value = if let Some((_, value)) = result { value } else if let Some(default_value) = default_value { A::synthesise_expression( @@ -522,51 +501,55 @@ impl<'a> Environment<'a> { checking_data, ) } else { + let keys; + let possibles = if let PropertyKey::String(s) = &key { + keys = get_property_key_names_on_a_single_type( + rhs, + &checking_data.types, + self, + ); + let mut possibles = + crate::get_closest(keys.iter().map(AsRef::as_ref), s) + .unwrap_or(vec![]); + possibles.sort_unstable(); + possibles + } else { + Vec::new() + }; checking_data.diagnostics_container.add_error( TypeCheckError::PropertyDoesNotExist { - property: match on { - PropertyKey::String(s) => { - PropertyRepresentation::StringKey(s.to_string()) - } - PropertyKey::Type(t) => PropertyRepresentation::Type( - printing::print_type(t, &checking_data.types, self, false), - ), - }, + property: PropertyKeyRepresentation::new( + &key, + self, + &checking_data.types, + ), on: TypeStringRepresentation::from_type_id( rhs, self, &checking_data.types, false, ), - site: position, - possibles: get_property_key_names_on_a_single_type( - rhs, - &mut checking_data.types, - self, - ) - .iter() - .map(AsRef::as_ref) - .collect::>(), + position, + possibles, }, ); TypeId::ERROR_TYPE }; - self.assign_to_assign_only_handle_errors( + self.assign_to_assignable_handle_errors( name, rhs_value, - assignment_span, + assignment_position, checking_data, ); } } } - - rhs } #[allow(clippy::needless_pass_by_value)] + #[allow(clippy::unused_self)] fn assign_to_array_destructure_handle_errors< T: crate::ReadFromFS, A: crate::ASTImplementation, @@ -574,35 +557,31 @@ impl<'a> Environment<'a> { &mut self, _assignments: Vec>, _rhs: TypeId, - assignment_span: Span, + assignment_position: SpanWithSource, checking_data: &mut CheckingData, - ) -> TypeId { - checking_data.raise_unimplemented_error( - "destructuring array (needs iterator)", - assignment_span.with_source(self.get_source()), - ); - - TypeId::ERROR_TYPE + ) { + checking_data + .raise_unimplemented_error("destructuring array (needs iterator)", assignment_position); } fn get_reference( &mut self, reference: Reference, checking_data: &mut CheckingData, - bind_this: bool, + mode: AccessMode, ) -> TypeId { match reference { Reference::Variable(name, position) => { self.get_variable_handle_error(&name, position, checking_data).unwrap().1 } - Reference::Property { on, with, publicity, span } => { + Reference::Property { on, with, publicity, position } => { let get_property_handle_errors = self.get_property_handle_errors( on, publicity, &with, checking_data, - span, - bind_this, + position, + mode, ); match get_property_handle_errors { Ok(i) => i.get_value(), @@ -612,30 +591,20 @@ impl<'a> Environment<'a> { } } - fn set_reference( + fn set_reference_handle_errors( &mut self, reference: Reference, rhs: TypeId, + _position: SpanWithSource, checking_data: &mut CheckingData, - ) -> Result { + ) { match reference { - Reference::Variable(name, position) => Ok(self.assign_to_variable_handle_errors( - name.as_str(), - position, - rhs, - checking_data, - )), - Reference::Property { on, with, publicity, span } => Ok(self - .set_property( - on, - publicity, - &with, - rhs, - &mut checking_data.types, - span, - &checking_data.options, - )? - .unwrap_or(rhs)), + Reference::Variable(name, position) => { + self.assign_to_variable_handle_errors(name.as_str(), position, rhs, checking_data); + } + Reference::Property { on, with, publicity, position } => { + self.set_property_handle_errors(on, publicity, &with, rhs, position, checking_data); + } } } @@ -645,7 +614,7 @@ impl<'a> Environment<'a> { assignment_position: SpanWithSource, new_type: TypeId, checking_data: &mut CheckingData, - ) -> TypeId { + ) { let result = self.assign_to_variable( variable_name, assignment_position, @@ -653,12 +622,11 @@ impl<'a> Environment<'a> { &mut checking_data.types, ); match result { - Ok(ok) => ok, + Ok(()) => {} Err(error) => { checking_data .diagnostics_container .add_error(TypeCheckError::AssignmentError(error)); - TypeId::ERROR_TYPE } } } @@ -670,68 +638,70 @@ impl<'a> Environment<'a> { assignment_position: SpanWithSource, new_type: TypeId, types: &mut TypeStore, - ) -> Result { + ) -> Result<(), AssignmentError> { // Get without the effects let variable_in_map = self.get_variable_unbound(variable_name); if let Some((_, boundary, variable)) = variable_in_map { match variable { - VariableOrImport::Variable { mutability, declared_at, context: _ } => { - match mutability { - VariableMutability::Constant => { - Err(AssignmentError::Constant(*declared_at)) + VariableOrImport::Variable { + mutability, + declared_at, + context: _, + allow_reregistration: _, + } => match mutability { + VariableMutability::Constant => Err(AssignmentError::Constant(*declared_at)), + VariableMutability::Mutable { reassignment_constraint } => { + let variable = variable.clone(); + let variable_position = *declared_at; + let variable_id = variable.get_id(); + + if boundary.is_none() + && !self + .get_chain_of_info() + .any(|info| info.variable_current_value.contains_key(&variable_id)) + { + return Err(AssignmentError::TDZ(TDZ { + position: assignment_position, + variable_name: variable_name.to_owned(), + })); } - VariableMutability::Mutable { reassignment_constraint } => { - let variable = variable.clone(); - let variable_site = *declared_at; - let variable_id = variable.get_id(); - - if boundary.is_none() - && !self.get_chain_of_info().any(|info| { - info.variable_current_value.contains_key(&variable_id) - }) { - return Err(AssignmentError::TDZ(TDZ { - position: assignment_position, - variable_name: variable_name.to_owned(), - })); - } - - if let Some(reassignment_constraint) = *reassignment_constraint { - let result = type_is_subtype_object( - reassignment_constraint, - new_type, - self, - types, - ); - if let SubTypeResult::IsNotSubType(_mismatches) = result { - return Err(AssignmentError::DoesNotMeetConstraint { - variable_type: TypeStringRepresentation::from_type_id( - reassignment_constraint, - self, - types, - false, - ), - value_type: TypeStringRepresentation::from_type_id( - new_type, self, types, false, - ), - variable_site, - value_site: assignment_position, - }); - } + if let Some(reassignment_constraint) = *reassignment_constraint { + let result = type_is_subtype_object( + reassignment_constraint, + new_type, + self, + types, + ); + + if let SubTypeResult::IsNotSubType(_mismatches) = result { + return Err(AssignmentError::DoesNotMeetConstraint { + variable_type: TypeStringRepresentation::from_type_id( + reassignment_constraint, + self, + types, + false, + ), + value_type: TypeStringRepresentation::from_type_id( + new_type, self, types, false, + ), + variable_position, + value_position: assignment_position, + }); } + } - self.info.events.push(Event::SetsVariable( - variable_id, - new_type, - assignment_position, - )); - self.info.variable_current_value.insert(variable_id, new_type); + self.info.events.push(Event::SetsVariable( + variable_id, + new_type, + assignment_position, + )); + self.info.variable_current_value.insert(variable_id, new_type); - Ok(new_type) - } + Ok(()) } - } + }, VariableOrImport::MutableImport { .. } | VariableOrImport::ConstantImport { .. } => { Err(AssignmentError::Constant(assignment_position)) @@ -770,51 +740,6 @@ impl<'a> Environment<'a> { self.context_type.requests.extend(requests); } - /// TODO decidable & private? - #[must_use] - pub fn property_in(&self, on: TypeId, property: &PropertyKey) -> bool { - self.get_chain_of_info().any(|info| match info.current_properties.get(&on) { - Some(v) => { - v.iter().any( - |(_, p, v)| if let PropertyValue::Deleted = v { false } else { p == property }, - ) - } - None => false, - }) - } - - /// TODO decidable & private? - pub fn delete_property( - &mut self, - on: TypeId, - property: &PropertyKey, - position: SpanWithSource, - ) -> bool { - let existing = self.property_in(on, property); - - let under = property.into_owned(); - - // on_default() okay because might be in a nested context. - // entry empty does not mean no properties, just no properties set on this level - self.info.current_properties.entry(on).or_default().push(( - Publicity::Public, - under.clone(), - PropertyValue::Deleted, - )); - - // TODO Event::Delete. Dependent result based on in - self.info.events.push(Event::Setter { - on, - under, - new: PropertyValue::Deleted, - initialization: false, - publicity: Publicity::Public, - position, - }); - - existing - } - pub(crate) fn get_parent(&self) -> GeneralContext { match self.context_type.parent { GeneralContext::Syntax(syn) => GeneralContext::Syntax(syn), @@ -822,51 +747,51 @@ impl<'a> Environment<'a> { } } - #[allow(clippy::too_many_arguments)] - pub fn get_property( - &mut self, - on: TypeId, - publicity: Publicity, - property: &PropertyKey, - types: &mut TypeStore, - with: Option, - position: SpanWithSource, - options: &TypeCheckOptions, - bind_this: bool, - ) -> Option<(PropertyKind, TypeId)> { - crate::types::properties::get_property( - on, - publicity, - property, - with, - self, - &mut CheckThings { debug_types: options.debug_types }, - types, - position, - bind_this, - ) - } - pub fn get_property_handle_errors( &mut self, on: TypeId, publicity: Publicity, - key: &PropertyKey, + under: &PropertyKey, checking_data: &mut CheckingData, - site: SpanWithSource, - bind_this: bool, + position: SpanWithSource, + mode: AccessMode, ) -> Result { - let get_property = self.get_property( + // let get_property = if let AccessMode::Optional = mode { + // let is_lhs_null = evaluate_equality_inequality_operation( + // lhs.0, + // &EqualityAndInequality::StrictEqual, + // TypeId::NULL_TYPE, + // &mut checking_data.types, + // checking_data.options.strict_casts, + // )?; + // Ok(new_conditional_context( + // environment, + // (is_lhs_null, lhs.1), + // |env: &mut Environment, data: &mut CheckingData| { + // A::synthesise_expression(rhs, TypeId::ANY_TYPE, env, data) + // }, + // Some(|_env: &mut Environment, _data: &mut CheckingData| lhs.0), + // checking_data, + // )) + // } else { + let mut diagnostics = Default::default(); + let get_property = crate::types::properties::get_property( on, publicity, - key, + under, + self, + (&mut CheckThings { debug_types: checking_data.options.debug_types }, &mut diagnostics), &mut checking_data.types, - None, - site, - &checking_data.options, - bind_this, + position, + mode, + ); + diagnostics.append_to( + crate::types::calling::CallingContext::Getter, + &mut checking_data.diagnostics_container, ); + // }; + if let Some((kind, result)) = get_property { Ok(match kind { PropertyKind::Getter => Instance::GValue(result), @@ -876,32 +801,26 @@ impl<'a> Environment<'a> { } }) } else { + let keys; + let possibles = if let PropertyKey::String(s) = under { + keys = get_property_key_names_on_a_single_type(on, &checking_data.types, self); + let mut possibles = + crate::get_closest(keys.iter().map(AsRef::as_ref), s).unwrap_or(vec![]); + possibles.sort_unstable(); + possibles + } else { + Vec::new() + }; checking_data.diagnostics_container.add_error(TypeCheckError::PropertyDoesNotExist { - // TODO printing temp - property: match key { - PropertyKey::String(s) => PropertyRepresentation::StringKey(s.to_string()), - PropertyKey::Type(t) => PropertyRepresentation::Type(printing::print_type( - *t, - &checking_data.types, - self, - false, - )), - }, + property: PropertyKeyRepresentation::new(under, self, &checking_data.types), on: crate::diagnostics::TypeStringRepresentation::from_type_id( on, self, &checking_data.types, false, ), - site, - possibles: get_property_key_names_on_a_single_type( - on, - &mut checking_data.types, - self, - ) - .iter() - .map(AsRef::as_ref) - .collect::>(), + position, + possibles, }); Err(()) } @@ -919,12 +838,14 @@ impl<'a> Environment<'a> { if let Some((in_root, crossed_boundary, og_var)) = variable_information { (in_root, crossed_boundary, og_var.clone()) } else { + let possibles = { + let mut possibles = + crate::get_closest(self.get_all_variable_names(), name).unwrap_or(vec![]); + possibles.sort_unstable(); + possibles + }; checking_data.diagnostics_container.add_error( - TypeCheckError::CouldNotFindVariable { - variable: name, - possibles: self.get_all_variable_names(), - position, - }, + TypeCheckError::CouldNotFindVariable { variable: name, possibles, position }, ); return Err(TypeId::ERROR_TYPE); } @@ -985,7 +906,7 @@ impl<'a> Environment<'a> { let ty = checking_data.types.get_type_by_id(current_value); // TODO temp - if let Type::SpecialObject(SpecialObjects::Function(..)) = ty { + if let Type::SpecialObject(SpecialObject::Function(..)) = ty { return Ok(VariableWithValue(og_var.clone(), current_value)); } else if let Type::RootPolyType(PolyNature::Open(_)) = ty { crate::utilities::notify!( @@ -1124,8 +1045,18 @@ impl<'a> Environment<'a> { } } - pub fn throw_value(&mut self, thrown: TypeId, position: SpanWithSource) { + pub fn throw_value(&mut self, thrown: TypeId, position: SpanWithSource, types: &mut TypeStore) { let final_event = FinalEvent::Throw { thrown, position }; + + // WIP + let final_return = + if let ReturnState::Rolling { under, returned: rolling_returning } = self.info.state { + types.new_conditional_type(under, rolling_returning, TypeId::NEVER_TYPE) + } else { + TypeId::NEVER_TYPE + }; + self.info.state = ReturnState::Finished(final_return); + self.info.events.push(final_event.into()); } @@ -1193,7 +1124,6 @@ impl<'a> Environment<'a> { // Add the expected return type instead here // if it fell through to another then it could be bad - crate::utilities::notify!("Here {:?}", expected); let expected_return = checking_data.types.new_error_type(expected); let final_event = FinalEvent::Return { returned: expected_return, @@ -1205,8 +1135,20 @@ impl<'a> Environment<'a> { } } - let final_event = FinalEvent::Return { returned, position: returned_position }; - self.info.events.push(final_event.into()); + { + let final_event = FinalEvent::Return { returned, position: returned_position }; + self.info.events.push(final_event.into()); + + // WIP + let final_return = if let ReturnState::Rolling { under, returned: rolling_returning } = + self.info.state + { + checking_data.types.new_conditional_type(under, rolling_returning, returned) + } else { + returned + }; + self.info.state = ReturnState::Finished(final_return); + } } pub fn add_continue( @@ -1251,26 +1193,38 @@ impl<'a> Environment<'a> { /// /// Returns the result of the setter... TODO could return new else #[allow(clippy::too_many_arguments)] - pub fn set_property( + pub fn set_property_handle_errors( &mut self, on: TypeId, publicity: Publicity, under: &PropertyKey, new: TypeId, - types: &mut TypeStore, setter_position: SpanWithSource, - options: &TypeCheckOptions, - ) -> Result, SetPropertyError> { - crate::types::properties::set_property( + checking_data: &mut CheckingData, + ) { + // For setters + let mut diagnostics = Default::default(); + let result = crate::types::properties::set_property( on, - publicity, - under, - PropertyValue::Value(new), - self, - &mut CheckThings { debug_types: options.debug_types }, - types, + (publicity, under, new), setter_position, - ) + self, + (&mut CheckThings { debug_types: checking_data.options.debug_types }, &mut diagnostics), + &mut checking_data.types, + ); + diagnostics.append_to( + crate::types::calling::CallingContext::Setter, + &mut checking_data.diagnostics_container, + ); + + match result { + Ok(()) => {} + Err(error) => { + checking_data + .diagnostics_container + .add_error(TypeCheckError::SetPropertyError(error)); + } + } } /// `continue` has different behavior to `break` right? @@ -1325,23 +1279,240 @@ impl<'a> Environment<'a> { } None } -} -fn set_property_error_to_type_check_error( - ctx: &impl InformationChain, - error: SetPropertyError, - assignment_span: SpanWithSource, - types: &TypeStore, - new: TypeId, -) -> TypeCheckError<'static> { - match error { - SetPropertyError::NotWriteable => TypeCheckError::PropertyNotWriteable(assignment_span), - SetPropertyError::DoesNotMeetConstraint { property_constraint, reason: _ } => { - TypeCheckError::AssignmentError(AssignmentError::PropertyConstraint { - property_constraint, - value_type: TypeStringRepresentation::from_type_id(new, ctx, types, false), - assignment_position: assignment_span, - }) + pub fn declare_interface<'b, A: crate::ASTImplementation>( + &mut self, + name: &str, + parameters: Option<&'b [A::TypeParameter<'b>]>, + extends: Option<&'b [A::TypeAnnotation<'b>]>, + types: &mut TypeStore, + ) -> Result { + // Interface merging + { + if let Some(id) = self.named_types.get(name) { + let ty = types.get_type_by_id(*id); + return if let Type::Interface { .. } = ty { + Ok(DeclareInterfaceResult::Merging { ty: *id, in_same_context: true }) + } else { + Err(AlreadyExists) + }; + } + + // It is fine that it doesn't necessarily need to be an interface + let result = self + .parents_iter() + .find_map(|env| get_on_ctx!(env.named_types.get(name))) + .and_then(|id| { + matches!(types.get_type_by_id(*id), Type::Interface { .. }).then_some(*id) + }); + + if let Some(existing) = result { + return Ok(DeclareInterfaceResult::Merging { + ty: existing, + in_same_context: false, + }); + }; + } + + let parameters = parameters.map(|parameters| { + parameters + .iter() + .map(|parameter| { + let ty = Type::RootPolyType(PolyNature::StructureGeneric { + name: A::type_parameter_name(parameter).to_owned(), + // This is assigned later + extends: TypeId::ANY_TO_INFER_TYPE, + }); + types.register_type(ty) + }) + .collect() + }); + + let ty = Type::Interface { + name: name.to_owned(), + parameters, + extends: extends.map(|_| TypeId::ANY_TO_INFER_TYPE), + }; + let interface_ty = types.register_type(ty); + self.named_types.insert(name.to_owned(), interface_ty); + Ok(DeclareInterfaceResult::New(interface_ty)) + } + + /// Registers the class type + pub fn declare_class<'b, A: crate::ASTImplementation>( + &mut self, + name: &str, + type_parameters: Option<&'b [A::TypeParameter<'b>]>, + types: &mut TypeStore, + ) -> Result { + { + // Special + if let Some(Scope::DefinitionModule { .. }) = + self.context_type.as_syntax().map(|s| &s.scope) + { + match name { + "Array" => { + return Ok(TypeId::ARRAY_TYPE); + } + "Promise" => { + return Ok(TypeId::PROMISE_TYPE); + } + "String" => { + return Ok(TypeId::STRING_TYPE); + } + "Number" => { + return Ok(TypeId::NUMBER_TYPE); + } + "Boolean" => { + return Ok(TypeId::BOOLEAN_TYPE); + } + "Function" => { + return Ok(TypeId::FUNCTION_TYPE); + } + "ImportMeta" => { + return Ok(TypeId::IMPORT_META); + } + _ => {} + } + } + } + + let type_parameters = type_parameters.map(|type_parameters| { + type_parameters + .iter() + .map(|parameter| { + let ty = Type::RootPolyType(PolyNature::StructureGeneric { + name: A::type_parameter_name(parameter).to_owned(), + //A::parameter_constrained(parameter), + // TODO + extends: TypeId::ANY_TO_INFER_TYPE, + }); + types.register_type(ty) + }) + .collect() + }); + + let ty = Type::Class { name: name.to_owned(), type_parameters }; + let class_type = types.register_type(ty); + self.named_types.insert(name.to_owned(), class_type); + // TODO duplicates + + Ok(class_type) + } + + pub fn declare_alias<'b, A: crate::ASTImplementation>( + &mut self, + name: &str, + parameters: Option<&'b [A::TypeParameter<'b>]>, + _position: Span, + types: &mut TypeStore, + ) -> Result { + let parameters = parameters.map(|parameters| { + parameters + .iter() + .map(|parameter| { + let ty = Type::RootPolyType(PolyNature::StructureGeneric { + name: A::type_parameter_name(parameter).to_owned(), + // Set later for recursion + extends: TypeId::ANY_TO_INFER_TYPE, + }); + types.register_type(ty) + }) + .collect() + }); + + let ty = Type::AliasTo { to: TypeId::ANY_TO_INFER_TYPE, name: name.to_owned(), parameters }; + let alias_ty = types.register_type(ty); + let existing_type = self.named_types.insert(name.to_owned(), alias_ty); + + if existing_type.is_none() { + Ok(alias_ty) + } else { + Err(AlreadyExists) } } + + // TODO copy this logic for interface and class + pub fn register_alias<'b, U: crate::ReadFromFS, A: crate::ASTImplementation>( + &mut self, + on: TypeId, + ast_parameters: Option<&'b [A::TypeParameter<'b>]>, + to: &'b A::TypeAnnotation<'b>, + position: Span, + checking_data: &mut CheckingData, + ) { + if on == TypeId::ERROR_TYPE { + return; + } + + let new_to = if let Type::AliasTo { to: _, parameters: Some(parameters), name: _ } = + checking_data.types.get_type_by_id(on) + { + let mut sub_environment = self.new_lexical_environment(Scope::TypeAlias); + let parameters = parameters.clone(); + for parameter in parameters.iter().copied() { + let Type::RootPolyType(PolyNature::StructureGeneric { name, .. }) = + checking_data.types.get_type_by_id(parameter) + else { + unreachable!() + }; + sub_environment.named_types.insert(name.clone(), parameter); + } + for (parameter, ast_parameter) in parameters.into_iter().zip(ast_parameters.unwrap()) { + let new_to = A::synthesise_type_parameter_extends( + ast_parameter, + &mut sub_environment, + checking_data, + ); + checking_data.types.update_generic_extends(parameter, new_to); + } + let ty = A::synthesise_type_annotation(to, &mut sub_environment, checking_data); + // TODO temp as object types use the same environment.properties representation + { + let crate::LocalInformation { current_properties, prototypes, .. } = + sub_environment.info; + self.info.current_properties.extend(current_properties); + self.info.prototypes.extend(prototypes); + } + + // TODO check cycles + ty + } else { + let ty = A::synthesise_type_annotation(to, self, checking_data); + + // Cycle checking + let disjoint = crate::types::disjoint::types_are_disjoint( + ty, + on, + &mut Vec::new(), + self, + &checking_data.types, + ); + + if disjoint { + ty + } else { + checking_data.diagnostics_container.add_error(TypeCheckError::CyclicTypeAlias { + position: position.with_source(self.get_source()), + }); + TypeId::ERROR_TYPE + } + }; + + checking_data.types.update_alias(on, new_to); + } + + /// For functions and classes + pub(crate) fn register_constructable_function( + &mut self, + referenced_in_scope_as: TypeId, + function: crate::FunctionId, + ) { + self.info.events.push(Event::Miscellaneous( + crate::events::MiscellaneousEvents::CreateConstructor { + referenced_in_scope_as, + function, + }, + )); + } } diff --git a/checker/src/context/information.rs b/checker/src/context/information.rs index 51212f22..480eefbc 100644 --- a/checker/src/context/information.rs +++ b/checker/src/context/information.rs @@ -1,16 +1,19 @@ use source_map::SpanWithSource; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use crate::{ events::{Event, RootReference}, - features::functions::{ClosureId, ThisValue}, + features::functions::ClosureId, types::{ + calling::ThisValue, properties::{PropertyKey, Publicity}, TypeStore, }, PropertyValue, Type, TypeId, VariableId, }; +pub type Properties = Vec<(Publicity, PropertyKey<'static>, PropertyValue)>; + /// Things that are currently true or have happened #[derive(Debug, Default, binary_serialize_derive::BinarySerializable)] pub struct LocalInformation { @@ -20,53 +23,104 @@ pub struct LocalInformation { /// This can be not have a value if not defined pub(crate) variable_current_value: HashMap, - pub(crate) current_properties: - HashMap, PropertyValue)>>, + pub(crate) current_properties: HashMap, /// Can be modified (unfortunately) so here pub(crate) prototypes: HashMap, pub(crate) closure_current_values: HashMap<(ClosureId, RootReference), TypeId>, - pub(crate) configurable: HashMap<(TypeId, TypeId), TypeId>, - pub(crate) enumerable: HashMap<(TypeId, TypeId), TypeId>, - pub(crate) writable: HashMap<(TypeId, TypeId), TypeId>, - pub(crate) frozen: HashMap, + /// Not writeable, `TypeError: Cannot add property t, object is not extensible`. TODO conditional ? + pub(crate) frozen: HashSet, /// Object type (LHS), must always be RHS /// /// *not quite the best place, but used in [`InformationChain`]* pub(crate) object_constraints: HashMap, + pub(crate) state: ReturnState, + /// For super calls etc /// /// TODO not great that this has to be Option to satisfy Default pub(crate) value_of_this: ThisValue, } +#[derive(Debug, Default, binary_serialize_derive::BinarySerializable, Clone)] +pub(crate) enum ReturnState { + #[default] + Continued, + Rolling { + under: TypeId, + returned: TypeId, + }, + Finished(TypeId), +} + +impl ReturnState { + pub(crate) fn is_finished(&self) -> bool { + matches!(self, ReturnState::Finished(..)) + } + + pub(crate) fn get_returned(self, types: &mut TypeStore) -> TypeId { + match self { + ReturnState::Continued => TypeId::UNDEFINED_TYPE, + ReturnState::Rolling { under, returned } => { + types.new_conditional_type(under, returned, TypeId::UNDEFINED_TYPE) + } + ReturnState::Finished(ty) => ty, + } + } + + pub(crate) fn append(&mut self, new: ReturnState) { + match self { + ReturnState::Continued => *self = new, + ReturnState::Rolling { .. } => match new { + ReturnState::Continued => todo!(), + ReturnState::Rolling { .. } => todo!(), + new @ ReturnState::Finished(_) => { + crate::utilities::notify!("Warning overwriting conditional"); + *self = new; + } + }, + ReturnState::Finished(_) => todo!(), + } + } +} + impl LocalInformation { - /// TODO temp - pub fn register_property( + /// For interfaces only + pub fn register_property_on_type( &mut self, on: TypeId, publicity: Publicity, under: PropertyKey<'static>, to: PropertyValue, - register_setter_event: bool, + ) { + self.current_properties.entry(on).or_default().push((publicity, under, to)); + } + pub fn register_property( + &mut self, + on: TypeId, + publicity: Publicity, + under: PropertyKey<'static>, + value: PropertyValue, position: SpanWithSource, ) { - // crate::utilities::notify!("Registering {:?} {:?} {:?}", on, under, to); - self.current_properties.entry(on).or_default().push((publicity, under.clone(), to.clone())); - if register_setter_event { - self.events.push(Event::Setter { + self.current_properties.entry(on).or_default().push(( + publicity, + under.clone(), + value.clone(), + )); + self.events.push(Event::Miscellaneous( + crate::events::MiscellaneousEvents::RegisterProperty { on, under, - new: to, - initialization: true, + value, publicity, position, - }); - } + }, + )); } // /// This is how invocation contexts register throws... @@ -79,6 +133,31 @@ impl LocalInformation { &self.events } + /// Use `features::delete_property` + pub(crate) fn delete_property( + &mut self, + on: TypeId, + (publicity, key): (Publicity, PropertyKey<'static>), + position: SpanWithSource, + option: Option, + ) { + // on_default() okay because might be in a nested context. + // entry empty does not mean no properties, just no properties set on this level + self.current_properties.entry(on).or_default().push(( + publicity, + key.clone(), + PropertyValue::Deleted, + )); + + self.events.push(Event::Miscellaneous(crate::events::MiscellaneousEvents::Delete { + on, + publicity, + under: key, + into: option, + position, + })); + } + pub(crate) fn new_object( &mut self, prototype: Option, @@ -126,9 +205,6 @@ impl LocalInformation { self.current_properties.extend(other.current_properties); self.prototypes.extend(other.prototypes); self.closure_current_values.extend(other.closure_current_values); - self.configurable.extend(other.configurable); - self.enumerable.extend(other.enumerable); - self.writable.extend(other.writable); self.frozen.extend(other.frozen); } @@ -142,24 +218,12 @@ impl LocalInformation { .extend(other.current_properties.iter().map(|(l, r)| (*l, r.clone()))); self.closure_current_values .extend(other.closure_current_values.iter().map(|(l, r)| (l.clone(), *r))); - self.configurable.extend(other.configurable.iter().clone()); - self.enumerable.extend(other.enumerable.iter().clone()); - self.writable.extend(other.writable.iter().clone()); self.frozen.extend(other.frozen.iter().clone()); } #[must_use] pub fn is_halted(&self) -> bool { - if let Some(last) = self.events.last() { - if let Event::FinalEvent(_) = last { - true - } else { - crate::utilities::notify!("TODO ifs others"); - false - } - } else { - false - } + self.state.is_finished() } } @@ -173,6 +237,17 @@ impl InformationChain for LocalInformation { } } +pub struct ModuleInformation<'a> { + pub top: &'a LocalInformation, + pub module: &'a LocalInformation, +} + +impl<'a> InformationChain for ModuleInformation<'a> { + fn get_chain_of_info(&self) -> impl Iterator { + IntoIterator::into_iter([self.top, self.module]) + } +} + pub(crate) fn get_value_of_constant_import_variable( variable: VariableId, info: &impl InformationChain, @@ -193,15 +268,23 @@ pub fn merge_info( ) { let truthy_events = truthy.events.len() as u32; let otherwise_events = otherwise.as_ref().map_or(0, |f| f.events.len() as u32); - onto.events.push(Event::Conditionally { condition, truthy_events, otherwise_events, position }); - onto.events.append(&mut truthy.events); - if let Some(ref mut otherwise) = otherwise { - crate::utilities::notify!("truthy {:?} otherwise {:?}", truthy.events, otherwise.events); - onto.events.append(&mut otherwise.events); - } + if truthy_events + otherwise_events != 0 { + onto.events.push(Event::Conditionally { + condition, + truthy_events, + otherwise_events, + position, + }); + + onto.events.append(&mut truthy.events); + if let Some(ref mut otherwise) = otherwise { + // crate::utilities::notify!("truthy events={:?}, otherwise events={:?}", truthy.events, otherwise.events); + onto.events.append(&mut otherwise.events); + } - onto.events.push(Event::EndOfControlFlow(truthy_events + otherwise_events)); + onto.events.push(Event::EndOfControlFlow(truthy_events + otherwise_events)); + } // TODO don't need to do above some scope for (var, true_value) in truthy.variable_current_value { @@ -232,5 +315,41 @@ pub fn merge_info( onto.current_properties.extend(otherwise.current_properties.drain()); } + // TODO I think these are okay + onto.state = match ( + truthy.state, + otherwise.as_ref().map_or(ReturnState::Continued, |o| o.state.clone()), + ) { + (ReturnState::Continued, ReturnState::Continued) => ReturnState::Continued, + (ReturnState::Continued, ReturnState::Rolling { .. }) => todo!(), + (ReturnState::Continued, ReturnState::Finished(returned)) => { + ReturnState::Rolling { under: types.new_logical_negation_type(condition), returned } + } + (ReturnState::Rolling { under, returned }, ReturnState::Continued) => { + ReturnState::Rolling { under: types.new_logical_and_type(condition, under), returned } + } + ( + ReturnState::Rolling { under: truthy_under, returned: truthy_returned }, + ReturnState::Rolling { under: otherwise_under, returned: otherwise_returned }, + ) => { + let under = types.new_logical_or_type(truthy_under, otherwise_under); + let returned = + types.new_conditional_type(condition, truthy_returned, otherwise_returned); + ReturnState::Rolling { under, returned } + } + (ReturnState::Rolling { .. }, ReturnState::Finished(_)) => todo!(), + (ReturnState::Finished(returned), ReturnState::Continued) => { + ReturnState::Rolling { under: condition, returned } + } + (ReturnState::Finished(_), ReturnState::Rolling { .. }) => todo!(), + (ReturnState::Finished(truthy_return), ReturnState::Finished(otherwise_return)) => { + ReturnState::Finished(types.new_conditional_type( + condition, + truthy_return, + otherwise_return, + )) + } + }; + // TODO set more information? an exit condition? } diff --git a/checker/src/context/invocation.rs b/checker/src/context/invocation.rs index e8cb8b90..b7218ade 100644 --- a/checker/src/context/invocation.rs +++ b/checker/src/context/invocation.rs @@ -1,7 +1,12 @@ //! When a function is called (or a group of events like function such as a iteration block) it creates a mini-environment for which events are applied into -use super::information::LocalInformation; -use crate::{events::ApplicationResult, types::TypeStore, Environment, FunctionId, TypeId}; +use source_map::SpanWithSource; + +use super::LocalInformation; +use crate::{ + context::information::merge_info, events::ApplicationResult, types::TypeStore, Environment, + FunctionId, TypeId, +}; /// For anything that might involve a call, including gets, sets and actual calls pub trait CallCheckingBehavior { @@ -71,6 +76,7 @@ pub(crate) enum InvocationKind { AlwaysTrue, Function(FunctionId), LoopIteration, + Unknown, } impl CallCheckingBehavior for InvocationContext { @@ -117,6 +123,21 @@ impl InvocationContext { InvocationContext(Vec::new()) } + pub(crate) fn in_unknown(&self) -> bool { + self.0.iter().any(|c| matches!(c, InvocationKind::Unknown)) + } + + /// WIP + pub(crate) fn new_unknown_target( + &mut self, + cb: impl for<'a> FnOnce(&'a mut InvocationContext) -> T, + ) -> T { + self.0.push(InvocationKind::Unknown); + let result = cb(self); + self.0.pop(); + result + } + fn new_conditional_target( &mut self, cb: impl for<'a> FnOnce(&'a mut InvocationContext) -> T, @@ -168,7 +189,7 @@ impl InvocationContext { &mut self, top_environment: &mut Environment, types: &mut TypeStore, - _condition: TypeId, + (condition, condition_position): (TypeId, SpanWithSource), (input_left, input_right): (T, T), mut data: I, cb: impl for<'a> Fn( @@ -179,12 +200,12 @@ impl InvocationContext { &'a mut I, ) -> R, ) -> (I, (R, R)) { - let (_truthy_info, truthy_result) = + let (truthy_info, truthy_result) = self.new_conditional_target(|target: &mut InvocationContext| { cb(top_environment, types, target, input_left, &mut data) }); - let (_otherwise_info, otherwise_result) = + let (otherwise_info, otherwise_result) = self.new_conditional_target(|target: &mut InvocationContext| { cb(top_environment, types, target, input_right, &mut data) }); @@ -192,10 +213,44 @@ impl InvocationContext { // TODO all things that are // - variable and property values (these aren't read from events) // - immutable, mutable, prototypes etc - let _info = self.get_latest_info(top_environment); + // let info = self.get_latest_info(top_environment); - // TODO - // merge_info(top_environment, info, condition, truthy_info, Some(otherwise_info), types); + if self.0.iter().any(|x| matches!(x, InvocationKind::Conditional(_))) { + todo!("nested, get latest") + // let local_information = &mut top_environment.info; + // match top_environment.context_type.parent { + // crate::GeneralContext::Syntax(env) => { + // merge_info( + // env, + // local_information, + // condition, + // truthy_info, + // Some(otherwise_info), + // types, + // condition_position, + // ); + // } + // crate::GeneralContext::Root(_) => todo!(), + // } + } else { + let local_information = &mut top_environment.info; + match top_environment.context_type.parent { + crate::GeneralContext::Syntax(env) => { + merge_info( + env, + local_information, + condition, + truthy_info, + Some(otherwise_info), + types, + condition_position, + ); + } + crate::GeneralContext::Root(_) => { + crate::utilities::notify!("Top environment is root?"); + } + } + } (data, (truthy_result, otherwise_result)) } diff --git a/checker/src/context/mod.rs b/checker/src/context/mod.rs index 1f809017..f501c254 100644 --- a/checker/src/context/mod.rs +++ b/checker/src/context/mod.rs @@ -10,33 +10,26 @@ mod root; pub(crate) use invocation::CallCheckingBehavior; pub use root::RootContext; -use source_map::{Span, SpanWithSource}; +use source_map::SpanWithSource; use crate::{ context::environment::ExpectedReturnType, - diagnostics::{ - CannotRedeclareVariable, TypeCheckError, TypeCheckWarning, TypeStringRepresentation, TDZ, - }, + diagnostics::{CannotRedeclareVariable, TypeCheckError, TypeStringRepresentation, TDZ}, events::RootReference, features::{ assignments::Reference, functions::ClosureChain, - objects::{Proxy, SpecialObjects}, + objects::SpecialObject, variables::{VariableMutability, VariableOrImport}, }, - types::{ - generics::generic_type_arguments::GenericArguments, FunctionType, PolyNature, Type, TypeId, - TypeStore, - }, - CheckingData, DiagnosticsContainer, FunctionId, VariableId, + types::{FunctionType, PolyNature, Type, TypeId, TypeStore}, + CheckingData, DiagnosticsContainer, FunctionId, TypeMappings, VariableId, }; -use self::{ - environment::{DynamicBoundaryKind, FunctionScope}, - information::{InformationChain, LocalInformation}, -}; +use self::environment::{DynamicBoundaryKind, FunctionScope}; pub use environment::Scope; pub(crate) use environment::Syntax; +pub use information::{InformationChain, LocalInformation, Properties}; use std::{ collections::{ @@ -173,7 +166,7 @@ pub struct Context { pub(crate) can_reference_this: CanReferenceThis, /// When a objects `TypeId` is in here getting a property returns a constructor rather than - pub possibly_mutated_objects: HashSet, + pub possibly_mutated_objects: HashMap, pub possibly_mutated_variables: HashSet, // pub (crate) info: info, @@ -184,6 +177,8 @@ pub struct VariableRegisterArguments { pub constant: bool, pub space: Option, pub initial_value: Option, + /// for `var` declarations + pub allow_reregistration: bool, } #[derive(Debug, Clone, binary_serialize_derive::BinarySerializable)] @@ -203,7 +198,7 @@ impl Context { &mut self, name: &'b str, declared_at: SpanWithSource, - VariableRegisterArguments { constant, initial_value, space }: VariableRegisterArguments, + VariableRegisterArguments { constant, initial_value, space, allow_reregistration }: VariableRegisterArguments, record_event: bool, ) -> Result<(), CannotRedeclareVariable<'b>> { let id = VariableId(declared_at.source, declared_at.start); @@ -214,31 +209,43 @@ impl Context { VariableMutability::Mutable { reassignment_constraint: space } }; - let variable = VariableOrImport::Variable { declared_at, mutability, context: None }; + let variable = VariableOrImport::Variable { + declared_at, + mutability, + context: None, + allow_reregistration, + }; - let existing = match self.variables.entry(name.to_owned()) { - Entry::Occupied(_) => true, + let entry = self.variables.entry(name.to_owned()); + let existing_that_can_be_rewritten = match entry { + Entry::Occupied(e) => match e.get() { + VariableOrImport::Variable { allow_reregistration, .. } => !*allow_reregistration, + VariableOrImport::MutableImport { .. } + | VariableOrImport::ConstantImport { .. } => true, + }, Entry::Vacant(vacant) => { vacant.insert(variable); + self.variable_names.insert(id, name.to_owned()); false } }; - self.variable_names.insert(id, name.to_owned()); - - if let Some(initial_value) = initial_value { - self.info.variable_current_value.insert(id, initial_value); - } + // Still set data regardless + { + if let Some(initial_value) = initial_value { + self.info.variable_current_value.insert(id, initial_value); + } - if record_event { - self.info.events.push(crate::events::Event::RegisterVariable { - name: name.to_owned(), - position: declared_at, - initial_value, - }); + if record_event { + self.info.events.push(crate::events::Event::RegisterVariable { + name: name.to_owned(), + position: declared_at, + initial_value, + }); + } } - if existing { + if existing_that_can_be_rewritten { Err(CannotRedeclareVariable { name }) } else { Ok(()) @@ -251,9 +258,23 @@ impl Context { argument: VariableRegisterArguments, declared_at: SpanWithSource, diagnostics_container: &mut DiagnosticsContainer, + type_mappings: &mut TypeMappings, record_event: bool, ) { - if let Err(_err) = self.register_variable(name, declared_at, argument, record_event) { + if argument.allow_reregistration { + if let Some(existing) = self.variables.get(name) { + type_mappings.var_aliases.insert(declared_at.start, existing.get_id()); + } + } + + if let Some(reassignment_constraint) = argument.space { + let id = crate::VariableId(self.get_source(), declared_at.start); + type_mappings.variables_to_constraints.0.insert(id, reassignment_constraint); + } + + let register_variable = self.register_variable(name, declared_at, argument, record_event); + + if let Err(CannotRedeclareVariable { name }) = register_variable { diagnostics_container.add_error(TypeCheckError::CannotRedeclareVariable { name: name.to_owned(), position: declared_at, @@ -461,21 +482,19 @@ impl Context { } #[allow(clippy::map_flatten)] - pub fn get_all_variable_names(&self) -> Vec<&str> { + pub fn get_all_variable_names(&self) -> impl Iterator { self.parents_iter() - .map(|env| get_on_ctx!(env.variables.keys()).collect::>()) + .map(|env| get_on_ctx!(env.variables.keys())) .flatten() .map(AsRef::as_ref) - .collect::>() } #[allow(clippy::map_flatten)] - pub fn get_all_named_types(&self) -> Vec<&str> { + pub fn get_all_named_types(&self) -> impl Iterator { self.parents_iter() - .map(|env| get_on_ctx!(env.named_types.keys()).collect::>()) + .map(|env| get_on_ctx!(env.named_types.keys())) .flatten() .map(AsRef::as_ref) - .collect::>() } pub(crate) fn get_variable_name(&self, id: VariableId) -> &str { @@ -692,6 +711,7 @@ impl Context { None } else if self.context_type.get_parent().is_some() { self.info.events.append(&mut info.events); + self.info.state.append(info.state); None } else { Some((info, Default::default())) @@ -717,6 +737,10 @@ impl Context { }) } + /// ```ts + /// (...) + /// ^ + /// ``` pub fn new_explicit_type_parameter( &mut self, name: &str, @@ -727,7 +751,7 @@ impl Context { let ty = Type::RootPolyType(PolyNature::FunctionGeneric { name: name.to_owned(), // TODO this is fixed!! - eager_fixed: constraint_type.unwrap_or(TypeId::ANY_TYPE), + extends: constraint_type.unwrap_or(TypeId::ANY_TYPE), }); let ty = types.register_type(ty); @@ -753,184 +777,17 @@ impl Context { if let Some(val) = self.get_type_from_name(name) { val } else { - checking_data.diagnostics_container.add_error(TypeCheckError::CouldNotFindType( - name, - self.get_all_named_types(), - pos, - )); - - TypeId::ERROR_TYPE - } - } - - /// TODO extends - pub fn register_interface<'a, U: crate::ReadFromFS, A: crate::ASTImplementation>( - &mut self, - name: &str, - nominal: bool, - parameters: Option<&'a [A::TypeParameter<'a>]>, - _extends: Option<&'a [A::TypeAnnotation<'a>]>, - position: SpanWithSource, - checking_data: &mut CheckingData, - ) -> TypeId { - // Interface merging - { - let existing = if let Some(id) = self.named_types.get(name) { - if let Type::Interface { .. } = checking_data.types.get_type_by_id(*id) { - checking_data - .diagnostics_container - .add_warning(TypeCheckWarning::MergingInterfaceInSameContext { position }); - - Some(*id) - } else { - checking_data.diagnostics_container.add_error( - TypeCheckError::TypeAlreadyDeclared { name: name.to_owned(), position }, - ); - return TypeId::ERROR_TYPE; - } - } else { - self.parents_iter().find_map(|env| get_on_ctx!(env.named_types.get(name))).and_then( - |id| { - matches!(checking_data.types.get_type_by_id(*id), Type::Interface { .. }) - .then_some(*id) - }, - ) - }; - - if let Some(existing) = existing { - return existing; + let possibles = { + let mut possibles = + crate::get_closest(self.get_all_named_types(), name).unwrap_or(vec![]); + possibles.sort_unstable(); + possibles }; - } + checking_data + .diagnostics_container + .add_error(TypeCheckError::CouldNotFindType(name, possibles, pos)); - let parameters = parameters.map(|parameters| { - parameters - .iter() - .map(|parameter| { - let ty = Type::RootPolyType(PolyNature::FunctionGeneric { - name: A::type_parameter_name(parameter).to_owned(), - // This is assigned later - eager_fixed: TypeId::ANY_TYPE, - }); - checking_data.types.register_type(ty) - }) - .collect() - }); - - let ty = Type::Interface { name: name.to_owned(), nominal, parameters }; - let interface_ty = checking_data.types.register_type(ty); - self.named_types.insert(name.to_owned(), interface_ty); - interface_ty - } - - /// Registers the class type - pub fn register_class<'a, A: crate::ASTImplementation>( - &mut self, - name: &str, - parameters: Option<&'a [A::TypeParameter<'a>]>, - _extends: Option<&'a A::Expression<'a>>, - types: &mut TypeStore, - ) -> TypeId { - { - // Special - if let Some(Scope::DefinitionModule { .. }) = - self.context_type.as_syntax().map(|s| &s.scope) - { - match name { - "Array" => { - return TypeId::ARRAY_TYPE; - } - "Promise" => { - return TypeId::PROMISE_TYPE; - } - "String" => { - return TypeId::STRING_TYPE; - } - "Number" => { - return TypeId::NUMBER_TYPE; - } - "Boolean" => { - return TypeId::BOOLEAN_TYPE; - } - "ImportMeta" => { - return TypeId::IMPORT_META; - } - _ => {} - } - } - } - - let parameters = parameters.map(|parameters| { - parameters - .iter() - .map(|parameter| { - let ty = Type::RootPolyType(PolyNature::StructureGeneric { - name: A::type_parameter_name(parameter).to_owned(), - constrained: A::parameter_constrained(parameter), - }); - types.register_type(ty) - }) - .collect() - }); - - let ty = Type::Class { name: name.to_owned(), parameters }; - let interface_ty = types.register_type(ty); - self.named_types.insert(name.to_owned(), interface_ty); - interface_ty - } - - pub fn new_alias<'a, U: crate::ReadFromFS, A: crate::ASTImplementation>( - &mut self, - name: &str, - parameters: Option<&'a [A::TypeParameter<'a>]>, - to: &'a A::TypeAnnotation<'a>, - position: Span, - checking_data: &mut CheckingData, - ) -> TypeId { - // Doing this as may be a bit faster maybe? - let mut env = self.new_lexical_environment(Scope::TypeAlias); - - let (parameters, to) = if let Some(parameters) = parameters { - let parameters = parameters - .iter() - .map(|parameter| { - let name = A::type_parameter_name(parameter).to_owned(); - let ty = Type::RootPolyType(PolyNature::FunctionGeneric { - name: name.clone(), - eager_fixed: TypeId::ANY_TYPE, - }); - let ty = checking_data.types.register_type(ty); - // TODO declare type - env.named_types.insert(name, ty); - ty - }) - .collect(); - - let to = A::synthesise_type_annotation(to, &mut env, checking_data); - (Some(parameters), to) - } else { - // TODO should just use self - let to = A::synthesise_type_annotation(to, &mut env, checking_data); - (None, to) - }; - - // TODO temp as object types use the same environment.properties representation - env.info.current_properties.into_iter().for_each(|(t, mut props)| { - self.info.current_properties.entry(t).or_default().append(&mut props); - }); - - // Works as an alias - let ty = Type::AliasTo { to, name: name.to_owned(), parameters }; - let alias_ty = checking_data.types.register_type(ty); - let existing_type = self.named_types.insert(name.to_owned(), alias_ty); - - if existing_type.is_some() { - checking_data.diagnostics_container.add_error(TypeCheckError::TypeAlreadyDeclared { - name: name.to_owned(), - position: position.with_source(self.get_source()), - }); TypeId::ERROR_TYPE - } else { - alias_ty } } @@ -939,16 +796,6 @@ impl Context { id: VariableId, value_ty: TypeId, ) { - if let Some(_closed_over_references) = - self.context_type.as_syntax().map(|s| &s.closed_over_references) - { - // crate::utilities::notify!( - // "is {:?} closed over {:?}", - // id, - // closed_over_references.get(&RootReference::Variable(id)) - // ); - } - self.info.variable_current_value.insert(id, value_ty); } @@ -964,13 +811,18 @@ impl Context { let id = crate::VariableId(declared_at.source, declared_at.start); let kind = VariableMutability::Constant; - let variable = VariableOrImport::Variable { declared_at, mutability: kind, context }; + let variable = VariableOrImport::Variable { + declared_at, + mutability: kind, + context, + allow_reregistration: false, + }; let entry = self.variables.entry(name.to_owned()); if let Entry::Vacant(vacant) = entry { vacant.insert(variable); // TODO unsure ... - let ty = if let Type::SpecialObject(SpecialObjects::Function(..)) = + let ty = if let Type::SpecialObject(SpecialObject::Function(..)) = types.get_type_by_id(variable_ty) { variable_ty @@ -1044,6 +896,7 @@ impl Context { }) } + /// WIP for tree shaking pub(crate) fn is_always_run(&self) -> bool { !self.parents_iter().any(|ctx| { if let GeneralContext::Syntax(s) = ctx { @@ -1122,6 +975,16 @@ impl Context { } }) } + + pub(crate) fn get_prototype(&self, on: TypeId) -> TypeId { + if let Some(prototype) = self.info.prototypes.get(&on) { + *prototype + } else if let Some(parent) = self.context_type.get_parent() { + get_on_ctx!(parent.get_prototype(on)) + } else { + TypeId::OBJECT_TYPE + } + } } pub enum AssignmentError { @@ -1134,9 +997,9 @@ pub enum AssignmentError { /// Covers both assignment and declaration DoesNotMeetConstraint { variable_type: TypeStringRepresentation, - variable_site: SpanWithSource, + variable_position: SpanWithSource, value_type: TypeStringRepresentation, - value_site: SpanWithSource, + value_position: SpanWithSource, }, PropertyConstraint { property_constraint: TypeStringRepresentation, @@ -1146,45 +1009,6 @@ pub enum AssignmentError { TDZ(TDZ), } -/// Wraps logic -#[derive(Debug, Clone)] -pub enum Logical { - Pure(T), - /// Note this uses [`PossibleLogical`] rather than [`Logical`] - Or { - /// This can be [`TypeId::BOOLEAN_TYPE`] for unknown left-right-ness - condition: TypeId, - left: Box>, - right: Box>, - }, - Implies { - on: Box, - antecedent: GenericArguments, - }, -} - -#[derive(Debug, Clone)] -pub enum MissingOrToCalculate { - /// Doesn't contain request - Missing, - /// From [`TypeId::ERROR_TYPE`] - Error, - /// From [`TypeId::ANY_TYPE`] - Infer { on: TypeId }, - /// Proxies require extra work in some cases - Proxy(Proxy), -} - -pub type PossibleLogical = Result, MissingOrToCalculate>; - -pub enum SetPropertyError { - NotWriteable, - DoesNotMeetConstraint { - property_constraint: TypeStringRepresentation, - reason: crate::types::subtyping::NonEqualityReason, - }, -} - /// TODO mutable let imports pub(crate) fn get_value_of_variable( info: &impl InformationChain, diff --git a/checker/src/context/root.rs b/checker/src/context/root.rs index 221f626d..4c6bf909 100644 --- a/checker/src/context/root.rs +++ b/checker/src/context/root.rs @@ -1,11 +1,14 @@ -use super::{ClosedOverReferencesInScope, Context, ContextId, ContextType}; +use super::{ + ClosedOverReferencesInScope, Context, ContextId, ContextType, Environment, GeneralContext, + LocalInformation, +}; use crate::{ features::{ modules::{Exported, SynthesisedModule}, variables::VariableOrImport, }, types::TypeId, - CheckingData, Environment, GeneralContext, + CheckingData, }; use source_map::SourceId; use std::{collections::HashMap, iter::FromIterator, mem}; @@ -33,8 +36,6 @@ impl ContextType for Root { } } -const _CONTEXT_FILE_HEADER: &[u8] = b"EZNO\0CONTEXT\0FILE"; - impl RootContext { /// Merges two [`RootEnvironments`]. May be used for multiple `.d.ts` files pub(crate) fn _union(&mut self, other: Self) { @@ -68,16 +69,29 @@ impl RootContext { ("object".to_owned(), TypeId::OBJECT_TYPE), ("Literal".to_owned(), TypeId::LITERAL_RESTRICTION), ("Readonly".to_owned(), TypeId::READONLY_RESTRICTION), + ("Exclusive".to_owned(), TypeId::EXCLUSIVE_RESTRICTION), + ("Uppercase".to_owned(), TypeId::STRING_UPPERCASE), + ("Lowercase".to_owned(), TypeId::STRING_LOWERCASE), + ("Capitalize".to_owned(), TypeId::STRING_CAPITALIZE), + ("Uncapitalize".to_owned(), TypeId::STRING_UNCAPITALIZE), + ("NoInfer".to_owned(), TypeId::NO_INFER), + ("LessThan".to_owned(), TypeId::LESS_THAN), + ("GreaterThan".to_owned(), TypeId::GREATER_THAN), + ("MultipleOf".to_owned(), TypeId::MULTIPLE_OF), + ("NotNotANumber".to_owned(), TypeId::NOT_NOT_A_NUMBER), + ("Not".to_owned(), TypeId::NOT_RESTRICTION), + ("CaseInsensitive".to_owned(), TypeId::CASE_INSENSITIVE), ]); - let mut info = crate::LocalInformation::default(); + let mut info = LocalInformation::default(); - // Add undefined + // Add undefined as a variable let variables = { let variable_or_import = VariableOrImport::Variable { mutability: crate::features::variables::VariableMutability::Constant, declared_at: source_map::Nullable::NULL, context: None, + allow_reregistration: false, }; let undefined_id = variable_or_import.get_id(); let variables = [("undefined".to_owned(), variable_or_import)]; diff --git a/checker/src/diagnostics.rs b/checker/src/diagnostics.rs index 9b7fe104..d2c68fad 100644 --- a/checker/src/diagnostics.rs +++ b/checker/src/diagnostics.rs @@ -1,13 +1,15 @@ //! Contains type checking errors, warnings and related structures - -#![allow(clippy::upper_case_acronyms)] - use crate::{ - context::{environment::Label, information::InformationChain}, - diagnostics, get_closest, + context::{environment::Label, AssignmentError, InformationChain}, + diagnostics, + features::{ + modules::CouldNotOpenFile, operations::MathematicalAndBitwise, CannotDeleteFromError, + }, types::{ - calling::FunctionCallingError, printing::print_type_with_type_arguments, GenericChain, - GenericChainLink, + calling::FunctionCallingError, + printing::print_type_with_type_arguments, + properties::{assignment::SetPropertyError, PropertyKey}, + GenericChain, GenericChainLink, }, }; use source_map::{SourceId, SpanWithSource}; @@ -50,6 +52,7 @@ pub enum Diagnostic { } /// Temporary dead zone. Between the variable identifier being hoisted and the value being assigned +#[allow(clippy::upper_case_acronyms)] pub struct TDZ { pub variable_name: String, pub position: SpanWithSource, @@ -153,6 +156,10 @@ impl DiagnosticsContainer { Ok(self) } } + + pub fn count(&self) -> usize { + self.diagnostics.len() + } } impl IntoIterator for DiagnosticsContainer { @@ -168,15 +175,28 @@ impl IntoIterator for DiagnosticsContainer { use crate::types::{printing::print_type, TypeId, TypeStore}; /// TODO could be more things, for instance a property missing etc -pub enum TypeStringRepresentation { - Type(String), -} +pub struct TypeStringRepresentation(String); -pub enum PropertyRepresentation { +pub enum PropertyKeyRepresentation { Type(String), StringKey(String), } +impl PropertyKeyRepresentation { + pub fn new( + under: &PropertyKey, + environment: &impl InformationChain, + types: &TypeStore, + ) -> PropertyKeyRepresentation { + match under.clone() { + PropertyKey::String(s) => PropertyKeyRepresentation::StringKey(s.to_string()), + PropertyKey::Type(t) => { + PropertyKeyRepresentation::Type(print_type(t, types, environment, false)) + } + } + } +} + impl TypeStringRepresentation { #[must_use] pub fn from_type_id( @@ -186,7 +206,7 @@ impl TypeStringRepresentation { debug_mode: bool, ) -> Self { let value = print_type(id, types, ctx, debug_mode); - Self::Type(value) + Self(value) } #[must_use] @@ -198,68 +218,66 @@ impl TypeStringRepresentation { debug_mode: bool, ) -> Self { let value = print_type_with_type_arguments(id, type_arguments, types, ctx, debug_mode); - Self::Type(value) + Self(value) } /// TODO working it out pub(crate) fn from_property_constraint( - property_constraint: crate::context::Logical, + property_constraint: crate::types::logical::Logical, generics: GenericChain, ctx: &impl InformationChain, types: &TypeStore, debug_mode: bool, ) -> TypeStringRepresentation { match property_constraint { - crate::context::Logical::Pure(constraint) => match constraint { + crate::types::logical::Logical::Pure(constraint) => match constraint.inner_simple() { crate::PropertyValue::Value(v) => { - let value = print_type_with_type_arguments(v, generics, types, ctx, debug_mode); - Self::Type(value) + let value = + print_type_with_type_arguments(*v, generics, types, ctx, debug_mode); + Self(value) } + crate::PropertyValue::GetterAndSetter { .. } => todo!(), crate::PropertyValue::Getter(_) => todo!(), crate::PropertyValue::Setter(_) => todo!(), - crate::PropertyValue::Deleted => todo!(), - crate::PropertyValue::ConditionallyExists { .. } => todo!(), + crate::PropertyValue::Deleted => Self("never".to_owned()), + crate::PropertyValue::ConditionallyExists { .. } + | crate::PropertyValue::Configured { .. } => unreachable!(), }, - crate::context::Logical::Or { .. } => { - todo!() - // let left = Self::from_property_constraint(*left, None, ctx, types, debug_mode); - // let right = Self::from_property_constraint(*right, None, ctx, types, debug_mode); + crate::types::logical::Logical::Or { condition, left, right } => { + let left_right = (*left, *right); + // if let (Ok(left), Ok(right)) = left_right { + // let mut left = + // Self::from_property_constraint(left, None, ctx, types, debug_mode); + // let right = Self::from_property_constraint(right, None, ctx, types, debug_mode); - // #[allow(irrefutable_let_patterns)] - // if let (TypeStringRepresentation::Type(mut l), TypeStringRepresentation::Type(r)) = - // (left, right) - // { - // l.push_str(&r); - // TypeStringRepresentation::Type(l) + // crate::utilities::notify!("Here?"); + // left.0.push_str(" | "); + // left.0.push_str(&right.0); + // Self(left.0) // } else { - // unreachable!() + crate::utilities::notify!("Printing {:?} base on {:?}", left_right, condition); + Self("TODO".to_owned()) // } } - crate::context::Logical::Implies { on, antecedent } => { + crate::types::logical::Logical::Implies { on, antecedent } => { if generics.is_some() { todo!("chaining") } - Self::from_property_constraint( - *on, - Some(GenericChainLink::Link { - parent_link: None, - value: &antecedent, - from: TypeId::UNIMPLEMENTED_ERROR_TYPE, - }), - ctx, - types, - debug_mode, - ) + let generics = Some(GenericChainLink::PartiallyAppliedGenericArgumentsLink { + parent_link: None, + value: &antecedent, + from: TypeId::UNIMPLEMENTED_ERROR_TYPE, + }); + Self::from_property_constraint(*on, generics, ctx, types, debug_mode) } + crate::types::logical::Logical::BasedOnKey { .. } => todo!(), } } } impl Display for TypeStringRepresentation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - TypeStringRepresentation::Type(ty) => f.write_str(ty), - } + f.write_str(&self.0) } } @@ -271,25 +289,30 @@ impl From for Diagnostic { } } -use crate::{ - context::AssignmentError, - features::{modules::CouldNotOpenFile, operations::MathematicalAndBitwise}, -}; - /// Reasons for errors, intermediate type for generating [Diagnostic]s /// e.g. cannot Call, cannot equate, duplicate key etc -#[allow(unused)] pub(crate) enum TypeCheckError<'a> { FunctionCallingError(FunctionCallingError), JSXCallingError(FunctionCallingError), - TemplateLiteralError(FunctionCallingError), + TemplateLiteralCallingError(FunctionCallingError), + GetterCallingError(FunctionCallingError), + SetterCallingError(FunctionCallingError), + /// From calling super + SuperCallError(FunctionCallingError), + /// When accessing PropertyDoesNotExist { on: TypeStringRepresentation, - property: PropertyRepresentation, - site: SpanWithSource, + property: PropertyKeyRepresentation, + position: SpanWithSource, possibles: Vec<&'a str>, }, + /// When accessing + CyclicTypeAlias { + position: SpanWithSource, + }, + #[allow(dead_code)] NotInLoopOrCouldNotFindLabel(NotInLoopOrCouldNotFindLabel), + #[allow(dead_code)] RestParameterAnnotationShouldBeArrayType(SpanWithSource), CouldNotFindVariable { variable: &'a str, @@ -298,10 +321,15 @@ pub(crate) enum TypeCheckError<'a> { }, CouldNotFindType(&'a str, Vec<&'a str>, SpanWithSource), TypeHasNoGenericParameters(String, SpanWithSource), + /// For all `=`, including from declarations AssignmentError(AssignmentError), + #[allow(dead_code)] InvalidComparison(TypeStringRepresentation, TypeStringRepresentation), + #[allow(dead_code)] InvalidAddition(TypeStringRepresentation, TypeStringRepresentation), + #[allow(dead_code)] InvalidUnaryOperation(crate::features::operations::PureUnary, TypeStringRepresentation), + SetPropertyError(SetPropertyError), ReturnedTypeDoesNotMatch { expected_return_type: TypeStringRepresentation, returned_type: TypeStringRepresentation, @@ -309,76 +337,57 @@ pub(crate) enum TypeCheckError<'a> { annotation_position: SpanWithSource, returned_position: SpanWithSource, }, - // TODO are these the same errors? - TypeIsNotIndexable(TypeStringRepresentation), - TypeIsNotIterable(TypeStringRepresentation), - // This could be a syntax error but that is difficult to type... + /// This could be a syntax error but that is difficult to type... NonTopLevelExport(SpanWithSource), FieldNotExported { file: &'a str, importing: &'a str, position: SpanWithSource, + possibles: Vec<&'a str>, }, - InvalidJSXAttribute { - attribute_name: String, - attribute_type: TypeStringRepresentation, - value_type: TypeStringRepresentation, - // TODO - attribute_type_site: (), - value_site: SpanWithSource, - }, - InvalidJSXInterpolatedValue { - interpolation_site: SpanWithSource, - expected: TypeStringRepresentation, - found: TypeStringRepresentation, - }, - /// for the `satisfies` keyword + /// For the `satisfies` keyword NotSatisfied { at: SpanWithSource, expected: TypeStringRepresentation, found: TypeStringRepresentation, }, - // catch type is not compatible with thrown type + /// Catch type is not compatible with thrown type CatchTypeDoesNotMatch { at: SpanWithSource, expected: TypeStringRepresentation, found: TypeStringRepresentation, }, + /// Something the checker does not supported Unsupported { thing: &'static str, at: SpanWithSource, }, - ReDeclaredVariable { - name: &'a str, - position: SpanWithSource, - }, InvalidDefaultParameter { at: SpanWithSource, expected: TypeStringRepresentation, found: TypeStringRepresentation, }, /// TODO temp, needs more info + #[allow(dead_code)] FunctionDoesNotMeetConstraint { function_constraint: TypeStringRepresentation, function_type: TypeStringRepresentation, position: SpanWithSource, }, - StatementsNotRun { - between: SpanWithSource, - }, CannotRedeclareVariable { name: String, position: SpanWithSource, }, - // TODO parameter position + /// This is for structure generics + #[allow(dead_code)] GenericArgumentDoesNotMeetRestriction { parameter_restriction: TypeStringRepresentation, argument: TypeStringRepresentation, position: SpanWithSource, }, - NotDefinedOperator(&'static str, SpanWithSource), - PropertyNotWriteable(SpanWithSource), + #[allow(dead_code)] NotTopLevelImport(SpanWithSource), + #[allow(dead_code)] DoubleDefaultExport(SpanWithSource), CannotOpenFile { file: CouldNotOpenFile, @@ -386,6 +395,8 @@ pub(crate) enum TypeCheckError<'a> { possibles: Vec<&'a str>, partial_import_path: &'a str, }, + /// WIP + #[allow(dead_code)] VariableNotDefinedInContext { variable: &'a str, expected_context: &'a str, @@ -393,18 +404,20 @@ pub(crate) enum TypeCheckError<'a> { position: SpanWithSource, }, TypeNeedsTypeArguments(&'a str, SpanWithSource), - CannotFindType(&'a str, Vec<&'a str>, SpanWithSource), TypeAlreadyDeclared { name: String, position: SpanWithSource, }, + #[allow(clippy::upper_case_acronyms)] TDZ(TDZ), + #[allow(dead_code)] InvalidMathematicalOrBitwiseOperation { operator: MathematicalAndBitwise, lhs: TypeStringRepresentation, rhs: TypeStringRepresentation, position: SpanWithSource, }, + #[allow(dead_code)] InvalidCast { position: SpanWithSource, from: TypeStringRepresentation, @@ -412,6 +425,7 @@ pub(crate) enum TypeCheckError<'a> { }, /// TODO Position = Function body position. Could it be better /// TODO maybe warning? + #[allow(dead_code)] UnreachableVariableClosedOver(String, SpanWithSource), IncompatibleOverloadParameter { parameter_position: SpanWithSource, @@ -425,52 +439,33 @@ pub(crate) enum TypeCheckError<'a> { base: TypeStringRepresentation, overload: TypeStringRepresentation, }, + FunctionWithoutBodyNotAllowedHere { + position: SpanWithSource, + }, + CannotDeleteProperty(CannotDeleteFromError), } #[allow(clippy::useless_format)] -pub fn get_possibles_message(possibles: Vec<&str>, reference: &str) -> String { - let mut binding = get_closest(possibles.into_iter(), reference).unwrap_or(vec![]); - let candidates: &mut [&str] = binding.as_mut_slice(); - candidates.sort_unstable(); - match candidates { +pub fn get_possibles_message(possibles: &[&str]) -> String { + match possibles { [] => format!(""), - [a] => format!("Did you mean {a}?"), - [a, b] => format!("Did you mean {a} or {b}?"), - [a, b, c] => format!("Did you mean {a}, {b} or {c}?"), - [a @ .., b] => format!("Did you mean {items} or {b}?", items = a.join(", ")), + [a] => format!("Did you mean '{a}'?"), + [a @ .., b] => { + let mut iter = a.iter(); + let first = format!("'{first}'", first = iter.next().unwrap()); + format!( + "Did you mean {items} or '{b}'?", + items = iter.fold(first, |acc, item| format!("{acc}, '{item}'")) + ) + } } } -pub fn get_possibles_message_for_imports(possibles: &[&str], reference: &str) -> String { - let candidates = possibles - .iter() - .filter(|file| !file.ends_with(".d.ts")) - .filter_map(|file| file.strip_suffix(".ts")) - .map(|file| { - if file.starts_with("./") || file.starts_with("../") { - file.to_string() - } else { - "./".to_string() + file - } - }) - .collect::>(); - - get_possibles_message(candidates.iter().map(AsRef::as_ref).collect::>(), reference) -} - -pub fn get_property_does_not_exist_message( - property: PropertyRepresentation, - on: &TypeStringRepresentation, - possibles: Vec<&str>, -) -> String { - match property { - PropertyRepresentation::Type(ty) => { - format!("No property of type {ty} on {on}. {}", get_possibles_message(possibles, &ty)) - } - PropertyRepresentation::StringKey(property) => format!( - "No property '{property}' on {on}. {}", - get_possibles_message(possibles, &property) - ), +fn map_error_empty(n: Vec, cb: impl FnOnce(Vec) -> T) -> T { + if n.is_empty() { + ::default() + } else { + cb(n) } } @@ -478,314 +473,367 @@ impl From> for Diagnostic { fn from(error: TypeCheckError<'_>) -> Self { let kind = super::DiagnosticKind::Error; match error { - TypeCheckError::CouldNotFindVariable { variable, possibles, position } => { - Diagnostic::Position { - reason: format!( - "Could not find variable '{variable}' in scope. {}", get_possibles_message(possibles, variable)), + TypeCheckError::CouldNotFindVariable { variable, possibles, position } => { + Diagnostic::PositionWithAdditionalLabels { + reason: format!("Could not find variable '{variable}' in scope"), + labels: map_error_empty(possibles, |possibles| vec![( + get_possibles_message(&possibles), position, - kind, - } - } - TypeCheckError::CouldNotFindType(reference, possibles, pos) => Diagnostic::Position { - reason: format!("Could not find type '{reference}'. {}", get_possibles_message(possibles, reference)), - position: pos, + )]), + position, kind, - }, - TypeCheckError::PropertyDoesNotExist { property, on, site, possibles } => { - Diagnostic::Position { - reason: get_property_does_not_exist_message(property, &on, possibles), - position: site, - kind, - } } - TypeCheckError::FunctionCallingError(error) => function_calling_error_diagnostic(error, kind, ""), - TypeCheckError::JSXCallingError(error) => function_calling_error_diagnostic(error, kind, " (in JSX)"), - TypeCheckError::TemplateLiteralError(error) => function_calling_error_diagnostic(error, kind, " (in template literal)"), - TypeCheckError::AssignmentError(error) => match error { - AssignmentError::DoesNotMeetConstraint { - variable_type, - variable_site, - value_type, - value_site, - } => Diagnostic::PositionWithAdditionalLabels { - reason: format!( - "Type {value_type} is not assignable to type {variable_type}", - ), - position: value_site, - labels: vec![( - format!("Variable declared with type {variable_type}"), - variable_site, - )], - kind, - }, - AssignmentError::PropertyConstraint { - property_constraint: property_type, - value_type, - assignment_position, - } => Diagnostic::Position { - reason: format!( - "Type {value_type} does not meet property constraint {property_type}" - ), - position: assignment_position, - kind, + } + TypeCheckError::CouldNotFindType(reference, possibles, position) => Diagnostic::PositionWithAdditionalLabels { + reason: format!("Could not find type '{reference}'"), + position, + labels: map_error_empty(possibles, |possibles| vec![( + get_possibles_message(&possibles), + position, + )]), + kind, + }, + TypeCheckError::PropertyDoesNotExist { property, on, position, possibles } => { + Diagnostic::PositionWithAdditionalLabels { + reason: match property { + PropertyKeyRepresentation::Type(ty) => format!("No property of type {ty} on {on}"), + PropertyKeyRepresentation::StringKey(property) => format!("No property '{property}' on {on}"), }, - AssignmentError::Constant(position) => Diagnostic::Position { - reason: "Cannot assign to constant".into(), + position, + labels: map_error_empty(possibles, |possibles| vec![( + get_possibles_message(&possibles), position, - kind, - }, - AssignmentError::VariableNotFound { variable, assignment_position } => { - Diagnostic::Position { - reason: format!("Cannot assign to unknown variable '{variable}'"), - position: assignment_position, - kind, - } - } - AssignmentError::TDZ(TDZ { variable_name, position }) => { - Diagnostic::Position { - reason: format!("Cannot assign to '{variable_name}' before declaration"), - position, - kind, - } - } - }, - TypeCheckError::InvalidJSXAttribute { - attribute_name, - attribute_type, - value_type, - attribute_type_site: _variable_site, - value_site, - } => Diagnostic::Position { - reason: format!( - "Type {attribute_name} is not assignable to {value_type} attribute of type {attribute_type}", - ), - position: value_site, + )]), kind, - }, - TypeCheckError::ReturnedTypeDoesNotMatch { - annotation_position, - returned_position, - expected_return_type, - returned_type, + } + } + TypeCheckError::FunctionCallingError(error) => function_calling_error_diagnostic(error, kind, ""), + TypeCheckError::JSXCallingError(error) => function_calling_error_diagnostic(error, kind, " (in JSX)"), + TypeCheckError::GetterCallingError(error) => function_calling_error_diagnostic(error, kind, " (in getter)"), + TypeCheckError::SetterCallingError(error) => function_calling_error_diagnostic(error, kind, " (in setter)"), + TypeCheckError::TemplateLiteralCallingError(error) => { + function_calling_error_diagnostic(error, kind, " (in template literal)") + }, + TypeCheckError::SuperCallError(error) => function_calling_error_diagnostic(error, kind, " (in super call)"), + TypeCheckError::AssignmentError(error) => match error { + AssignmentError::DoesNotMeetConstraint { + variable_type, + variable_position, + value_type, + value_position, } => Diagnostic::PositionWithAdditionalLabels { reason: format!( - "Cannot return {returned_type} because the function is expected to return {expected_return_type}", + "Type {value_type} is not assignable to type {variable_type}", ), + position: value_position, labels: vec![( - format!("Function annotated to return {expected_return_type} here"), - annotation_position, + format!("Variable declared with type {variable_type}"), + variable_position, )], - position: returned_position, kind, }, - TypeCheckError::InvalidDefaultParameter { - expected, - found, - at, - } => Diagnostic::Position { - reason: format!( - "Cannot use a default value of type {found} for parameter of type {expected}", - ), - position: at, - kind, - }, - TypeCheckError::CatchTypeDoesNotMatch { - expected, - found, - at, + AssignmentError::PropertyConstraint { + property_constraint: property_type, + value_type, + assignment_position, } => Diagnostic::Position { reason: format!( - "Cannot catch type {found} because the try block throws {expected}", + "Type {value_type} does not meet property constraint {property_type}" ), - position: at, + position: assignment_position, kind, }, - TypeCheckError::TypeHasNoGenericParameters(name, position) => { - Diagnostic::Position { - reason: format!("Type '{name}' has no generic parameters",), - position, - kind, - } - } - TypeCheckError::InvalidComparison(_, _) => todo!(), - TypeCheckError::InvalidAddition(_, _) => todo!(), - TypeCheckError::InvalidUnaryOperation(_, _) => todo!(), - TypeCheckError::TypeIsNotIndexable(_) => todo!(), - TypeCheckError::TypeIsNotIterable(_) => todo!(), - TypeCheckError::NonTopLevelExport(position) => Diagnostic::Position { - reason: "Cannot export at not top level".to_owned(), + AssignmentError::Constant(position) => Diagnostic::Position { + reason: "Cannot assign to constant".into(), position, kind, }, - TypeCheckError::FieldNotExported { file, importing, position } => { - Diagnostic::Position { - reason: format!("{importing} not exported from {file}"), - position, - kind, - } - } - TypeCheckError::InvalidJSXInterpolatedValue { - interpolation_site: _, - expected: _, - found: _, - } => todo!(), - TypeCheckError::RestParameterAnnotationShouldBeArrayType(pos) => { + AssignmentError::VariableNotFound { variable, assignment_position } => { Diagnostic::Position { - reason: "Rest parameter annotation should be array type".to_owned(), - position: pos, + reason: format!("Cannot assign to unknown variable '{variable}'"), + position: assignment_position, kind, } } - TypeCheckError::Unsupported { thing, at } => Diagnostic::Position { - reason: format!("Unsupported: {thing}"), - position: at, - kind, - }, - TypeCheckError::ReDeclaredVariable { name, position } => { + AssignmentError::TDZ(TDZ { variable_name, position }) => { Diagnostic::Position { - reason: format!("Cannot declare variable {name}"), + reason: format!("Cannot assign to '{variable_name}' before declaration"), position, kind, } } - TypeCheckError::FunctionDoesNotMeetConstraint { - function_constraint, - function_type, + }, + TypeCheckError::ReturnedTypeDoesNotMatch { + annotation_position, + returned_position, + expected_return_type, + returned_type, + } => Diagnostic::PositionWithAdditionalLabels { + reason: format!( + "Cannot return {returned_type} because the function is expected to return {expected_return_type}" + ), + labels: vec![( + format!("Function annotated to return {expected_return_type} here"), + annotation_position, + )], + position: returned_position, + kind, + }, + TypeCheckError::InvalidDefaultParameter { + expected, + found, + at, + } => Diagnostic::Position { + reason: format!( "Cannot use a default value of type {found} for parameter of type {expected}"), + position: at, + kind, + }, + TypeCheckError::CatchTypeDoesNotMatch { + expected, + found, + at, + } => Diagnostic::Position { + reason: format!( "Cannot catch type {found} because the try block throws {expected}" ), + position: at, + kind, + }, + TypeCheckError::TypeHasNoGenericParameters(name, position) => { + Diagnostic::Position { + reason: format!("Type '{name}' has no generic parameters"), position, - } => Diagnostic::Position { - reason: format!( - "{function_constraint} constraint on function does not match synthesised form {function_type}", - ), + kind, + } + } + TypeCheckError::InvalidComparison(_, _) => todo!(), + TypeCheckError::InvalidAddition(_, _) => todo!(), + TypeCheckError::InvalidUnaryOperation(_, _) => todo!(), + TypeCheckError::NonTopLevelExport(position) => Diagnostic::Position { + reason: "Cannot export at not top level".to_owned(), + position, + kind, + }, + TypeCheckError::FieldNotExported { file, importing, position, possibles } => { + Diagnostic::PositionWithAdditionalLabels { + reason: format!("{importing} not exported from {file}"), position, kind, - }, - TypeCheckError::StatementsNotRun { between } => Diagnostic::Position { - reason: "Statements are never run".to_owned(), - position: between, + labels: map_error_empty(possibles, |possibles| vec![( + get_possibles_message(&possibles), + position, + )]), + } + } + TypeCheckError::RestParameterAnnotationShouldBeArrayType(pos) => { + Diagnostic::Position { + reason: "Rest parameter annotation should be array type".to_owned(), + position: pos, kind, - }, - TypeCheckError::NotSatisfied { at, expected, found } => Diagnostic::Position { - reason: format!("Expected {expected}, found {found}"), - position: at, + } + } + TypeCheckError::Unsupported { thing, at } => Diagnostic::Position { + reason: format!("Unsupported: {thing}"), + position: at, + kind, + }, + TypeCheckError::FunctionDoesNotMeetConstraint { + function_constraint, + function_type, + position, + } => Diagnostic::Position { + reason: format!( + "{function_constraint} constraint on function does not match synthesised form {function_type}", + ), + position, + kind, + }, + TypeCheckError::NotSatisfied { at, expected, found } => Diagnostic::Position { + reason: format!("Expected {expected}, found {found}"), + position: at, + kind, + }, + TypeCheckError::CannotRedeclareVariable { name, position } => { + Diagnostic::Position { + reason: format!("Cannot redeclare variable '{name}'"), + position, kind, - }, - TypeCheckError::CannotRedeclareVariable { name, position } => { - Diagnostic::Position { - reason: format!("Cannot redeclare variable '{name}'"), - position, - kind, - } } - TypeCheckError::NotDefinedOperator(op, position) => Diagnostic::Position { - reason: format!("Operator not typed {op}"), + } + TypeCheckError::GenericArgumentDoesNotMeetRestriction { + argument, + parameter_restriction, + position, + } => Diagnostic::Position { + reason: format!( + "Generic argument {argument} does not match {parameter_restriction}" + ), + position, + kind, + }, + TypeCheckError::NotTopLevelImport(position) => Diagnostic::Position { + reason: "Import must be in the top of the scope".to_owned(), + position, + kind, + }, + TypeCheckError::DoubleDefaultExport(_) => todo!(), + TypeCheckError::CannotOpenFile { file, import_position, possibles, partial_import_path } => if let Some(import_position) = import_position { + Diagnostic::PositionWithAdditionalLabels { + reason: format!("Cannot find {partial_import_path}"), + position: import_position, + kind, + labels: map_error_empty(possibles, |possibles| vec![( + get_possibles_message(&possibles), + import_position, + )]) + } + } else { + Diagnostic::Global { reason: format!("Cannot find file {}", file.0.display()), kind } + }, + TypeCheckError::VariableNotDefinedInContext { + variable, + expected_context, + current_context, + position, + } => Diagnostic::Position { + reason: format!("'{variable}' is only available on the {expected_context}, currently in {current_context}"), + position, + kind, + }, + TypeCheckError::TypeNeedsTypeArguments(ty, position) => Diagnostic::Position { + reason: format!("Type {ty} requires type arguments"), + position, + kind, + }, + TypeCheckError::TypeAlreadyDeclared { name, position } => Diagnostic::Position { + reason: format!("Type named '{name}' already declared"), + position, + kind, + }, + TypeCheckError::TDZ(TDZ { position, variable_name }) => Diagnostic::Position { + reason: format!("Variable '{variable_name}' used before declaration"), + position, + kind, + }, + TypeCheckError::InvalidMathematicalOrBitwiseOperation { operator, lhs, rhs, position } => Diagnostic::Position { + // TODO temp + reason: format!("Cannot {lhs} {operator:?} {rhs}"), + position, + kind, + }, + TypeCheckError::NotInLoopOrCouldNotFindLabel(_) => todo!(), + TypeCheckError::InvalidCast { position, from, to } => { + Diagnostic::Position { + reason: format!("Cannot cast {from} to {to}"), position, kind, - }, - TypeCheckError::PropertyNotWriteable(position) => Diagnostic::Position { - reason: "Property not writeable".into(), + } + }, + TypeCheckError::UnreachableVariableClosedOver(name, function_position) => { + Diagnostic::Position { + reason: format!("Function contains unreachable closed over variable '{name}'"), + position: function_position, + kind, + } + }, + TypeCheckError::IncompatibleOverloadParameter { parameter_position, overloaded_parameter_position, parameter, overloaded_parameter } => Diagnostic::PositionWithAdditionalLabels { + reason: format!( + "Overload with parameter of {overloaded_parameter} does not meet base parameter {parameter}" + ), + labels: vec![( + format!("Function has base type {parameter} here"), + parameter_position, + )], + position: overloaded_parameter_position, + kind, + }, + TypeCheckError::IncompatibleOverloadReturnType { base_position, overload_position, base, overload } => Diagnostic::PositionWithAdditionalLabels { + reason: format!( + "Cannot return {overload} in overload because base function is expected to return {base}" + ), + labels: vec![( + format!("Function annotated to return {base} here"), + base_position, + )], + position: overload_position, + kind, + }, + TypeCheckError::FunctionWithoutBodyNotAllowedHere { position } => { + Diagnostic::Position { + reason: "Function without body not allowed here".to_owned(), position, kind, - }, - TypeCheckError::GenericArgumentDoesNotMeetRestriction { - argument, - parameter_restriction, + } + } + TypeCheckError::CyclicTypeAlias { position } => { + Diagnostic::Position { + reason: "Circular type reference".to_owned(), position, - } => Diagnostic::Position { - reason: format!( - "Generic argument {argument} does not match {parameter_restriction}" - ), + kind, + } + } + TypeCheckError::CannotDeleteProperty(CannotDeleteFromError::Constraint { constraint, position }) => { + Diagnostic::Position { + reason: format!("Cannot delete from object constrained to {constraint}"), position, kind, - }, - TypeCheckError::NotTopLevelImport(position) => Diagnostic::Position { - reason: "Import must be in the top of the scope".to_owned(), + } + } + TypeCheckError::CannotDeleteProperty(CannotDeleteFromError::NonConfigurable { + position, + }) => { + Diagnostic::Position { + reason: "Cannot delete from non-configurable property".to_owned(), position, kind, - }, - TypeCheckError::DoubleDefaultExport(_) => todo!(), - TypeCheckError::CannotOpenFile { file, import_position, possibles, partial_import_path } => if let Some(import_position) = import_position { - Diagnostic::Position { - reason: format!("Cannot find {partial_import_path}. {}", get_possibles_message_for_imports(possibles.as_slice(), partial_import_path)), - position: import_position, - kind, - } - } else { - Diagnostic::Global { reason: format!("Cannot find file {}", file.0.display()), kind } - }, - TypeCheckError::VariableNotDefinedInContext { - variable, - expected_context, - current_context, + } + } + TypeCheckError::SetPropertyError(error) => match error { + SetPropertyError::NotWriteable { + property, position, } => Diagnostic::Position { - reason: format!("'{variable}' is only available on the {expected_context}, currently in {current_context}"), + reason: match property { + PropertyKeyRepresentation::Type(ty) => format!("Cannot write to property of type {ty}"), + PropertyKeyRepresentation::StringKey(property) => format!("Cannot write to property '{property}'") + }, position, kind, }, - TypeCheckError::TypeNeedsTypeArguments(ty, position) => Diagnostic::Position { - reason: format!("Type {ty} requires type arguments"), + SetPropertyError::DoesNotMeetConstraint { + property_constraint, + value_type, + reason: _, position, - kind, - }, - TypeCheckError::CannotFindType(ty, possibles, position) => Diagnostic::Position { - reason: format!("Cannot find type {ty}. {}",get_possibles_message(possibles,ty)), + } => Diagnostic::Position { + reason: format!( + "Type {value_type} does not meet property constraint {property_constraint}" + ), position, kind, }, - TypeCheckError::TypeAlreadyDeclared { name, position } => Diagnostic::Position { - reason: format!("Type named '{name}' already declared"), + SetPropertyError::AssigningToGetter { + property, position, - kind, - }, - TypeCheckError::TDZ(TDZ { position, variable_name }) => Diagnostic::Position { - reason: format!("Variable '{variable_name}' used before declaration"), + } => Diagnostic::Position { + reason: match property { + PropertyKeyRepresentation::Type(ty) => format!("Cannot write to property of type {ty} as it is a getter"), + PropertyKeyRepresentation::StringKey(property) => format!("Cannot write to property '{property}' as it is a getter") + }, position, kind, }, - TypeCheckError::InvalidMathematicalOrBitwiseOperation { operator, lhs, rhs, position } => Diagnostic::Position { - // TODO temp - reason: format!("Cannot {lhs} {operator:?} {rhs}"), + SetPropertyError::AssigningToNonExistent { + property, + position, + } => Diagnostic::Position { + reason: match property { + PropertyKeyRepresentation::Type(ty) => format!("Cannot write to non-existent property of type {ty}"), + PropertyKeyRepresentation::StringKey(property) => format!("Cannot write to non-existent property '{property}'") + }, position, - kind, - }, - TypeCheckError::NotInLoopOrCouldNotFindLabel(_) => todo!(), - TypeCheckError::InvalidCast { position, from, to } => { - Diagnostic::Position { - reason: format!("Cannot cast {from} to {to}"), - position, - kind, - } - } - TypeCheckError::UnreachableVariableClosedOver(name, function_position) => { - Diagnostic::Position { - reason: format!("Function contains unreachable closed over variable '{name}'"), - position: function_position, - kind, - } - } - TypeCheckError::IncompatibleOverloadParameter { parameter_position, overloaded_parameter_position, parameter, overloaded_parameter } => Diagnostic::PositionWithAdditionalLabels { - reason: format!( - "Overload with parameter of {overloaded_parameter} does not meet base parameter {parameter}", - ), - labels: vec![( - format!("Function has base type {parameter} here"), - parameter_position, - )], - position: overloaded_parameter_position, - kind, - }, - TypeCheckError::IncompatibleOverloadReturnType { base_position, overload_position, base, overload } => Diagnostic::PositionWithAdditionalLabels { - reason: format!( - "Cannot return {overload} in overload because base function is expected to return {base}", - ), - labels: vec![( - format!("Function annotated to return {base} here"), - base_position, - )], - position: overload_position, kind, } } + } } } @@ -819,6 +867,12 @@ pub enum TypeCheckWarning { position: SpanWithSource, }, InvalidOrUnimplementedDefinitionFileItem(SpanWithSource), + /// TODO WIP + ConditionalExceptionInvoked { + value: TypeStringRepresentation, + /// Should be set + call_site: SpanWithSource, + }, Unreachable(SpanWithSource), } @@ -883,10 +937,22 @@ impl From for Diagnostic { TypeCheckWarning::Unreachable(position) => { Diagnostic::Position { reason: "Unreachable statement".to_owned(), position, kind } } + TypeCheckWarning::ConditionalExceptionInvoked { value, call_site } => { + Diagnostic::Position { + reason: format!("Conditional '{value}' was thrown in function"), + position: call_site, + kind, + } + } } } } +/// Only for internal things +/// +/// WIP +pub struct InfoDiagnostic(pub String, pub SpanWithSource); + #[derive(Debug)] pub struct CannotFindTypeError<'a>(pub &'a str); @@ -1011,12 +1077,12 @@ fn function_calling_error_diagnostic( kind, }, FunctionCallingError::NoLogicForIdentifier(name, position) => Diagnostic::Position { - reason: format!("no logic for constant function {name}{context}"), + reason: format!("No logic for constant function {name}{context}"), kind, position, }, FunctionCallingError::NeedsToBeCalledWithNewKeyword(position) => Diagnostic::Position { - reason: "class constructor must be called with new".to_owned(), + reason: "Class constructor must be called with new".to_owned(), kind, position, }, @@ -1034,7 +1100,7 @@ fn function_calling_error_diagnostic( assignment_position, call_site, } => Diagnostic::PositionWithAdditionalLabels { - reason: format!("Invalid assignment to parameter{context}"), + reason: format!("Invalid assignment through parameter{context}"), position: call_site, kind, labels: vec![( @@ -1042,11 +1108,6 @@ fn function_calling_error_diagnostic( assignment_position, )], }, - FunctionCallingError::UnconditionalThrow { value, call_site } => Diagnostic::Position { - reason: format!("Conditional '{value}' was thrown in function{context}"), - position: call_site, - kind, - }, FunctionCallingError::MismatchedThis { call_site, expected, found } => { Diagnostic::Position { reason: format!("The 'this' context of the function is expected to be {expected}, found {found}{context}"), @@ -1061,5 +1122,25 @@ fn function_calling_error_diagnostic( kind, } } + FunctionCallingError::DeleteConstraint { constraint, delete_position, call_site: _ } => { + Diagnostic::Position { + reason: format!("Cannot delete from object constrained to {constraint}"), + position: delete_position, + kind, + } + } + FunctionCallingError::NotConfiguarable { + property, + call_site, + } => { + Diagnostic::Position { + reason: match property { + PropertyKeyRepresentation::Type(ty) => format!("Property of type '{ty}' not configurable"), + PropertyKeyRepresentation::StringKey(property) => format!("Property '{property}' not configurable"), + }, + position: call_site, + kind, + } + } } } diff --git a/checker/src/events/application.rs b/checker/src/events/application.rs index 055b8cb3..b21150ef 100644 --- a/checker/src/events/application.rs +++ b/checker/src/events/application.rs @@ -3,34 +3,31 @@ use source_map::SpanWithSource; use super::{CallingTiming, Event, FinalEvent, PrototypeArgument, RootReference}; use crate::{ - context::{ - get_value_of_variable, invocation::InvocationContext, CallCheckingBehavior, - SetPropertyError, - }, + context::{get_value_of_variable, invocation::InvocationContext, CallCheckingBehavior}, diagnostics::{TypeStringRepresentation, TDZ}, events::ApplicationResult, features::{ - functions::ThisValue, iteration::{self, IterationKind}, - objects::SpecialObjects, + objects::SpecialObject, + CannotDeleteFromError, }, subtyping::type_is_subtype, types::{ - calling::{self, FunctionCallingError}, - functions::SynthesisedArgument, + calling::{self, CallingDiagnostics, FunctionCallingError, SynthesisedArgument, ThisValue}, generics::substitution::SubstitutionArguments, is_type_truthy_falsy, printing::print_type, - properties::{get_property, set_property, PropertyKey, PropertyValue}, + properties::{assignment::SetPropertyError, get_property, set_property, PropertyValue}, substitute, PartiallyAppliedGenerics, TypeId, TypeStore, }, Decidable, Environment, Type, }; -#[derive(Default)] -pub struct ErrorsAndInfo { - pub errors: Vec, - pub warnings: Vec, +pub(crate) struct ApplicationInput { + pub(crate) this_value: ThisValue, + pub(crate) call_site: SpanWithSource, + /// From above, not log + pub(crate) max_inline: u16, } /// `type_arguments` are mutable to add new ones during lookup etc @@ -40,210 +37,194 @@ pub struct ErrorsAndInfo { #[allow(clippy::too_many_arguments)] pub(crate) fn apply_events( events: &[Event], - this_value: ThisValue, + input: &ApplicationInput, mut type_arguments: &mut SubstitutionArguments, top_environment: &mut Environment, target: &mut InvocationContext, types: &mut TypeStore, - mut errors: &mut ErrorsAndInfo, - call_site: SpanWithSource, + mut diagnostics: &mut CallingDiagnostics, ) -> Option { let mut trailing = None::<(TypeId, ApplicationResult)>; + // TODO this could be done better + let unknown_mode = target.in_unknown(); + + // crate::utilities::notify!("Running {:?}", events); + let mut idx = 0; while idx < events.len() { let event = &events[idx]; - // crate::utilities::notify!("Running single {:?}", event); + + // crate::utilities::notify!("Running single {:?}", event); + match &event { Event::ReadsReference { reference, reflects_dependency, position } => { + // if B::CHECK_PARAMETERS { + // TODO check free variables from inference + // } + if let Some(id) = reflects_dependency { - let value = match reference { - RootReference::Variable(id) => { - // Some(&*type_arguments) - let value = - get_value_of_variable(top_environment, *id, Some(type_arguments)); - if let Some(ty) = value { - ty - } else { - errors.errors.push( - crate::types::calling::FunctionCallingError::TDZ { - error: TDZ { - variable_name: top_environment - .get_variable_name(*id) - .to_owned(), - position: *position, - }, - call_site, - }, + if unknown_mode { + if let RootReference::Variable(variable) = reference { + // TODO this is okay for loops, not sure about other cases of this function + crate::utilities::notify!( + "Setting loop variable here {:?}", + reflects_dependency + ); + target + .get_latest_info(top_environment) + .variable_current_value + .insert(*variable, *id); + } + + // Think this is correct + type_arguments.set_during_application(*id, *id); + } else { + let value = match reference { + RootReference::Variable(id) => { + // Some(&*type_arguments) + let value = get_value_of_variable( + top_environment, + *id, + Some(type_arguments), ); - TypeId::ERROR_TYPE + if let Some(ty) = value { + ty + } else { + diagnostics.errors.push( + crate::types::calling::FunctionCallingError::TDZ { + error: TDZ { + variable_name: top_environment + .get_variable_name(*id) + .to_owned(), + position: *position, + }, + call_site: input.call_site, + }, + ); + TypeId::ERROR_TYPE + } } - } - RootReference::This => this_value.get(top_environment, types, *position), - }; - type_arguments.set_during_application(*id, value); - // crate::utilities::notify!( - // "Setting free variable argument {:?} here {:?}", - // *id, - // type_arguments.arguments - // ); + RootReference::This => { + input.this_value.get(top_environment, types, *position) + } + }; + type_arguments.set_during_application(*id, value); + } } } Event::SetsVariable(variable, value, position) => { - let new_value = substitute(*value, type_arguments, top_environment, types); + if unknown_mode { + // TODO Top environment? + // TODO maybe needs to be returned back up, rather than set here? + top_environment.possibly_mutated_variables.insert(*variable); + } else { + let new_value = substitute(*value, type_arguments, top_environment, types); - // TODO temp assigns to many contexts, which is bad. - // Closures should have an indicator of what they close over #56 - let info = target.get_latest_info(top_environment); - for id in &type_arguments.closures { - info.closure_current_values - .insert((*id, RootReference::Variable(*variable)), new_value); - } + // There is probably a way to speed this up. For example `Environment` could have a range? + // This also doesn't work around conditionals + let in_above_environment = + top_environment.variables.values().any(|var| var.get_id() == *variable); + + // TODO temp assigns to many contexts, which is bad. + // Closures should have an indicator of what they close over #56 + let info = target.get_latest_info(top_environment); + for id in &type_arguments.closures { + info.closure_current_values + .insert((*id, RootReference::Variable(*variable)), new_value); + } + + if !in_above_environment { + info.events.push(Event::SetsVariable(*variable, new_value, *position)); + } - info.events.push(Event::SetsVariable(*variable, new_value, *position)); - info.variable_current_value.insert(*variable, new_value); + info.variable_current_value.insert(*variable, new_value); + } } - Event::Getter { on, under, reflects_dependency, publicity, position, bind_this } => { + Event::Getter { on, under, reflects_dependency, publicity, position, mode } => { // let was = on; let on = substitute(*on, type_arguments, top_environment, types); // crate::utilities::notify!("was {:?} now {:?}", was, on); - let under = match under { - PropertyKey::Type(under) => { - let ty = substitute(*under, type_arguments, top_environment, types); - PropertyKey::from_type(ty, types) - } - under @ PropertyKey::String(_) => under.clone(), - }; + let under = under.substitute(type_arguments, top_environment, types); let Some((_, value)) = get_property( on, *publicity, &under, - None, top_environment, - target, + (target, diagnostics), types, *position, - *bind_this, + *mode, ) else { // TODO getters can fail here panic!( - "could not get property {under:?} at {position:?} on {}, (inference or some checking failed)", - print_type(on, types, top_environment, true) - ); + "Could not get property {under:?} at {position:?} on {}, (inference or some checking failed)", + print_type(on, types, top_environment, true) + ); }; if let Some(id) = reflects_dependency { type_arguments.set_during_application(*id, value); + + // { + // let value = print_type(value, types, top_environment, true); + // crate::utilities::notify!("Setting {:?} to {:?}", id, value); + // } } } - Event::Setter { on, under, new, initialization, publicity, position } => { - // let was = on; + Event::Setter { on, under, new, publicity, position } => { let on = substitute(*on, type_arguments, top_environment, types); - // crate::utilities::notify!("was {:?} now {:?}", was, on); - - let under = match under { - PropertyKey::Type(under) => { - let ty = substitute(*under, type_arguments, top_environment, types); - PropertyKey::from_type(ty, types) - } - under @ PropertyKey::String(_) => under.clone(), - }; - - let new = match new { - PropertyValue::Value(new) => PropertyValue::Value(substitute( - *new, - type_arguments, - top_environment, - types, - )), - // For declare property - PropertyValue::Getter(_) => todo!(), - PropertyValue::Setter(_) => todo!(), - // TODO this might be a different thing at some point - PropertyValue::Deleted => { - top_environment.delete_property(on, &under, *position); - return None; - } - PropertyValue::ConditionallyExists { .. } => { - todo!() - } - }; - - let _gc = top_environment.as_general_context(); - // crate::utilities::notify!( - // "[Event::Setter] {}[{}] = {}", - // crate::types::printing::print_type(on, types, &gc, true), - // crate::types::printing::print_type(under, types, &gc, true), - // if let Property::Value(new) = new { - // crate::types::printing::print_type(new, types, &gc, true) - // } else { - // format!("{:#?}", new) - // } - // ); + if unknown_mode { + // TODO constraint something? + // TODO maybe needs to be returned back up, rather than set here? + top_environment.possibly_mutated_objects.insert(on, TypeId::ANY_TYPE); + } else { + let under = under.substitute(type_arguments, top_environment, types); + let new = substitute(*new, type_arguments, top_environment, types); - if *initialization { - // TODO temp fix for closures - let on = if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on, - arguments: _, - }) = types.get_type_by_id(on) { - *on - } else { - on - }; + crate::utilities::notify!( + "[Event::Setter] ({})[{:?}] = {:?}", + crate::types::printing::print_type(on, types, top_environment, true), + under, + new + ); + } - target.get_latest_info(top_environment).register_property( - on, - *publicity, - under.clone(), - new, - true, - *position, - ); - } else { let result = set_property( on, - *publicity, - &under, - new.clone(), + (*publicity, &under, new), + *position, top_environment, - target, + (target, diagnostics), types, - *position, ); - if let Err(err) = result { - if let SetPropertyError::DoesNotMeetConstraint { - property_constraint, - reason: _, - } = err - { - let value_type = if let PropertyValue::Value(id) = new { - TypeStringRepresentation::from_type_id( - id, - top_environment, - types, - false, - ) + match result { + Ok(()) => {} + Err(err) => { + if let SetPropertyError::DoesNotMeetConstraint { + property_constraint, + value_type, + reason: _, + position: _, + } = err + { + diagnostics.errors.push( + crate::types::calling::FunctionCallingError::SetPropertyConstraint { + property_type: property_constraint, + value_type, + assignment_position: *position, + call_site: input.call_site, + }, + ); } else { - todo!() - }; - - errors.errors.push( - crate::types::calling::FunctionCallingError::SetPropertyConstraint { - property_type: property_constraint, - value_type, - assignment_position: *position, - call_site, - }, - ); - } else { - unreachable!() + unreachable!("set property check failed") + } } } } @@ -255,9 +236,20 @@ pub(crate) fn apply_events( timing, called_with_new, possibly_thrown: _, - position: _, + call_site, } => { - let on = substitute(*on, type_arguments, top_environment, types); + let on = match on { + calling::Callable::Fixed(fixed, this_value) => { + crate::utilities::notify!("TODO specialise this?"); + calling::Callable::Fixed(*fixed, *this_value) + } + calling::Callable::Type(on) => calling::Callable::Type(substitute( + *on, + type_arguments, + top_environment, + types, + )), + }; // crate::utilities::notify!("was {:?} now {:?}", was, on); @@ -272,58 +264,47 @@ pub(crate) fn apply_events( match timing { CallingTiming::Synchronous => { - let result = crate::types::calling::call_type( - on, - with, - &crate::types::calling::CallingInput { - called_with_new: *called_with_new, - call_site_type_arguments: None, - // TODO: - call_site: source_map::Nullable::NULL, - }, - top_environment, - target, - types, - ); - match result { - Ok(mut result) => { - errors.warnings.append(&mut result.warnings); - - if let Some(ApplicationResult::Throw { .. }) = result.result { - // TODO conditional here - return result.result; - } - - if let Some(reflects_dependency) = reflects_dependency { - let as_type = calling::application_result_to_return_type( - result.result, - top_environment, - types, - ); - type_arguments - .set_during_application(*reflects_dependency, as_type); - } + let input = crate::types::calling::CallingInput { + called_with_new: *called_with_new, + // TODO: + max_inline: input.max_inline, + call_site: *call_site, + }; + let result = + on.call(with, input, top_environment, (target, diagnostics), types); + if let Ok(result) = result { + if let Some(ApplicationResult::Throw { .. }) = result.result { + // TODO conditional here + return result.result; + } - // if result.thrown_type != TypeId::NEVER_TYPE { - // // TODO - // // return FinalEvent::Throw { - // // thrown: result.thrown_type, - // // position: source_map::Nullable::NULL, - // // } - // // .into(); - // } + if let Some(reflects_dependency) = reflects_dependency { + let as_type = calling::application_result_to_return_type( + result.result, + top_environment, + types, + ); + type_arguments + .set_during_application(*reflects_dependency, as_type); } - Err(mut other_errors) => { - crate::utilities::notify!( - "inference and or checking failed at function" + + // if result.thrown_type != TypeId::NEVER_TYPE { + // // TODO + // // return FinalEvent::Throw { + // // thrown: result.thrown_type, + // // position: source_map::Nullable::NULL, + // // } + // // .into(); + // } + } else { + crate::utilities::notify!( + "inference and or checking failed at function" + ); + if let Some(reflects_dependency) = reflects_dependency { + type_arguments.set_during_application( + *reflects_dependency, + TypeId::ERROR_TYPE, ); - errors.errors.append(&mut other_errors.errors); - if let Some(reflects_dependency) = reflects_dependency { - type_arguments.set_during_application( - *reflects_dependency, - TypeId::ERROR_TYPE, - ); - } } } } @@ -359,6 +340,7 @@ pub(crate) fn apply_events( let (truthy_events, otherwise_events) = (*truthy_events as usize, *otherwise_events as usize); let offset = idx + 1; + match fixed_result { Decidable::Known(result) => { let (start, end) = if result { @@ -376,13 +358,12 @@ pub(crate) fn apply_events( target.new_unconditional_target(|target: &mut InvocationContext| { apply_events( events_to_run, - this_value, + input, type_arguments, top_environment, target, types, - errors, - *position, + diagnostics, ) }); @@ -403,25 +384,24 @@ pub(crate) fn apply_events( .evaluate_conditionally( top_environment, types, - condition, + (condition, *position), (truthy_events_slice, otherwise_events_slice), - (type_arguments, errors), - |top_environment, types, target, input, data| { - let (type_arguments, errors) = data; + (type_arguments, diagnostics), + |top_environment, types, target, events, data| { + let (type_arguments, diagnostics) = data; apply_events( + events, input, - this_value, type_arguments, top_environment, target, types, - errors, - *position, + diagnostics, ) }, ); - (type_arguments, errors) = data; + (type_arguments, diagnostics) = data; match (truthy_result, otherwise_result) { (Some(truthy_result), Some(otherwise_result)) => { @@ -470,46 +450,48 @@ pub(crate) fn apply_events( // TODO let is_under_dyn = true; - let new_object_id = - match prototype { - PrototypeArgument::Yeah(prototype) => { - let prototype = - substitute(*prototype, type_arguments, top_environment, types); - target.get_latest_info(top_environment).new_object( - Some(prototype), - types, - *position, - is_under_dyn, - ) - } - PrototypeArgument::None => target - .get_latest_info(top_environment) - .new_object(None, types, *position, is_under_dyn), - PrototypeArgument::Function(id) => types.register_type( - crate::Type::SpecialObject(SpecialObjects::Function(*id, this_value)), - ), - }; + let new_object_ty = match prototype { + PrototypeArgument::Yeah(prototype) => { + let prototype = + substitute(*prototype, type_arguments, top_environment, types); + target.get_latest_info(top_environment).new_object( + Some(prototype), + types, + *position, + is_under_dyn, + ) + } + PrototypeArgument::None => target.get_latest_info(top_environment).new_object( + None, + types, + *position, + is_under_dyn, + ), + PrototypeArgument::Function(id) => types.register_type( + crate::Type::SpecialObject(SpecialObject::Function(*id, input.this_value)), + ), + }; // TODO conditionally if any properties are structurally generic - // let new_object_id_with_curried_arguments = - // curry_arguments(type_arguments, types, new_object_id); + // let new_object_ty_with_curried_arguments = + // curry_arguments(type_arguments, types, new_object_ty); // crate::utilities::notify!( // "Setting {:?} to {:?}", // referenced_in_scope_as, - // new_object_id_with_curried_arguments + // new_object_ty_with_curried_arguments // ); if let Some(object_constraint) = top_environment.get_object_constraint(*referenced_in_scope_as) { top_environment.add_object_constraints( - std::iter::once((new_object_id, object_constraint)), + std::iter::once((new_object_ty, object_constraint)), types, ); } - type_arguments.set_during_application(*referenced_in_scope_as, new_object_id); + type_arguments.set_during_application(*referenced_in_scope_as, new_object_ty); } Event::Iterate { kind, iterate_over, initial } => { let closure_id = types.new_closure_id(); @@ -554,25 +536,27 @@ pub(crate) fn apply_events( let offset = idx + 1; - let () = target.new_loop_iteration(|target| { + let result = target.new_loop_iteration(|target| { let events = &events[offset..(offset + *iterate_over as usize)]; // crate::utilities::notify!("Running in iteration {:?}", events); iteration::run_iteration_block( kind, events, + input, iteration::RunBehavior::Run, type_arguments, top_environment, target, - errors, + diagnostics, types, - // TODO iterator.position - call_site, - ); + ) }); - // if result.is_some() { - // return result; - // } + + crate::utilities::notify!("Inner loop returned {:?}", result); + + if result.is_some() { + return result; + } idx += *iterate_over as usize + 1; } Event::ExceptionTrap { investigate, handle, finally, trapped_type_id } => { @@ -583,13 +567,12 @@ pub(crate) fn apply_events( let inner_result = apply_events( &events[offset..(offset + investigate)], - this_value, + input, type_arguments, top_environment, target, types, - errors, - call_site, + diagnostics, ); if let Some(result) = inner_result { @@ -631,21 +614,23 @@ pub(crate) fn apply_events( if let crate::subtyping::SubTypeResult::IsNotSubType(_reason) = result { - errors.errors.push(FunctionCallingError::CannotCatch { - catch: TypeStringRepresentation::from_type_id( - constraint, - top_environment, - types, - false, - ), - thrown: TypeStringRepresentation::from_type_id( - thrown, - top_environment, - types, - false, - ), - thrown_position: position, - }); + diagnostics.errors.push( + FunctionCallingError::CannotCatch { + catch: TypeStringRepresentation::from_type_id( + constraint, + top_environment, + types, + false, + ), + thrown: TypeStringRepresentation::from_type_id( + thrown, + top_environment, + types, + false, + ), + thrown_position: position, + }, + ); } } } @@ -655,13 +640,12 @@ pub(crate) fn apply_events( let result = apply_events( handler, - this_value, + input, type_arguments, top_environment, target, types, - errors, - call_site, + diagnostics, ); if result.is_some() { @@ -677,220 +661,178 @@ pub(crate) fn apply_events( } Event::RegisterVariable { .. } => {} Event::EndOfControlFlow { .. } => { - crate::utilities::notify!("Shouldn't be here"); + crate::utilities::notify!("Shouldn't be applying `Event::EndOfControlFlow`"); } - Event::FinalEvent(final_event) => { - let application_result = match final_event { - FinalEvent::Break { carry, position } => ApplicationResult::Break { - // TODO is this correct? - carry: carry.saturating_sub(target.get_iteration_depth()), - position: *position, - }, - FinalEvent::Continue { carry, position } => ApplicationResult::Continue { - // TODO is this correct? - carry: carry.saturating_sub(target.get_iteration_depth()), - position: *position, - }, - FinalEvent::Throw { thrown, position } => { - let substituted_thrown = - substitute(*thrown, type_arguments, top_environment, types); - if target.in_unconditional() { - let value = TypeStringRepresentation::from_type_id( - substituted_thrown, - // TODO is this okay? - top_environment, - types, - false, - ); - errors.errors.push(FunctionCallingError::UnconditionalThrow { - value, - call_site, - }); + Event::Miscellaneous(misc) => match misc { + super::MiscellaneousEvents::Has { on, publicity, under, into } => { + let on = substitute(*on, type_arguments, top_environment, types); + let under = under.substitute(type_arguments, top_environment, types); + + let has = crate::features::in_operator( + (*publicity, &under), + on, + top_environment, + types, + ); + + type_arguments.arguments.insert(*into, has); + } + super::MiscellaneousEvents::Delete { on, publicity, under, into, position } => { + let on = substitute(*on, type_arguments, top_environment, types); + let under = under.substitute(type_arguments, top_environment, types); + + match crate::features::delete_operator( + (*publicity, under), + on, + *position, + top_environment, + types, + ) { + Ok(result) => { + if let Some(into) = into { + type_arguments.arguments.insert(*into, result); + } } - ApplicationResult::Throw { thrown: substituted_thrown, position: *position } - } - FinalEvent::Return { returned, position } => { - let substituted_returned = - substitute(*returned, type_arguments, top_environment, types); - ApplicationResult::Return { - returned: substituted_returned, - position: *position, + Err(err) => { + diagnostics.errors.push(match err { + CannotDeleteFromError::Constraint { constraint, position } => { + FunctionCallingError::DeleteConstraint { + constraint, + delete_position: position, + call_site: input.call_site, + } + } + CannotDeleteFromError::NonConfigurable { position: _ } => { + todo!() + } + }); + + if let Some(into) = into { + type_arguments.arguments.insert(*into, TypeId::ERROR_TYPE); + } } } - }; - return Some(if let Some((on, trailing)) = trailing { - ApplicationResult::Or { + } + super::MiscellaneousEvents::RegisterProperty { + on, + publicity, + under, + value, + position, + } => { + let on = substitute(*on, type_arguments, top_environment, types); + + // TODO temp fix for closures + let on = if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on, - truthy_result: Box::new(trailing), - otherwise_result: Box::new(application_result), - } - } else { - application_result - }); - } - } - idx += 1; - } - None -} + arguments: _, + }) = types.get_type_by_id(on) + { + *on + } else { + on + }; -/// For loops and recursion need to evaluate events. This version does that but assumes many times -/// -/// - TODO more might need covering -/// - TODO `_this_value` is not being used -#[allow(clippy::used_underscore_binding)] -pub(crate) fn apply_events_unknown( - events: &[Event], - _this_value: ThisValue, - type_arguments: &mut SubstitutionArguments, - environment: &mut Environment, - target: &mut InvocationContext, - types: &mut TypeStore, -) { - let mut idx = 0; - while idx < events.len() { - match &events[idx] { - Event::ReadsReference { reflects_dependency, reference, .. } => { - if let (Some(reflects_dependency), RootReference::Variable(variable)) = - (reflects_dependency, reference) - { - // TODO this is okay for loops, not sure about other cases of this function - crate::utilities::notify!( - "Setting loop variable here {:?}", - reflects_dependency + let under = under.substitute(type_arguments, top_environment, types); + let value = match value { + PropertyValue::Value(value) => PropertyValue::Value(substitute( + *value, + type_arguments, + top_environment, + types, + )), + value => { + crate::utilities::notify!("TODO value {:?}", value); + value.clone() + } + }; + + target.get_latest_info(top_environment).register_property( + on, + *publicity, + under.clone(), + value.clone(), + *position, ); - target - .get_latest_info(environment) - .variable_current_value - .insert(*variable, *reflects_dependency); } - } - Event::Getter { reflects_dependency, .. } => { - // Evaluates getters - if let Some(_reflects_dependency) = reflects_dependency { - crate::utilities::notify!("Run getters ???"); - // let on = substitute(on, type_arguments, environment, types); - // let under = match under { - // under @ PropertyKey::String(_) => under, - // PropertyKey::Type(under) => { - // PropertyKey::Type(substitute(under, type_arguments, environment, types)) - // } - // }; - // get_property( - // on, - // publicity, - // &under, - // None, - // top_environment, - // target, - // types, - // position, - // bind_this, - // ) + super::MiscellaneousEvents::CreateConstructor { + referenced_in_scope_as, + function, + } => { + let new_function_ty = types.register_type(Type::SpecialObject( + SpecialObject::Function(*function, Default::default()), + )); + // Apply curring + let new_function_ty = if type_arguments.closures.is_empty() { + new_function_ty + } else { + types.register_type(Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: new_function_ty, + arguments: crate::types::generics::generic_type_arguments::GenericArguments::Closure(type_arguments.closures.clone()), + })) + }; + type_arguments.set_during_application(*referenced_in_scope_as, new_function_ty); + } + }, + Event::FinalEvent(final_event) => { + // I think this is okay + if !unknown_mode { + let application_result = match final_event { + FinalEvent::Break { carry, position } => ApplicationResult::Break { + // TODO is this correct? + carry: carry.saturating_sub(target.get_iteration_depth()), + position: *position, + }, + FinalEvent::Continue { carry, position } => ApplicationResult::Continue { + // TODO is this correct? + carry: carry.saturating_sub(target.get_iteration_depth()), + position: *position, + }, + FinalEvent::Throw { thrown, position } => { + let substituted_thrown = + substitute(*thrown, type_arguments, top_environment, types); + if target.in_unconditional() { + let value = TypeStringRepresentation::from_type_id( + substituted_thrown, + // TODO is this okay? + top_environment, + types, + false, + ); + let warning = + crate::diagnostics::TypeCheckWarning::ConditionalExceptionInvoked { + value, + call_site: input.call_site, + }; + diagnostics.warnings.push(warning); + } + ApplicationResult::Throw { + thrown: substituted_thrown, + position: *position, + } + } + FinalEvent::Return { returned, position } => { + let substituted_returned = + substitute(*returned, type_arguments, top_environment, types); + ApplicationResult::Return { + returned: substituted_returned, + position: *position, + } + } + }; + return Some(if let Some((on, trailing)) = trailing { + ApplicationResult::Or { + on, + truthy_result: Box::new(trailing), + otherwise_result: Box::new(application_result), + } + } else { + application_result + }); } } - Event::SetsVariable(variable, _value, _) => { - // crate::utilities::notify!("Here"); - // let new_value = - // crate::types::get_constraint(*value, types).map_or(*value, |value| { - // types.register_type(Type::RootPolyType(crate::types::PolyNature::Open( - // value, - // ))) - // }); - - crate::utilities::notify!("Here"); - environment.possibly_mutated_variables.insert(*variable); - - // // Don't like this but I think it is okay - // environment.info.variable_current_value.insert(*variable, new_value); - } - Event::Setter { on, under, new, initialization: _, publicity, position } => { - let on = substitute(*on, type_arguments, environment, types); - let new_value = match new { - PropertyValue::Value(new) => { - let new = crate::types::get_constraint(*new, types).map_or(*new, |value| { - types.register_type(Type::RootPolyType(crate::types::PolyNature::Open( - value, - ))) - }); - PropertyValue::Value(new) - } - // TODO - PropertyValue::Getter(_) - | PropertyValue::Setter(_) - | PropertyValue::Deleted => new.clone(), - PropertyValue::ConditionallyExists { .. } => todo!(), - }; - let under = match under { - under @ PropertyKey::String(_) => under.clone(), - PropertyKey::Type(under) => { - PropertyKey::Type(substitute(*under, type_arguments, environment, types)) - } - }; - - environment - .info - .register_property(on, *publicity, under, new_value, false, *position); - } - Event::CallsType { .. } => { - crate::utilities::notify!("TODO ?"); - } - Event::Conditionally { truthy_events, otherwise_events, .. } => { - let (truthy_events, otherwise_events) = - (*truthy_events as usize, *otherwise_events as usize); - - let offset = idx + 1; - let truthy_events_slice = &events[offset..(offset + truthy_events)]; - let otherwise_events_slice = - &events[(offset + truthy_events)..(offset + truthy_events + otherwise_events)]; - - // TODO think this is correct...? - apply_events_unknown( - truthy_events_slice, - _this_value, - type_arguments, - environment, - target, - types, - ); - apply_events_unknown( - otherwise_events_slice, - _this_value, - type_arguments, - environment, - target, - types, - ); - idx += truthy_events + otherwise_events; - } - Event::CreateObject { .. } => { - crate::utilities::notify!("Creating object"); - } - Event::FinalEvent(..) => { - crate::utilities::notify!("final events"); - } - Event::Iterate { .. } => { - // This should be fine? - // for event in iterate_over.to_vec() { - // apply_event_unknown( - // event, - // this_value, - // type_arguments, - // environment, - // target, - // types, - // ) - // } - crate::utilities::notify!("Iterate trap anytime"); - } - Event::ExceptionTrap { .. } => { - crate::utilities::notify!("Exception trap anytime"); - } - Event::EndOfControlFlow { .. } => { - crate::utilities::notify!("Shouldn't be here"); - } - Event::RegisterVariable { .. } => {} } idx += 1; } + None } diff --git a/checker/src/events/mod.rs b/checker/src/events/mod.rs index b5a1bd00..a376db6f 100644 --- a/checker/src/events/mod.rs +++ b/checker/src/events/mod.rs @@ -3,14 +3,14 @@ //! Events is the general name for the IR (intermediate representation) of impure operations. Effect = Events of a function pub(crate) mod application; +pub mod printing; use crate::{ context::get_on_ctx, features::{functions::ClosedOverVariables, iteration::IterationKind}, types::{ - calling::CalledWithNew, - functions::SynthesisedArgument, - properties::{PropertyKey, PropertyValue, Publicity}, + calling::{Callable, CalledWithNew, SynthesisedArgument}, + properties::{AccessMode, PropertyKey, PropertyValue, Publicity}, TypeId, }, FunctionId, GeneralContext, SpanWithSource, VariableId, @@ -62,30 +62,25 @@ pub enum Event { reflects_dependency: Option, publicity: Publicity, position: SpanWithSource, - bind_this: bool, + mode: AccessMode, }, - /// All changes to the value of a property + /// All assignments to the value of a property. TODO explain others for defining getters etc Setter { on: TypeId, under: PropertyKey<'static>, - // Can be a getter through define property - new: PropertyValue, - /// THIS DOES NOT CALL SETTERS, JUST SETS VALUE! - /// TODO this is [define] property - /// see - initialization: bool, + new: TypeId, publicity: Publicity, position: SpanWithSource, }, /// This includes closed over variables, anything dependent CallsType { - on: TypeId, + on: Callable, with: Box<[SynthesisedArgument]>, reflects_dependency: Option, timing: CallingTiming, called_with_new: CalledWithNew, possibly_thrown: Option, - position: SpanWithSource, + call_site: SpanWithSource, }, /// Run events conditionally Conditionally { @@ -148,7 +143,7 @@ pub enum Event { /// `None` for `let x;` initial_value: Option, }, - + Miscellaneous(MiscellaneousEvents), /// TODO was trying to avoid EndOfControlFlow(u32), } @@ -166,7 +161,33 @@ impl From for Event { } } -/// Nothing runs after this event +/// Some of these are [`crate::features::objects::Proxy`] traps +#[derive(Debug, Clone, binary_serialize_derive::BinarySerializable)] +pub enum MiscellaneousEvents { + /// Also for + Has { on: TypeId, publicity: Publicity, under: PropertyKey<'static>, into: TypeId }, + /// Very similar to [`MiscellaneousEvents::Has`] but deletes the properties + /// Also for + Delete { + on: TypeId, + publicity: Publicity, + under: PropertyKey<'static>, + into: Option, + position: SpanWithSource, + }, + /// :/ + RegisterProperty { + on: TypeId, + publicity: Publicity, + under: PropertyKey<'static>, + value: PropertyValue, + position: SpanWithSource, + }, + /// Creates a new function or class + CreateConstructor { referenced_in_scope_as: TypeId, function: FunctionId }, +} + +/// A break in application #[derive(Debug, Clone, Copy, binary_serialize_derive::BinarySerializable)] pub enum FinalEvent { Return { @@ -188,6 +209,9 @@ pub enum FinalEvent { position: SpanWithSource, }, // Yield { + // value: TypeId, + // returns: TypeId, + // position: SpanWithSource, // } } diff --git a/checker/src/events/printing.rs b/checker/src/events/printing.rs new file mode 100644 index 00000000..9e128329 --- /dev/null +++ b/checker/src/events/printing.rs @@ -0,0 +1,200 @@ +use std::collections::HashSet; + +use super::{Event, FinalEvent}; +use crate::{ + context::InformationChain, + events::CallingTiming, + types::{ + calling::Callable, + printing::{print_property_key_into_buf, print_type_into_buf}, + properties::PropertyKey, + GenericChain, TypeStore, + }, +}; + +pub fn debug_effects( + buf: &mut String, + events: &[Event], + types: &TypeStore, + info: &C, + depth: u8, + debug: bool, +) { + use std::fmt::Write; + + let args = GenericChain::None; + + let mut idx = 0; + + while idx < events.len() { + for _ in 0..depth { + buf.push('\t'); + } + let event = &events[idx]; + match event { + Event::ReadsReference { reference, reflects_dependency, position: _ } => { + write!(buf, "read '{reference:?}' into {reflects_dependency:?}").unwrap(); + } + Event::SetsVariable(variable, value, _) => { + write!(buf, "{variable:?}' = ").unwrap(); + print_type_into_buf(*value, buf, &mut HashSet::new(), args, types, info, debug); + } + Event::Getter { + on, + under, + reflects_dependency, + publicity: _, + position: _, + mode: _, + } => { + buf.push_str("read "); + print_type_into_buf(*on, buf, &mut HashSet::new(), args, types, info, debug); + if let PropertyKey::String(_) = under { + buf.push('.'); + } + print_property_key_into_buf( + under, + buf, + &mut HashSet::new(), + args, + types, + info, + debug, + ); + write!(buf, " into {reflects_dependency:?}").unwrap(); + } + Event::Setter { on, under, new, publicity: _, position: _ } => { + print_type_into_buf(*on, buf, &mut HashSet::new(), args, types, info, debug); + buf.push('['); + print_property_key_into_buf( + under, + buf, + &mut HashSet::default(), + args, + types, + info, + debug, + ); + buf.push_str("] = "); + print_type_into_buf(*new, buf, &mut HashSet::new(), args, types, info, debug); + } + Event::CallsType { + on, + with: _, + reflects_dependency, + timing, + called_with_new: _, + call_site: _, + possibly_thrown: _, + } => { + buf.push_str("call "); + match on { + Callable::Fixed(function, _) => { + write!(buf, " {function:?} ").unwrap(); + } + Callable::Type(on) => { + let mut cycles = HashSet::new(); + print_type_into_buf(*on, buf, &mut cycles, args, types, info, debug); + } + } + write!(buf, " into {reflects_dependency:?} ",).unwrap(); + buf.push_str(match timing { + CallingTiming::Synchronous => "now", + CallingTiming::QueueTask => "queue", + CallingTiming::AtSomePointManyTimes => "sometime", + }); + // TODO args + } + Event::Conditionally { condition, truthy_events, otherwise_events, position: _ } => { + let truthy_events = *truthy_events as usize; + let otherwise_events = *otherwise_events as usize; + + buf.push_str("if "); + print_type_into_buf(*condition, buf, &mut HashSet::new(), args, types, info, debug); + buf.push_str(" then\n"); + + let events_if_true = &events[(idx + 1)..=(idx + truthy_events)]; + if truthy_events != 0 { + debug_effects(buf, events_if_true, types, info, depth + 1, debug); + } + + if otherwise_events != 0 { + let start = idx + truthy_events + 1; + let otherwise = &events[(start)..(start + otherwise_events)]; + for _ in 0..depth { + buf.push('\t'); + } + buf.push_str("else\n"); + debug_effects(buf, otherwise, types, info, depth + 1, debug); + } + idx += truthy_events + otherwise_events + 1; + continue; + } + Event::CreateObject { prototype: _, referenced_in_scope_as, position: _ } => { + write!(buf, "create object as {referenced_in_scope_as:?}").unwrap(); + } + Event::Iterate { iterate_over, initial: _, kind: _ } => { + buf.push_str("iterate\n"); + let inner_events = &events[(idx + 1)..(idx + 1 + *iterate_over as usize)]; + debug_effects(buf, inner_events, types, info, depth + 1, debug); + idx += *iterate_over as usize + 1; + continue; + } + Event::FinalEvent(FinalEvent::Throw { thrown, .. }) => { + buf.push_str("throw "); + print_type_into_buf(*thrown, buf, &mut HashSet::new(), args, types, info, debug); + } + Event::FinalEvent(FinalEvent::Break { .. }) => { + buf.push_str("break"); + } + Event::FinalEvent(FinalEvent::Continue { .. }) => { + buf.push_str("continue"); + } + Event::FinalEvent(FinalEvent::Return { returned, position: _ }) => { + buf.push_str("return "); + print_type_into_buf(*returned, buf, &mut HashSet::new(), args, types, info, debug); + } + Event::ExceptionTrap { .. } => todo!(), + Event::RegisterVariable { name, .. } => { + write!(buf, "register variable {name}").unwrap(); + } + Event::EndOfControlFlow(_) => { + buf.push_str("end"); + } + Event::Miscellaneous(misc) => match misc { + super::MiscellaneousEvents::Has { .. } => { + buf.push_str("Has"); + } + super::MiscellaneousEvents::Delete { .. } => { + buf.push_str("Delete"); + } + super::MiscellaneousEvents::RegisterProperty { + on, + publicity: _, + under, + value, + position: _, + } => { + print_type_into_buf(*on, buf, &mut HashSet::new(), args, types, info, debug); + buf.push('['); + print_property_key_into_buf( + under, + buf, + &mut HashSet::default(), + args, + types, + info, + debug, + ); + buf.push_str("] = "); + write!(buf, "{value:?}").unwrap(); + } + super::MiscellaneousEvents::CreateConstructor { .. } => { + buf.push_str("create constructor"); + } + }, + } + buf.push('\n'); + idx += 1; + } +} diff --git a/checker/src/features/assignments.rs b/checker/src/features/assignments.rs index d9742156..486aab57 100644 --- a/checker/src/features/assignments.rs +++ b/checker/src/features/assignments.rs @@ -18,13 +18,18 @@ pub enum Assignable { #[derive(Clone)] pub enum Reference { Variable(String, SpanWithSource), - Property { on: TypeId, with: PropertyKey<'static>, publicity: Publicity, span: SpanWithSource }, + Property { + on: TypeId, + with: PropertyKey<'static>, + publicity: Publicity, + position: SpanWithSource, + }, } pub enum AssignableObjectDestructuringField { /// `{ x: y }` Mapped { - on: PropertyKey<'static>, + key: PropertyKey<'static>, name: Assignable, default_value: Option>>, position: SpanWithSource, @@ -65,7 +70,7 @@ impl Reference { #[must_use] pub fn get_position(&self) -> SpanWithSource { match self { - Reference::Variable(_, span) | Reference::Property { span, .. } => *span, + Reference::Variable(_, position) | Reference::Property { position, .. } => *position, } } diff --git a/checker/src/features/conditional.rs b/checker/src/features/conditional.rs index 306de014..c4eb8abc 100644 --- a/checker/src/features/conditional.rs +++ b/checker/src/features/conditional.rs @@ -5,6 +5,7 @@ use crate::{ }; use source_map::Span; +/// For top level checking pub fn new_conditional_context( environment: &mut Environment, (condition, position): (TypeId, Span), @@ -44,12 +45,17 @@ where let Context { context_type: Syntax { free_variables, closed_over_references, .. }, info, + possibly_mutated_objects, + possibly_mutated_variables, .. } = truthy_environment; environment.context_type.free_variables.extend(free_variables); environment.context_type.closed_over_references.extend(closed_over_references); + environment.possibly_mutated_objects.extend(possibly_mutated_objects); + environment.possibly_mutated_variables.extend(possibly_mutated_variables); + (result, info) }; @@ -64,12 +70,17 @@ where let Context { context_type: Syntax { free_variables, closed_over_references, .. }, info, + possibly_mutated_objects, + possibly_mutated_variables, .. } = falsy_environment; environment.context_type.free_variables.extend(free_variables); environment.context_type.closed_over_references.extend(closed_over_references); + environment.possibly_mutated_objects.extend(possibly_mutated_objects); + environment.possibly_mutated_variables.extend(possibly_mutated_variables); + (result, Some(info)) } else { (R::default(), None) @@ -81,9 +92,9 @@ where let position = position.with_source(environment.get_source()); match environment.context_type.parent { - crate::GeneralContext::Syntax(syn) => { + crate::GeneralContext::Syntax(syn_parent) => { merge_info( - syn, + syn_parent, &mut environment.info, condition, truthy_info, @@ -92,9 +103,9 @@ where position, ); } - crate::GeneralContext::Root(root) => { + crate::GeneralContext::Root(root_parent) => { merge_info( - root, + root_parent, &mut environment.info, condition, truthy_info, diff --git a/checker/src/features/constant_functions.rs b/checker/src/features/constant_functions.rs index 54829285..4ba842ca 100644 --- a/checker/src/features/constant_functions.rs +++ b/checker/src/features/constant_functions.rs @@ -2,17 +2,20 @@ use iterator_endiate::EndiateIteratorExt; use source_map::SpanWithSource; use crate::{ - context::{get_on_ctx, information::InformationChain}, - features::objects::Proxy, + context::{get_on_ctx, information::InformationChain, invocation::CheckThings}, + events::printing::debug_effects, + features::objects::{ObjectBuilder, Proxy}, types::{ - functions::SynthesisedArgument, - printing::{debug_effects, print_type}, + calling::{Callable, FunctionCallingError, SynthesisedArgument, ThisValue}, + logical::{Logical, LogicalOrValid}, + printing::print_type, + properties::{AccessMode, Descriptor, PropertyKey, Publicity}, FunctionEffect, PartiallyAppliedGenerics, Type, TypeRestrictions, TypeStore, }, - Constant, Environment, TypeId, + Constant, Environment, PropertyValue, TypeId, }; -use super::{functions::ThisValue, objects::SpecialObjects}; +use super::objects::SpecialObject; // TODO ... pub(crate) enum ConstantOutput { @@ -21,6 +24,7 @@ pub(crate) enum ConstantOutput { } pub enum ConstantFunctionError { + FunctionCallingError(FunctionCallingError), NoLogicForIdentifier(String), /// This will get picked up by the main calling logic BadCall, @@ -35,15 +39,17 @@ pub enum ConstantFunctionError { pub(crate) type CallSiteTypeArguments = TypeRestrictions; /// Computes a constant value +#[allow(clippy::too_many_arguments)] pub(crate) fn call_constant_function( id: &str, this_argument: ThisValue, call_site_type_args: Option<&CallSiteTypeArguments>, arguments: &[SynthesisedArgument], types: &mut TypeStore, - // TODO mut for satisfies which needs checking + // TODO `mut` for satisfies which needs checking. Also needed for freeze etc environment: &mut Environment, - _call_site: SpanWithSource, + call_site: SpanWithSource, + diagnostics: &mut crate::types::calling::CallingDiagnostics, ) -> Result { // crate::utilities::notify!("Calling constant function {} with {:?}", name, arguments); // TODO as parameter @@ -94,10 +100,10 @@ pub(crate) fn call_constant_function( let ty = types.new_constant_type(Constant::Number(try_into)); Ok(ConstantOutput::Value(ty)) } - Err(_) => Ok(ConstantOutput::Value(TypeId::NAN_TYPE)), + Err(_) => Ok(ConstantOutput::Value(TypeId::NAN)), } } - // String stuff + // String stuff. TODO could this be replaced by intrinsics "toUpperCase" | "toLowerCase" | "string_length" => { if let Some(Type::Constant(Constant::String(s))) = this_argument.get_passed().map(|t| types.get_type_by_id(t)) @@ -201,10 +207,7 @@ pub(crate) fn call_constant_function( }; let get_type_by_id = types.get_type_by_id(ty); - if let Type::SpecialObject( - SpecialObjects::Function(func, _) - | SpecialObjects::ClassConstructor { constructor: func, .. }, - ) + let message = if let Type::SpecialObject(SpecialObject::Function(func, _)) | Type::FunctionReference(func) = get_type_by_id { let function_type = @@ -212,26 +215,27 @@ pub(crate) fn call_constant_function( let effects = &function_type.effect; if id.ends_with("rust") { - Ok(ConstantOutput::Diagnostic(format!("{effects:#?}"))) + format!("{effects:#?}") } else { match effects { FunctionEffect::SideEffects { events, .. } => { - let mut buf = String::new(); + let mut buf = String::from("Effects:\n"); debug_effects(&mut buf, events, types, environment, 0, true); - Ok(ConstantOutput::Diagnostic(buf)) + buf } FunctionEffect::Constant { identifier, may_throw: _ } => { - Ok(ConstantOutput::Diagnostic(format!("Constant: {identifier}"))) + format!("Constant: {identifier}") } FunctionEffect::InputOutput { identifier, may_throw: _ } => { - Ok(ConstantOutput::Diagnostic(format!("InputOutput: {identifier}"))) + format!("InputOutput: {identifier}") } - FunctionEffect::Unknown => Ok(ConstantOutput::Diagnostic("unknown".into())), + FunctionEffect::Unknown => "unknown".into(), } } } else { - Ok(ConstantOutput::Diagnostic(format!("{get_type_by_id:?} is not a function"))) - } + format!("{get_type_by_id:?} is not a function") + }; + Ok(ConstantOutput::Diagnostic(message)) } // For functions "bind" => { @@ -239,7 +243,7 @@ pub(crate) fn call_constant_function( let first_argument = arguments.first(); if let ( Some( - Type::SpecialObject(SpecialObjects::Function(func, _)) + Type::SpecialObject(SpecialObject::Function(func, _)) | Type::FunctionReference(func), ), Some(this_ty), @@ -247,7 +251,7 @@ pub(crate) fn call_constant_function( { let type_id = this_ty.non_spread_type().map_err(|()| ConstantFunctionError::BadCall)?; - let value = types.register_type(Type::SpecialObject(SpecialObjects::Function( + let value = types.register_type(Type::SpecialObject(SpecialObject::Function( *func, ThisValue::Passed(type_id), ))); @@ -270,24 +274,277 @@ pub(crate) fn call_constant_function( } "getPrototypeOf" => { if let Some(first) = arguments.first() { - crate::utilities::notify!("TODO walk up chain"); - let prototype = environment - .info - .prototypes - .get(&first.non_spread_type().unwrap()) - .copied() - .unwrap_or(TypeId::NULL_TYPE); + let on = first.non_spread_type().unwrap(); + let prototype = environment.get_prototype(on); Ok(ConstantOutput::Value(prototype)) } else { Err(ConstantFunctionError::BadCall) } } + "freeze" => { + if let Some(on) = + (arguments.len() == 1).then(|| arguments[0].non_spread_type().ok()).flatten() + { + environment.info.frozen.insert(on); + Ok(ConstantOutput::Value(on)) + } else { + Err(ConstantFunctionError::BadCall) + } + } + "isFrozen" => { + if let Some(on) = + (arguments.len() == 1).then(|| arguments[0].non_spread_type().ok()).flatten() + { + let is_frozen = + environment.get_chain_of_info().any(|info| info.frozen.contains(&on)); + Ok(ConstantOutput::Value(if is_frozen { TypeId::TRUE } else { TypeId::FALSE })) + } else { + Err(ConstantFunctionError::BadCall) + } + } + "defineProperty" => { + // TODO check configurable + if let [on, property, descriptor] = arguments { + let on = on.non_spread_type().map_err(|()| ConstantFunctionError::BadCall)?; + let property = + property.non_spread_type().map_err(|()| ConstantFunctionError::BadCall)?; + let descriptor = + descriptor.non_spread_type().map_err(|()| ConstantFunctionError::BadCall)?; + + let under = PropertyKey::from_type(property, types); + // TODO + let mut behavior = CheckThings { debug_types: true }; + let publicity = Publicity::Public; + let mode = AccessMode::Regular; + + // This doesn't allow generic keys, but it starts to get very weird if that is something to be allowed + macro_rules! get_property { + ($key:expr) => { + crate::types::properties::get_property( + descriptor, + publicity, + &PropertyKey::String(std::borrow::Cow::Borrowed($key)), + environment, + (&mut behavior, diagnostics), + types, + call_site, + mode, + ) + .map(|(_, value)| value) + }; + } + + // TODO what about two specified, what about order + let value = if let Some(value) = get_property!("value") { + // TODO check no get or set otherwise causes type error + PropertyValue::Value(value) + } else { + let getter = get_property!("get"); + let setter = get_property!("set"); + if let (Some(getter), Some(setter)) = (getter, setter) { + // This a weird one, the only time is created wholey rather + // from accumulation in resolver + PropertyValue::GetterAndSetter { + getter: Callable::from_type(getter, types), + setter: Callable::from_type(setter, types), + } + } else if let Some(getter) = getter { + PropertyValue::Getter(Callable::from_type(getter, types)) + } else if let Some(setter) = setter { + PropertyValue::Setter(Callable::from_type(setter, types)) + } else { + return Err(ConstantFunctionError::BadCall); + } + }; + + // For configurablity + let existing = crate::types::properties::get_property_unbound( + (on, None), + (Publicity::Public, &under, None), + false, + environment, + types, + ); + + if let Ok(LogicalOrValid::Logical(Logical::Pure(PropertyValue::Configured { + on, + descriptor: Descriptor { writable: _, enumerable: _, configurable }, + }))) = existing + { + // WIP doesn't cover all valid cases + if configurable != TypeId::TRUE { + let valid = + if let (PropertyValue::Value(existing), PropertyValue::Value(new)) = + (*on, &value) + { + // yah weird spec + existing == *new + } else { + false + }; + if !valid { + return Err(ConstantFunctionError::FunctionCallingError( + FunctionCallingError::NotConfiguarable { + property: crate::diagnostics::PropertyKeyRepresentation::new( + &under, + environment, + types, + ), + // Should be set by parent + call_site, + }, + )); + } + } + } + + // FALSE is spec here! + let writable = get_property!("writable").unwrap_or(TypeId::FALSE); + let enumerable = get_property!("enumerable").unwrap_or(TypeId::FALSE); + let configurable = get_property!("configurable").unwrap_or(TypeId::FALSE); + + crate::utilities::notify!( + "values are = {:?}", + (writable, enumerable, configurable) + ); + + // Can skip if (read defaults to `true`) + let value = if let (TypeId::TRUE, TypeId::TRUE, TypeId::TRUE) = + (writable, enumerable, configurable) + { + value + } else { + PropertyValue::Configured { + on: Box::new(value), + descriptor: Descriptor { writable, enumerable, configurable }, + } + }; + + environment.info.register_property(on, Publicity::Public, under, value, call_site); + + Ok(ConstantOutput::Value(on)) + } else { + Err(ConstantFunctionError::BadCall) + } + } + "getOwnPropertyDescriptor" => { + if let [on, property] = arguments { + let on = on.non_spread_type().map_err(|()| ConstantFunctionError::BadCall)?; + let property = + property.non_spread_type().map_err(|()| ConstantFunctionError::BadCall)?; + + let value = crate::types::properties::resolver( + (on, None), + (Publicity::Public, &PropertyKey::from_type(property, types), None), + environment, + types, + ); + crate::utilities::notify!("value is = {:?}", value); + + match value { + Some((value, _, _)) => { + let mut descriptor = crate::types::properties::Descriptor::default(); + let mut object = + ObjectBuilder::new(None, types, call_site, &mut environment.info); + + match value.inner_simple() { + PropertyValue::Value(ty) => { + object.append( + Publicity::Public, + "value".into(), + PropertyValue::Value(*ty), + call_site, + &mut environment.info, + ); + } + PropertyValue::GetterAndSetter { getter, setter } => { + object.append( + Publicity::Public, + "get".into(), + PropertyValue::Value(getter.into_type(types)), + call_site, + &mut environment.info, + ); + object.append( + Publicity::Public, + "set".into(), + PropertyValue::Value(setter.into_type(types)), + call_site, + &mut environment.info, + ); + } + PropertyValue::Getter(getter) => { + object.append( + Publicity::Public, + "get".into(), + PropertyValue::Value(getter.into_type(types)), + call_site, + &mut environment.info, + ); + } + PropertyValue::Setter(setter) => { + object.append( + Publicity::Public, + "set".into(), + PropertyValue::Value(setter.into_type(types)), + call_site, + &mut environment.info, + ); + } + PropertyValue::Deleted => { + return Ok(ConstantOutput::Value(TypeId::UNDEFINED_TYPE)); + } + _ => unreachable!(), + } + + match value { + PropertyValue::ConditionallyExists { .. } => { + crate::utilities::notify!("TODO conditional. Union with undefined"); + return Err(ConstantFunctionError::BadCall); + } + PropertyValue::Configured { on: _, descriptor: d } => { + descriptor = d; + } + _ => {} + } + + object.append( + Publicity::Public, + "writable".into(), + PropertyValue::Value(descriptor.writable), + call_site, + &mut environment.info, + ); + object.append( + Publicity::Public, + "enumerable".into(), + PropertyValue::Value(descriptor.enumerable), + call_site, + &mut environment.info, + ); + object.append( + Publicity::Public, + "configurable".into(), + PropertyValue::Value(descriptor.configurable), + call_site, + &mut environment.info, + ); + + Ok(ConstantOutput::Value(object.build_object())) + } + // IMO this should be `TypeId::NULL` :( + None => Ok(ConstantOutput::Value(TypeId::UNDEFINED_TYPE)), + } + } else { + Err(ConstantFunctionError::BadCall) + } + } "proxy:constructor" => { crate::utilities::notify!("Here creating proxy"); if let [object, trap] = arguments { // TODO checking for both, what about spreading let value = types.register_type(Type::SpecialObject( - crate::features::objects::SpecialObjects::Proxy(Proxy { + crate::features::objects::SpecialObject::Proxy(Proxy { handler: trap.non_spread_type().expect("single type"), over: object.non_spread_type().expect("single type"), }), @@ -297,13 +554,21 @@ pub(crate) fn call_constant_function( Err(ConstantFunctionError::BadCall) } } + // "RegExp:constructor" => { + // crate::utilities::notify!("TODO check argument"); + // if let Some(arg) = arguments.first() { + // Ok(ConstantOutput::Value(features::regular_expressions::new_regexp(features::regular_expressions::TypeIdOrString::TypeId(arg), types, environment))) + // } else { + // Err(ConstantFunctionError::BadCall) + // } + // } // TODO - "json:parse" => { - crate::utilities::notify!("TODO json:parse"); + "JSON:parse" => { + crate::utilities::notify!("TODO JSON:parse"); Err(ConstantFunctionError::BadCall) } - "json:stringify" => { - crate::utilities::notify!("TODO json:stringify"); + "JSON:stringify" => { + crate::utilities::notify!("TODO JSON:stringify"); Err(ConstantFunctionError::BadCall) } // "satisfies" => { diff --git a/checker/src/features/exceptions.rs b/checker/src/features/exceptions.rs index 79b9a7a6..48573e96 100644 --- a/checker/src/features/exceptions.rs +++ b/checker/src/features/exceptions.rs @@ -116,6 +116,8 @@ pub fn new_try_context<'a, T: crate::ReadFromFS, A: crate::ASTImplementation>( constant: true, space: catch_variable_type, initial_value: Some(throw_variable_type), + // :>) + allow_reregistration: true, }; A::declare_and_assign_to_fields( diff --git a/checker/src/features/functions.rs b/checker/src/features/functions.rs index 405f6a2f..98549eea 100644 --- a/checker/src/features/functions.rs +++ b/checker/src/features/functions.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, collections::hash_map::Entry}; -use source_map::{SourceId, SpanWithSource}; +use source_map::{Nullable, SourceId, SpanWithSource}; use crate::{ context::{ @@ -10,58 +10,30 @@ use crate::{ ContextType, Syntax, }, diagnostics::{TypeCheckError, TypeStringRepresentation}, - events::{Event, FinalEvent, RootReference}, - features::create_closed_over_references, + events::RootReference, + features::{create_closed_over_references, objects::ObjectBuilder}, subtyping::{type_is_subtype_object, SubTypeResult}, types::{ self, + calling::Callable, classes::ClassValue, - functions::SynthesisedParameters, + functions::{FunctionBehavior, SynthesisedParameters}, generics::GenericTypeParameters, + logical::{Logical, LogicalOrValid}, printing::print_type, - properties::{PropertyKey, PropertyValue}, + properties::{get_property_unbound, PropertyKey, PropertyValue, Publicity}, substitute, Constructor, FunctionEffect, FunctionType, InternalFunctionEffect, PartiallyAppliedGenerics, PolyNature, SubstitutionArguments, SynthesisedParameter, SynthesisedRestParameter, TypeStore, }, - ASTImplementation, CheckingData, Environment, FunctionId, GeneralContext, Map, ReadFromFS, - Scope, Type, TypeId, VariableId, + ASTImplementation, CheckingData, Constant, Environment, FunctionId, GeneralContext, Map, + ReadFromFS, Scope, Type, TypeId, VariableId, }; -#[derive(Clone, Copy, Debug, Default, binary_serialize_derive::BinarySerializable)] -pub enum ThisValue { - Passed(TypeId), - /// Or pick from [`Constructor::Property`] - #[default] - UseParent, -} - -impl ThisValue { - pub(crate) fn get( - self, - environment: &mut Environment, - types: &TypeStore, - position: SpanWithSource, - ) -> TypeId { - match self { - ThisValue::Passed(value) => value, - ThisValue::UseParent => environment.get_value_of_this(types, position), - } - } - - pub(crate) fn get_passed(self) -> Option { - match self { - ThisValue::Passed(value) => Some(value), - ThisValue::UseParent => None, - } - } -} - #[derive(Debug, Clone, Copy)] pub enum GetterSetter { Getter, Setter, - None, } pub fn register_arrow_function( @@ -80,22 +52,30 @@ pub fn register_arrow_function( - expecting: TypeId, + expected: TypeId, is_async: bool, is_generator: bool, - location: Option, + location: ContextLocation, + name: Option, function: &impl SynthesisableFunction, environment: &mut Environment, checking_data: &mut CheckingData, ) -> TypeId { + let name = if let Some(name) = name { + checking_data.types.new_constant_type(Constant::String(name)) + } else { + extract_name(expected, &checking_data.types, environment) + }; let function_type = synthesise_function( function, FunctionRegisterBehavior::ExpressionFunction { - expecting, + expecting: expected, is_async, is_generator, location, + name, }, environment, checking_data, @@ -110,16 +90,19 @@ pub fn synthesise_hoisted_statement_function, environment: &mut Environment, checking_data: &mut CheckingData, -) { +) -> TypeId { + let name = checking_data.types.new_constant_type(Constant::String(name)); let behavior = FunctionRegisterBehavior::StatementFunction { - hoisted: variable_id, + variable_id, is_async, is_generator, location, internal_marker: None, + name, }; let function = synthesise_function(function, behavior, environment, checking_data); @@ -135,8 +118,11 @@ pub fn synthesise_hoisted_statement_function, + location: ContextLocation, + name: String, internal_marker: Option, function: &impl SynthesisableFunction, environment: &mut Environment, checking_data: &mut CheckingData, -) { +) -> TypeId { + let name = checking_data.types.new_constant_type(Constant::String(name)); let behavior = FunctionRegisterBehavior::StatementFunction { - hoisted: variable_id, + variable_id, is_async, is_generator, location, internal_marker, + name, }; let function = synthesise_function(function, behavior, environment, checking_data); - environment - .info - .variable_current_value - .insert(variable_id, checking_data.types.new_function_type(function)); + let ty = checking_data.types.new_function_type(function); + environment.info.variable_current_value.insert(variable_id, ty); + ty } pub fn function_to_property( - getter_setter: GetterSetter, + getter_setter: Option, function: FunctionType, types: &mut TypeStore, is_declare: bool, ) -> PropertyValue { match getter_setter { - GetterSetter::Getter => PropertyValue::Getter(Box::new(function)), - GetterSetter::Setter => PropertyValue::Setter(Box::new(function)), - GetterSetter::None => PropertyValue::Value( + Some(GetterSetter::Getter) => { + PropertyValue::Getter(Callable::new_from_function(function, types)) + } + Some(GetterSetter::Setter) => { + PropertyValue::Setter(Callable::new_from_function(function, types)) + } + None => PropertyValue::Value( if is_declare && matches!(function.effect, FunctionEffect::Unknown) { types.new_hoisted_function_type(function) } else { @@ -267,47 +259,6 @@ pub fn synthesise_function_default_value<'a, T: crate::ReadFromFS, A: ASTImpleme result } -/// TODO different place -/// TODO maybe generic -#[derive(Clone, Copy, Debug, binary_serialize_derive::BinarySerializable)] -pub enum FunctionBehavior { - /// For arrow functions, cannot have `this` bound - ArrowFunction { - is_async: bool, - }, - Method { - free_this_id: TypeId, - is_async: bool, - is_generator: bool, - }, - /// Functions defined `function`. Extends above by allowing `new` - Function { - /// This points the general `this` object. - /// When calling with: - /// - `new`: an arguments should set with (`free_this_id`, *new object*) - /// - regularly: bound argument, else parent `this` (I think) - free_this_id: TypeId, - /// The function type. [See](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new) - prototype: TypeId, - is_async: bool, - /// Cannot be called with `new` if true - is_generator: bool, - }, - /// Constructors, require new - Constructor { - /// The prototype of the base object - prototype: TypeId, - /// The id of the generic that needs to be pulled out - this_object_type: TypeId, - }, -} - -impl FunctionBehavior { - pub(crate) fn can_be_bound(self) -> bool { - matches!(self, Self::Method { .. } | Self::Function { .. }) - } -} - #[derive(Clone, Copy)] pub struct ReturnType(pub TypeId, pub SpanWithSource); @@ -388,20 +339,23 @@ pub enum FunctionRegisterBehavior<'a, A: crate::ASTImplementation> { is_async: bool, is_generator: bool, location: ContextLocation, + name: TypeId, }, StatementFunction { - hoisted: VariableId, + /// For hoisting cases + variable_id: VariableId, is_async: bool, is_generator: bool, location: ContextLocation, internal_marker: Option, + name: TypeId, }, ObjectMethod { // TODO this will take PartialFunction from hoisted? expecting: TypeId, is_async: bool, is_generator: bool, - // location: ContextLocation, + name: TypeId, // location: ContextLocation, }, ClassMethod { // TODO this will take PartialFunction from hoisted? @@ -412,6 +366,7 @@ pub enum FunctionRegisterBehavior<'a, A: crate::ASTImplementation> { internal_marker: Option, /// Used for shape of `this` this_shape: TypeId, + name: TypeId, }, Constructor { prototype: TypeId, @@ -419,6 +374,8 @@ pub enum FunctionRegisterBehavior<'a, A: crate::ASTImplementation> { super_type: Option, properties: ClassPropertiesToRegister<'a, A>, internal_marker: Option, + /// Name of the class + name: TypeId, }, } @@ -495,11 +452,13 @@ where prototype, properties, internal_marker, + name, } => { FunctionKind { behavior: FunctionBehavior::Constructor { prototype, this_object_type: TypeId::ERROR_TYPE, + name, }, scope: FunctionScope::Constructor { extends: super_type.is_some(), @@ -528,6 +487,7 @@ where &mut checking_data.types, base_environment, ); + crate::utilities::notify!("expected {:?}", expecting); if let Some((or, _)) = expected_parameters.as_ref().and_then(|a| a.get_parameter_type_at_index(0)) @@ -556,6 +516,7 @@ where is_async, is_generator, location, + name, } => { let (expected_parameters, expected_return) = get_expected_parameters_from_type( expecting, @@ -571,8 +532,9 @@ where behavior: FunctionBehavior::Function { is_async, is_generator, - free_this_id: TypeId::ERROR_TYPE, + this_id: TypeId::ERROR_TYPE, prototype, + name, }, scope: FunctionScope::Function { is_generator, @@ -590,11 +552,12 @@ where } } FunctionRegisterBehavior::StatementFunction { - hoisted: _, + variable_id: _variable_id, is_async, is_generator, location, internal_marker, + name, } => { let prototype = checking_data .types @@ -605,7 +568,8 @@ where is_async, is_generator, prototype, - free_this_id: TypeId::ERROR_TYPE, + this_id: TypeId::ERROR_TYPE, + name, }, scope: FunctionScope::Function { is_generator, @@ -628,6 +592,7 @@ where expecting, internal_marker, this_shape, + name, } => { let (expected_parameters, expected_return) = get_expected_parameters_from_type( expecting, @@ -640,6 +605,7 @@ where is_async, is_generator, free_this_id: this_shape, + name, }, scope: FunctionScope::MethodFunction { free_this_type: this_shape, @@ -653,7 +619,7 @@ where this_shape: Some(this_shape), } } - FunctionRegisterBehavior::ObjectMethod { is_async, is_generator, expecting } => { + FunctionRegisterBehavior::ObjectMethod { is_async, is_generator, expecting, name } => { let (expected_parameters, expected_return) = get_expected_parameters_from_type( expecting, &mut checking_data.types, @@ -665,6 +631,7 @@ where is_async, is_generator, free_this_id: TypeId::ERROR_TYPE, + name, }, scope: FunctionScope::MethodFunction { free_this_type: TypeId::ERROR_TYPE, @@ -736,7 +703,7 @@ where on: TypeId::NEW_TARGET_ARG, under: PropertyKey::String(Cow::Owned("value".to_owned())), result: this_constraint, - bind_this: true, + mode: types::properties::AccessMode::Regular, }, )); @@ -747,12 +714,12 @@ where false, ); - let this_free_variable = checking_data.types.register_type( - Type::RootPolyType(PolyNature::FreeVariable { - reference: RootReference::This, - based_on: this_constraint, - }), - ); + let ty = Type::RootPolyType(PolyNature::FreeVariable { + reference: RootReference::This, + based_on: this_constraint, + }); + + let this_free_variable = checking_data.types.register_type(ty); (this_free_variable, this_constructed_object) } else { @@ -766,20 +733,22 @@ where (TypeId::ANY_INFERRED_FREE_THIS, this_constructed_object) }; - if let FunctionBehavior::Function { ref mut free_this_id, .. } = behavior { - // TODO set object as well - *free_this_id = this_free_variable; - } - let new_conditional_type = checking_data.types.new_conditional_type( TypeId::NEW_TARGET_ARG, this_constructed_object, this_free_variable, ); - // TODO set super type as well + // TODO ahhhh + if let FunctionBehavior::Function { this_id: ref mut free_this_id, .. } = + behavior + { + *free_this_id = new_conditional_type; + } + // TODO set super type as well // TODO what is the union, shouldn't it be the this_constraint? + *this_type = new_conditional_type; } FunctionScope::Constructor { @@ -869,28 +838,6 @@ where .. } = function_environment.context_type; - let returned = if function.has_body() { - if let Some(event) = function_environment.info.events.last() { - match event { - // TODO - Event::FinalEvent(FinalEvent::Return { returned, position: _ }) => *returned, - Event::FinalEvent(FinalEvent::Throw { thrown: _, position: _ }) => { - TypeId::NEVER_TYPE - } - _ => { - crate::utilities::notify!("TODO might be others here"); - TypeId::UNDEFINED_TYPE - } - } - } else { - TypeId::UNDEFINED_TYPE - } - } else if let Some(ReturnType(ty, _)) = return_type_annotation { - ty - } else { - TypeId::UNDEFINED_TYPE - }; - // crate::utilities::notify!( // "closes_over {:?}, free_variable {:?}, in {:?}", // closes_over, @@ -901,6 +848,14 @@ where let info = function_environment.info; let variable_names = function_environment.variable_names; + let returned = if function.has_body() { + info.state.get_returned(&mut checking_data.types) + } else if let Some(ReturnType(ty, _)) = return_type_annotation { + ty + } else { + TypeId::UNDEFINED_TYPE + }; + { // let mut _back_requests = HashMap::<(), ()>::new(); // for (on, to) in requests { @@ -919,21 +874,28 @@ where // } } - // TODO this fixes properties being lost during printing and subtyping - for (on, properties) in info.current_properties { - match base_environment.info.current_properties.entry(on) { - Entry::Occupied(_occupied) => {} - Entry::Vacant(vacant) => { - vacant.insert(properties); + // TODO this fixes prototypes and properties being lost during printing and subtyping of the return type + { + for (k, v) in &info.prototypes { + base_environment.info.prototypes.insert(*k, *v); + } + + for (on, properties) in info.current_properties { + match base_environment.info.current_properties.entry(on) { + Entry::Occupied(_occupied) => {} + Entry::Vacant(vacant) => { + vacant.insert(properties); + } } } - } - for (on, properties) in info.closure_current_values { - match base_environment.info.closure_current_values.entry(on) { - Entry::Occupied(_occupied) => {} - Entry::Vacant(vacant) => { - vacant.insert(properties); + // TODO explain + for (on, properties) in info.closure_current_values { + match base_environment.info.closure_current_values.entry(on) { + Entry::Occupied(_occupied) => {} + Entry::Vacant(vacant) => { + vacant.insert(properties); + } } } } @@ -1057,7 +1019,18 @@ fn get_expected_parameters_from_type( .parameters .into_iter() .map(|p| SynthesisedParameter { - ty: substitute(p.ty, &type_arguments, environment, types), + ty: substitute( + if let Type::RootPolyType(PolyNature::Parameter { fixed_to }) = + types.get_type_by_id(p.ty) + { + *fixed_to + } else { + p.ty + }, + &type_arguments, + environment, + types, + ), ..p }) .collect(); @@ -1072,6 +1045,53 @@ fn get_expected_parameters_from_type( expected_return_type.map(|rt| substitute(rt, &type_arguments, environment, types)), ) } else { + crate::utilities::notify!("(un)Expected = {:?}", ty); (None, None) } } + +/// This is to implement +/// +/// Creates (expected & { name }) intersection object +/// +/// Little bit complex ... +pub fn new_name_expected_object( + name: TypeId, + expected: TypeId, + types: &mut TypeStore, + environment: &mut Environment, +) -> TypeId { + let mut name_object = + ObjectBuilder::new(None, types, SpanWithSource::NULL, &mut environment.info); + + name_object.append( + types::properties::Publicity::Public, + PropertyKey::String(Cow::Borrowed("name")), + PropertyValue::Value(name), + SpanWithSource::NULL, + &mut environment.info, + ); + + types.new_and_type(expected, name_object.build_object()).unwrap() +} + +/// Reverse of the above +pub fn extract_name(expecting: TypeId, types: &TypeStore, environment: &Environment) -> TypeId { + if let Type::And(_, rhs) = types.get_type_by_id(expecting) { + if let Ok(LogicalOrValid::Logical(Logical::Pure(PropertyValue::Value(ty)))) = + get_property_unbound( + (*rhs, None), + (Publicity::Public, &PropertyKey::String(Cow::Borrowed("name")), None), + false, + environment, + types, + ) { + ty + } else { + crate::utilities::notify!("Here"); + TypeId::EMPTY_STRING + } + } else { + TypeId::EMPTY_STRING + } +} diff --git a/checker/src/features/iteration.rs b/checker/src/features/iteration.rs index d6de0e51..02d9476b 100644 --- a/checker/src/features/iteration.rs +++ b/checker/src/features/iteration.rs @@ -3,23 +3,24 @@ use std::collections::HashMap; -use source_map::SpanWithSource; +use source_map::{BaseSpan, Nullable, SpanWithSource}; use crate::{ context::{ environment::Label, invocation::InvocationContext, CallCheckingBehavior, - ClosedOverReferencesInScope, + ClosedOverReferencesInScope, Environment, LocalInformation, Scope, }, events::{ - application::{apply_events_unknown, ErrorsAndInfo}, - apply_events, ApplicationResult, Event, FinalEvent, RootReference, + application::ApplicationInput, apply_events, ApplicationResult, Event, FinalEvent, + RootReference, }, features::{functions::ClosedOverVariables, operations::CanonicalEqualityAndInequality}, types::{ - printing::debug_effects, properties::get_properties_on_single_type, substitute, - Constructor, ObjectNature, PolyNature, SubstitutionArguments, TypeStore, + calling::{CallingContext, CallingDiagnostics}, + properties::get_properties_on_single_type, + substitute, Constructor, ObjectNature, PolyNature, SubstitutionArguments, TypeStore, }, - CheckingData, Constant, Environment, LocalInformation, Scope, Type, TypeId, VariableId, + CheckingData, Constant, Type, TypeId, VariableId, }; /// The type of iteration to synthesis @@ -52,6 +53,12 @@ pub fn synthesise_iteration( loop_body: impl FnOnce(&mut Environment, &mut CheckingData), position: SpanWithSource, ) { + let application_input = ApplicationInput { + this_value: crate::types::calling::ThisValue::UseParent, + call_site: position, + max_inline: checking_data.options.max_inline_count, + }; + match behavior { IterationBehavior::While(condition) => { let (condition, result, ..) = environment.new_lexical_environment_fold_into_parent( @@ -66,7 +73,11 @@ pub fn synthesise_iteration( ); // TODO not always needed - add_break_event(condition, position, &mut environment.info.events); + add_loop_described_break_event( + condition, + position, + &mut environment.info.events, + ); loop_body(environment, checking_data); @@ -94,36 +105,27 @@ pub fn synthesise_iteration( &loop_info, ); - let mut errors_and_info = ErrorsAndInfo::default(); + let mut diagnostics = CallingDiagnostics::default(); run_iteration_block( IterationKind::Condition { under: fixed_iterations.ok(), postfix_condition: false }, &events, + &application_input, RunBehavior::References(closes_over), &mut SubstitutionArguments::new_arguments_for_use_in_loop(), environment, &mut InvocationContext::new_empty(), - &mut errors_and_info, + &mut diagnostics, &mut checking_data.types, - position, ); + diagnostics + .append_to(CallingContext::Iteration, &mut checking_data.diagnostics_container); + // if let ApplicationResult::Interrupt(early_return) = run_iteration_block { // crate::utilities::notify!("Loop returned {:?}", early_return); // environment.info.events.push(Event::FinalEvent(early_return)); // } - - // TODO for other blocks - for warning in errors_and_info.warnings { - checking_data.diagnostics_container.add_info( - crate::diagnostics::Diagnostic::Position { - reason: warning.0, - // TODO temp - position: source_map::Nullable::NULL, - kind: crate::diagnostics::DiagnosticKind::Info, - }, - ); - } } IterationBehavior::DoWhile(condition) => { // let is_do_while = matches!(behavior, IterationBehavior::DoWhile(..)); @@ -143,7 +145,11 @@ pub fn synthesise_iteration( ); // TODO not always needed - add_break_event(condition, position, &mut environment.info.events); + add_loop_described_break_event( + condition, + position, + &mut environment.info.events, + ); condition }, @@ -167,18 +173,23 @@ pub fn synthesise_iteration( &loop_info, ); + let mut diagnostics = CallingDiagnostics::default(); + run_iteration_block( IterationKind::Condition { under: fixed_iterations.ok(), postfix_condition: true }, &events, + &application_input, RunBehavior::References(closes_over), &mut SubstitutionArguments::new_arguments_for_use_in_loop(), environment, &mut InvocationContext::new_empty(), - // TODO shouldn't be needed - &mut Default::default(), + &mut diagnostics, &mut checking_data.types, - position, ); + + diagnostics + .append_to(CallingContext::Iteration, &mut checking_data.diagnostics_container); + // if let ApplicationResult::Interrupt(early_return) = run_iteration_block { // todo!("{early_return:?}") // } @@ -232,7 +243,7 @@ pub fn synthesise_iteration( }; // TODO not always needed - add_break_event( + add_loop_described_break_event( condition, position, &mut environment.info.events, @@ -295,18 +306,22 @@ pub fn synthesise_iteration( environment.info.variable_current_value.insert(var, start); } + let mut diagnostics = CallingDiagnostics::default(); + run_iteration_block( IterationKind::Condition { under: fixed_iterations.ok(), postfix_condition: false }, &events, + &application_input, RunBehavior::References(closes_over), &mut SubstitutionArguments::new_arguments_for_use_in_loop(), environment, &mut InvocationContext::new_empty(), - // TODO shouldn't be needed - &mut Default::default(), + &mut diagnostics, &mut checking_data.types, - position, ); + + diagnostics + .append_to(CallingContext::Iteration, &mut checking_data.diagnostics_container); // if let ApplicationResult::Interrupt(early_return) = run_iteration_block { // todo!("{early_return:?}") // } @@ -330,9 +345,11 @@ pub fn synthesise_iteration( checking_data, |environment, checking_data| { let arguments = crate::VariableRegisterArguments { + // TODO based on LHS constant: true, space: None, initial_value: Some(variable), + allow_reregistration: false, }; A::declare_and_assign_to_fields(lhs, environment, checking_data, arguments); loop_body(environment, checking_data); @@ -341,27 +358,43 @@ pub fn synthesise_iteration( let (LocalInformation { events, .. }, closes_over) = result.unwrap(); + let mut diagnostics = CallingDiagnostics::default(); + run_iteration_block( IterationKind::Properties { on, variable }, &events, + &application_input, RunBehavior::References(closes_over), &mut SubstitutionArguments::new_arguments_for_use_in_loop(), environment, &mut InvocationContext::new_empty(), - // TODO shouldn't be needed - &mut Default::default(), + &mut diagnostics, &mut checking_data.types, - position, ); + + diagnostics + .append_to(CallingContext::Iteration, &mut checking_data.diagnostics_container); + // if let ApplicationResult::Interrupt(early_return) = run_iteration_block { // todo!("{early_return:?}") // } } - IterationBehavior::ForOf { lhs: _, rhs: _ } => todo!(), + IterationBehavior::ForOf { lhs: _, rhs } => { + let _position = A::expression_position(rhs).with_source(environment.get_source()); + let _rhs = A::synthesise_expression(rhs, TypeId::ANY_TYPE, environment, checking_data); + + // let _ = IteratorHelper::from_type(rhs, environment, checking_data, position); + todo!() + } } } -fn add_break_event(condition: TypeId, position: SpanWithSource, events: &mut Vec) { +/// Technically the `max_iterations` should make this obsolete +fn add_loop_described_break_event( + condition: TypeId, + position: SpanWithSource, + events: &mut Vec, +) { let break_event = Event::Conditionally { condition, truthy_events: 0, otherwise_events: 1, position }; events.push(break_event); @@ -405,20 +438,19 @@ pub enum RunBehavior { pub(crate) fn run_iteration_block( condition: IterationKind, events: &[Event], + input: &ApplicationInput, initial: RunBehavior, type_arguments: &mut SubstitutionArguments, top_environment: &mut Environment, invocation_context: &mut InvocationContext, - errors: &mut ErrorsAndInfo, + errors: &mut CallingDiagnostics, types: &mut TypeStore, - position: SpanWithSource, -) { - /// TODO via config and per line - const MAX_ITERATIONS: usize = 100; - - let mut s = String::new(); - debug_effects(&mut s, events, types, top_environment, 0, true); - crate::utilities::notify!("Applying:\n{}", s); +) -> Option { + // { + // let mut s = String::new(); + // debug_effects(&mut s, events, types, top_environment, 0, true); + // crate::utilities::notify!("Applying:\n{}", s); + // } match condition { IterationKind::Condition { postfix_condition, under } => { @@ -435,9 +467,19 @@ pub(crate) fn run_iteration_block( // // crate::utilities::notify!("Iteration events: {:#?}", events); // } + crate::utilities::notify!("under={:?}", under); + let non_exorbitant_amount_of_iterations = under .and_then(|under| under.calculate_iterations(types).ok()) - .and_then(|iterations| (iterations < MAX_ITERATIONS).then_some(iterations)); + .and_then(|iterations| { + let events_to_be_applied = iterations * events.len(); + + crate::utilities::notify!( + "count = {:?}. with", + (events_to_be_applied, input.max_inline) + ); + (events_to_be_applied < input.max_inline as usize).then_some(iterations) + }); if let Some(mut iterations) = non_exorbitant_amount_of_iterations { // These bodies always run at least once. TODO is there a better way? @@ -465,13 +507,13 @@ pub(crate) fn run_iteration_block( invocation_context, iterations, events, + input, type_arguments, top_environment, types, errors, |_, _| {}, - position, - ); + ) } else { evaluate_unknown_iteration_for_loop( events, @@ -482,16 +524,25 @@ pub(crate) fn run_iteration_block( top_environment, types, ); + None } } IterationKind::Properties { on, variable } => { + // Or exact ...?. TODO split ors if let Type::Object(ObjectNature::RealDeal) = types.get_type_by_id(on) { - let properties = get_properties_on_single_type(on, types, top_environment); + let properties = get_properties_on_single_type( + on, + types, + top_environment, + true, + TypeId::ANY_TYPE, + ); let mut n = 0; run_iteration_loop( invocation_context, properties.len(), events, + input, type_arguments, top_environment, types, @@ -504,8 +555,7 @@ pub(crate) fn run_iteration_block( n += 1; }, - position, - ); + ) } else { evaluate_unknown_iteration_for_loop( events, @@ -516,9 +566,25 @@ pub(crate) fn run_iteration_block( top_environment, types, ); + None } } - IterationKind::Iterator { .. } => todo!(), + IterationKind::Iterator { .. } => { + // if let Some(mut iterations) = non_exorbitant_amount_of_iterations { + // tod + // } else { + evaluate_unknown_iteration_for_loop( + events, + initial, + condition, + type_arguments, + invocation_context, + top_environment, + types, + ); + None + // } + } } } @@ -527,26 +593,26 @@ fn run_iteration_loop( invocation_context: &mut InvocationContext, iterations: usize, events: &[Event], + input: &ApplicationInput, type_arguments: &mut SubstitutionArguments, - top_environment: &mut crate::context::Context, + top_environment: &mut Environment, types: &mut TypeStore, - errors: &mut ErrorsAndInfo, + errors: &mut CallingDiagnostics, // For `for in` (TODO for of) mut each_iteration: impl for<'a> FnMut(&'a mut SubstitutionArguments, &mut TypeStore), - position: SpanWithSource, -) { +) -> Option { invocation_context.new_loop_iteration(|invocation_context| { + crate::utilities::notify!("running inline events: {:#?}", events); for _ in 0..iterations { each_iteration(type_arguments, types); let result = apply_events( events, - crate::features::functions::ThisValue::UseParent, + input, type_arguments, top_environment, invocation_context, types, errors, - position, ); if let Some(result) = result { @@ -559,33 +625,20 @@ fn run_iteration_loop( break; } ApplicationResult::Continue { carry, position } => { - let info = invocation_context.get_latest_info(top_environment); - info.events.push(FinalEvent::Continue { carry, position }.into()); - // return ApplicationResult::Interrupt(FinalEvent::Continue { - // carry: carry - 1, - // position, - // }) + return Some(ApplicationResult::Continue { carry: carry - 1, position }); } ApplicationResult::Break { carry, position } => { - let info = invocation_context.get_latest_info(top_environment); - info.events.push(FinalEvent::Continue { carry, position }.into()); - } - ApplicationResult::Return { returned, position } => { - let info = invocation_context.get_latest_info(top_environment); - info.events.push(FinalEvent::Return { returned, position }.into()); - } - ApplicationResult::Throw { thrown, position } => { - let info = invocation_context.get_latest_info(top_environment); - info.events.push(FinalEvent::Throw { thrown, position }.into()); + return Some(ApplicationResult::Break { carry: carry - 1, position }); } - ApplicationResult::Yield {} => todo!(), - ApplicationResult::Or { .. } => { - todo!("{:?}", result); + result => { + return Some(result); } } } } - }); + + None + }) } fn evaluate_unknown_iteration_for_loop( @@ -604,24 +657,57 @@ fn evaluate_unknown_iteration_for_loop( } }; + let mut calling_diagnostics = CallingDiagnostics::default(); + + // Make rest of scope aware of changes under the loop // TODO can skip if at the end of a function - apply_events_unknown( - events, - super::functions::ThisValue::UseParent, - type_arguments, - top_environment, - invocation_context, - types, - ); - - let get_latest_info = invocation_context.get_latest_info(top_environment); - get_latest_info.events.push(Event::Iterate { - kind, - initial, - iterate_over: events.len() as u32, + let _res = invocation_context.new_unknown_target(|invocation_context| { + // TODO + let max_inline = 10; + + apply_events( + events, + &ApplicationInput { + this_value: crate::types::calling::ThisValue::UseParent, + call_site: BaseSpan::NULL, + max_inline, + }, + type_arguments, + top_environment, + invocation_context, + types, + &mut calling_diagnostics, + ) }); - get_latest_info.events.extend(events.iter().cloned()); - get_latest_info.events.push(Event::EndOfControlFlow(events.len() as u32)); + + // add event + { + { + // let _in_definition = top_environment.parents_iter().any(|env| { + // matches!( + // env, + // GeneralContext::Syntax(crate::context::Context { + // context_type: Syntax { scope: Scope::DefinitionModule { .. }, .. }, + // .. + // }) + // ) + // }); + + // if !in_definition { + // crate::utilities::notify!("adding iteration events: {:#?}", events); + // } + } + + let get_latest_info = invocation_context.get_latest_info(top_environment); + get_latest_info.events.push(Event::Iterate { + kind, + initial, + iterate_over: events.len() as u32, + }); + + get_latest_info.events.extend(events.iter().cloned()); + get_latest_info.events.push(Event::EndOfControlFlow(events.len() as u32)); + } } /// Denotes values at the end of a loop diff --git a/checker/src/features/mod.rs b/checker/src/features/mod.rs index a8f608e3..712e2dd1 100644 --- a/checker/src/features/mod.rs +++ b/checker/src/features/mod.rs @@ -1,11 +1,11 @@ //! Contains implementations of specific JavaScript items and how Ezno handles them. //! Contains //! - Helper / abstracting functions for synthesising +//! - Internal representations for specific objects //! //! Does not contain -//! - Logic stuff -//! - Context -//! - Internal structures +//! - Type and logic stuff +//! - Contextual information pub mod assignments; pub mod conditional; @@ -22,14 +22,19 @@ pub mod variables; use source_map::SpanWithSource; use crate::{ - context::{get_value_of_variable, information::InformationChain, ClosedOverReferencesInScope}, + context::{get_value_of_variable, ClosedOverReferencesInScope, InformationChain}, + diagnostics::TypeStringRepresentation, events::RootReference, features::functions::ClosedOverVariables, - types::{get_constraint, PartiallyAppliedGenerics, TypeStore}, - CheckingData, Environment, Type, TypeId, + types::{ + get_constraint, + logical::{Logical, LogicalOrValid}, + properties, PartiallyAppliedGenerics, TypeStore, + }, + CheckingData, Environment, PropertyValue, Type, TypeId, }; -use self::objects::SpecialObjects; +use self::objects::SpecialObject; /// Returns result of `typeof *on*` pub fn type_of_operator(on: TypeId, types: &mut TypeStore) -> TypeId { @@ -47,20 +52,23 @@ pub fn type_of_operator(on: TypeId, types: &mut TypeStore) -> TypeId { }; // TODO could Cow or something to not allocate? types.new_constant_type(crate::Constant::String(name.to_owned())) + } else if on == TypeId::UNDEFINED_TYPE { + return types.new_constant_type(crate::Constant::String("undefined".to_owned())); + } else if on == TypeId::NULL_TYPE { + return types.new_constant_type(crate::Constant::String("null".to_owned())); } else { let ty = types.get_type_by_id(on); if let crate::Type::Constant(cst) = ty { + // TODO backing type let name = match cst { crate::Constant::NaN | crate::Constant::Number(_) => "number", crate::Constant::String(_) => "string", crate::Constant::Boolean(_) => "boolean", crate::Constant::Symbol { key: _ } => "symbol", - crate::Constant::Undefined => "undefined", - crate::Constant::Null => "object", }; // TODO could Cow or something to not allocate? types.new_constant_type(crate::Constant::String(name.to_owned())) - } else if let crate::Type::SpecialObject(SpecialObjects::Function(..)) = ty { + } else if let crate::Type::SpecialObject(SpecialObject::Function(..)) = ty { types.new_constant_type(crate::Constant::String("function".to_owned())) } else if let crate::Type::Object(..) | crate::Type::SpecialObject(..) = ty { types.new_constant_type(crate::Constant::String("object".to_owned())) @@ -97,15 +105,21 @@ pub fn instance_of_operator( if let Some(_constraint) = get_constraint(lhs, types) { todo!() } else { - let rhs_prototype = - if let Type::SpecialObject(SpecialObjects::ClassConstructor { prototype, .. }) = - types.get_type_by_id(rhs) - { - *prototype - } else { - // TODO err - rhs - }; + use crate::types::functions; + let rhs_prototype = if let Type::SpecialObject(SpecialObject::Function(func, _)) = + types.get_type_by_id(rhs) + { + let func = types.get_function_from_id(*func); + match &func.behavior { + functions::FunctionBehavior::ArrowFunction { .. } + | functions::FunctionBehavior::Method { .. } => TypeId::UNDEFINED_TYPE, + functions::FunctionBehavior::Function { prototype, .. } + | functions::FunctionBehavior::Constructor { prototype, .. } => *prototype, + } + } else { + // TODO err + rhs + }; if extends_prototype(lhs, rhs_prototype, information) { TypeId::TRUE @@ -115,43 +129,6 @@ pub fn instance_of_operator( } } -/// Returns result of `*on* as *cast_to*`. Returns `Err(())` for invalid casts where invalid casts -/// occur for casting a constant -pub fn as_cast(on: TypeId, cast_to: TypeId, types: &mut TypeStore) -> Result { - use crate::types::{Constructor, PolyNature}; - - fn can_cast_type(ty: &Type) -> bool { - match ty { - // TODO some of these are more correct than the others - crate::Type::RootPolyType(_rpt) => true, - crate::Type::Constructor(constr) => match constr { - Constructor::CanonicalRelationOperator { .. } - | Constructor::UnaryOperator { .. } - | Constructor::BinaryOperator { .. } => false, - Constructor::TypeOperator(_) => todo!(), - Constructor::TypeRelationOperator(_) => todo!(), - Constructor::Awaited { .. } - | Constructor::KeyOf(..) - | Constructor::ConditionalResult { .. } - | Constructor::Image { .. } - | Constructor::Property { .. } => true, - }, - _ => false, - } - } - - let can_cast = on == TypeId::ERROR_TYPE || can_cast_type(types.get_type_by_id(on)); - - if can_cast { - // TSC compat around `any` - let cast_to = if cast_to == TypeId::ANY_TYPE { TypeId::ERROR_TYPE } else { cast_to }; - - Ok(types.register_type(Type::RootPolyType(PolyNature::Open(cast_to)))) - } else { - Err(()) - } -} - /// Return `await *on*`. TODO await [`Type::Or`] etc pub fn await_expression( on: TypeId, @@ -240,3 +217,376 @@ pub(crate) fn create_closed_over_references( .collect(), ) } + +pub enum CannotDeleteFromError { + Constraint { constraint: TypeStringRepresentation, position: SpanWithSource }, + NonConfigurable { position: SpanWithSource }, +} + +/// WIP +pub fn delete_operator( + (publicity, under): (properties::Publicity, properties::PropertyKey<'_>), + rhs: TypeId, + position: SpanWithSource, + environment: &mut Environment, + types: &mut TypeStore, +) -> Result { + crate::utilities::notify!("Queue event"); + + let existing = has_property((publicity, &under), rhs, environment, types); + + { + let constraint = + environment.get_object_constraint(rhs).or_else(|| get_constraint(rhs, types)); + + if let Some(constraint) = constraint { + let constraint_type = types.get_type_by_id(constraint); + crate::utilities::notify!("constraint={:?}", constraint_type); + + if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::READONLY_RESTRICTION, + .. + }) = constraint_type + { + let constraint = + TypeStringRepresentation::from_type_id(constraint, environment, types, false); + return Err(CannotDeleteFromError::Constraint { constraint, position }); + } + + // Array indices deletion currently broken + let skip = constraint == TypeId::ARRAY_TYPE + || matches!( + constraint_type, + Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::ARRAY_TYPE, + .. + }) + ) || { + let get_prototype = environment.get_prototype(constraint); + crate::utilities::notify!("{:?}", get_prototype); + get_prototype == TypeId::ARRAY_TYPE + }; + + if !skip { + let property_constraint = properties::get_property_unbound( + (constraint, None), + (publicity, &under, None), + false, + environment, + types, + ); + + if let Ok(property_constraint) = property_constraint { + crate::utilities::notify!("property_constraint {:?}", property_constraint); + match property_constraint { + LogicalOrValid::Logical(Logical::Pure(n)) => match n { + PropertyValue::Value(_) + | PropertyValue::Getter(_) + | PropertyValue::GetterAndSetter { .. } + | PropertyValue::Setter(_) => { + crate::utilities::notify!( + "Cannot delete property because of constraint" + ); + let constraint = TypeStringRepresentation::from_type_id( + constraint, + environment, + types, + false, + ); + return Err(CannotDeleteFromError::Constraint { + constraint, + position, + }); + } + PropertyValue::Deleted => { + crate::utilities::notify!("Here?"); + } + PropertyValue::ConditionallyExists { .. } => { + crate::utilities::notify!("OKAY!!!"); + } + PropertyValue::Configured { on: _, descriptor } => { + crate::utilities::notify!("descriptor={:?}", descriptor); + } + }, + variant => { + crate::utilities::notify!("TODO variant={:?}", variant); + } // Logical::Or { .. } => todo!(), + // Logical::Implies { .. } => todo!(), + // Logical::BasedOnKey { .. } => todo!(), + } + } + } + } + + // Cannot `delete` from non-configurable + if let Ok(LogicalOrValid::Logical(Logical::Pure(value))) = + crate::types::properties::get_property_unbound( + (rhs, None), + (publicity, &under, None), + false, + environment, + types, + ) { + if !value.is_configuable_simple() { + return Err(CannotDeleteFromError::NonConfigurable { position }); + } + } + } + + // crate::utilities::notify!("Property constraint .is_some() {:?}", property_constraint.is_some()); + + // crate::utilities::notify!( + // "Re-assignment constraint {}, prop={} {:?}", + // print_type(constraint, types, environment, true), + // print_type(under, types, environment, true), + // property_constraint + // ); + + // TODO not great + let dependency = if get_constraint(rhs, types).is_some() { + Some(types.register_type(Type::Constructor(crate::types::Constructor::TypeOperator( + crate::types::TypeOperator::HasProperty(rhs, under.into_owned()), + )))) + } else { + None + }; + + environment.info.delete_property(rhs, (publicity, under.into_owned()), position, dependency); + + Ok(dependency.unwrap_or(existing)) +} + +pub fn in_operator( + (publicity, under): (properties::Publicity, &properties::PropertyKey<'_>), + rhs: TypeId, + environment: &mut Environment, + types: &mut TypeStore, +) -> TypeId { + let result = has_property((publicity, under), rhs, environment, types); + + // TODO if any + if get_constraint(rhs, types).is_some() { + let dependency = + types.register_type(Type::Constructor(crate::types::Constructor::TypeOperator( + crate::types::TypeOperator::HasProperty(rhs, under.into_owned()), + ))); + + { + let ty = crate::types::printing::print_type(result, types, environment, true); + crate::utilities::notify!("ty={:?}, dependency={:?}", ty, dependency); + } + + environment.info.events.push(crate::events::Event::Miscellaneous( + crate::events::MiscellaneousEvents::Has { + on: rhs, + publicity, + under: under.into_owned(), + into: dependency, + }, + )); + dependency + } else { + result + } +} + +pub mod tsc { + use source_map::SpanWithSource; + + use crate::{ + diagnostics, + types::{subtyping, Constructor, PolyNature, TypeStore}, + CheckingData, Environment, Type, TypeId, + }; + + /// Returns result of `*on* as *cast_to*`. Returns `Err(())` for invalid casts where invalid casts + /// occur for casting a constant + pub fn as_cast(on: TypeId, cast_to: TypeId, types: &mut TypeStore) -> Result { + fn can_cast_type(ty: &Type) -> bool { + match ty { + // TODO some of these are more correct than the others + Type::RootPolyType(_rpt) => true, + Type::Constructor(constr) => match constr { + Constructor::CanonicalRelationOperator { .. } + | Constructor::UnaryOperator { .. } + | Constructor::BinaryOperator { .. } => false, + Constructor::TypeOperator(_) => todo!(), + Constructor::TypeRelationOperator(_) => todo!(), + Constructor::Awaited { .. } + | Constructor::KeyOf(..) + | Constructor::ConditionalResult { .. } + | Constructor::Image { .. } + | Constructor::Property { .. } => true, + }, + _ => false, + } + } + + let can_cast = on == TypeId::ERROR_TYPE || can_cast_type(types.get_type_by_id(on)); + + if can_cast { + // TSC compat around `any` + let cast_to = if cast_to == TypeId::ANY_TYPE { TypeId::ERROR_TYPE } else { cast_to }; + + // TODO Type::Narrowed + Ok(types.register_type(Type::RootPolyType(PolyNature::Open(cast_to)))) + } else { + Err(()) + } + } + + pub fn non_null_assertion(on: TypeId, types: &mut TypeStore) -> Result { + /// TODO undefined? what about null + fn get_non_null_type(on: TypeId, types: &TypeStore) -> TypeId { + match types.get_type_by_id(on) { + // Type::Constructor(Constructor::ConditionalResult { + // condition: _, + // truthy_result, + // otherwise_result, + // result_union: _, + // }) => { + // if *truthy_result == TypeId::UNDEFINED_TYPE { + // *otherwise_result + // } else if *otherwise_result == TypeId::UNDEFINED_TYPE { + // *truthy_result + // } else { + // on + // } + // } + Type::Or(left, right) => { + if *left == TypeId::UNDEFINED_TYPE { + *right + } else if *right == TypeId::UNDEFINED_TYPE { + *left + } else { + on + } + } + ty => { + crate::utilities::notify!("{:?}", ty); + if let Some(constraint) = crate::types::get_constraint(on, types) { + get_non_null_type(constraint, types) + } else { + on + } + } + } + } + let cast_to = get_non_null_type(on, types); + + // TODO Type::Narrowed + Ok(types.register_type(Type::RootPolyType(PolyNature::Open(cast_to)))) + } + + pub fn check_satisfies( + expr_ty: TypeId, + to_satisfy: TypeId, + at: SpanWithSource, + environment: &mut Environment, + checking_data: &mut CheckingData, + ) { + pub(crate) fn check_satisfies( + expr_ty: TypeId, + to_satisfy: TypeId, + types: &TypeStore, + environment: &mut Environment, + ) -> bool { + // TODO `behavior.allow_error = true` would be better + if expr_ty == TypeId::ERROR_TYPE { + false + } else { + let mut state = subtyping::State { + already_checked: Default::default(), + mode: Default::default(), + contributions: Default::default(), + others: subtyping::SubTypingOptions { allow_errors: false }, + object_constraints: None, + }; + let result = + subtyping::type_is_subtype(to_satisfy, expr_ty, &mut state, environment, types); + + matches!(result, subtyping::SubTypeResult::IsSubType) + } + } + + if !check_satisfies(expr_ty, to_satisfy, &checking_data.types, environment) { + let expected = diagnostics::TypeStringRepresentation::from_type_id( + to_satisfy, + environment, + &checking_data.types, + false, + ); + let found = diagnostics::TypeStringRepresentation::from_type_id( + expr_ty, + environment, + &checking_data.types, + false, + ); + checking_data + .diagnostics_container + .add_error(diagnostics::TypeCheckError::NotSatisfied { at, expected, found }); + } + } +} + +/// WIP +pub(crate) fn has_property( + (publicity, key): (properties::Publicity, &properties::PropertyKey<'_>), + rhs: TypeId, + information: &impl InformationChain, + types: &mut TypeStore, +) -> TypeId { + match types.get_type_by_id(rhs) { + Type::Interface { .. } + | Type::Class { .. } + | Type::Constant(_) + | Type::FunctionReference(_) + | Type::Object(_) + | Type::PartiallyAppliedGenerics(_) + | Type::And(_, _) + | Type::SpecialObject(_) + | Type::AliasTo { .. } => { + let result = properties::get_property_unbound( + (rhs, None), + (publicity, key, None), + false, + information, + types, + ); + match result { + Ok(LogicalOrValid::Logical(result)) => match result { + Logical::Pure(_) => TypeId::TRUE, + Logical::Or { .. } => { + crate::utilities::notify!("or or implies `in`"); + TypeId::ERROR_TYPE + } + Logical::Implies { .. } => { + crate::utilities::notify!("or or implies `in`"); + TypeId::ERROR_TYPE + } + Logical::BasedOnKey { .. } => { + crate::utilities::notify!("mapped in"); + TypeId::ERROR_TYPE + } + }, + Ok(LogicalOrValid::NeedsCalculation(result)) => { + crate::utilities::notify!("TODO {:?}", result); + TypeId::ERROR_TYPE + } + Err(err) => { + crate::utilities::notify!("TODO {:?}", err); + TypeId::FALSE + } + } + } + Type::Or(_, _) => { + crate::utilities::notify!("Condtionally"); + TypeId::ERROR_TYPE + } + Type::RootPolyType(_) | Type::Constructor(_) => { + crate::utilities::notify!("Queue event / create dependent"); + let constraint = get_constraint(rhs, types).unwrap(); + has_property((publicity, key), constraint, information, types) + } + } +} diff --git a/checker/src/features/modules.rs b/checker/src/features/modules.rs index 6ef716bb..a0b8e048 100644 --- a/checker/src/features/modules.rs +++ b/checker/src/features/modules.rs @@ -6,7 +6,7 @@ use crate::{ information::{get_value_of_constant_import_variable, LocalInformation}, VariableRegisterArguments, }, - parse_source, CheckingData, Environment, Instance, Scope, Type, TypeId, TypeMappings, + parse_source, CheckingData, Environment, Instance, Map, Scope, TypeId, TypeMappings, VariableId, }; @@ -31,7 +31,7 @@ pub enum ImportKind<'a, T: Iterator>> { Everything, } -/// A module once it has been type chedked (note could have type errorts that have been raised) and all information has been resolved about it +/// A module once it has been type checked (note could have type errors that have been raised) and all information has been resolved about it pub struct SynthesisedModule { pub content: M, pub exported: Exported, @@ -59,14 +59,14 @@ impl SynthesisedModule { pub struct Exported { pub default: Option, /// Mutability purely for the mutation thingy - pub named: Vec<(String, (VariableId, VariableMutability))>, - pub named_types: Vec<(String, TypeId)>, + pub named: Map, + pub named_types: Map, } pub type ExportedVariable = (VariableId, VariableMutability); impl Exported { - pub(crate) fn get_export( + pub fn get_export( &self, want: &str, type_only: bool, @@ -74,22 +74,23 @@ impl Exported { let variable = if type_only { None } else { - self.named - .iter() - .find_map(|(export, value)| (export == want).then_some((value.0, value.1))) + self.named.get(want).map(|(name, mutability)| (*name, *mutability)) }; - let r#type = - self.named_types.iter().find_map(|(export, value)| (export == want).then_some(*value)); + let r#type = self.named_types.get(want).copied(); (variable, r#type) } + + pub fn keys(&self) -> impl Iterator { + self.named.keys().chain(self.named_types.keys()).map(AsRef::as_ref) + } } /// After a syntax error pub struct InvalidModule; -/// The result of syntehsising a module +/// The result of synthesising a module pub type FinalModule = Result, InvalidModule>; #[derive(Debug, Clone)] @@ -168,9 +169,11 @@ pub fn import_items< constant: true, initial_value: Some(TypeId::ERROR_TYPE), space: None, + allow_reregistration: false, }, position.with_source(current_source), &mut checking_data.diagnostics_container, + &mut checking_data.local_type_mappings, checking_data.options.record_all_assignments_and_reads, ); } @@ -179,32 +182,46 @@ pub fn import_items< match kind { ImportKind::Parts(parts) => { for part in parts { + // Here in nested because want to type variables as error otherwise if let Ok(Ok(ref exports)) = exports { + crate::utilities::notify!("{:?}", part); let (exported_variable, exported_type) = exports.get_export(part.value, type_only); if exported_variable.is_none() && exported_type.is_none() { + let possibles = { + let mut possibles = + crate::get_closest(exports.keys(), part.value).unwrap_or(vec![]); + possibles.sort_unstable(); + possibles + }; let position = part.position.with_source(current_source); checking_data.diagnostics_container.add_error( crate::diagnostics::TypeCheckError::FieldNotExported { file: partial_import_path, position, importing: part.value, + possibles, }, ); + // Register error environment.register_variable_handle_error( part.r#as, VariableRegisterArguments { constant: true, space: None, initial_value: Some(TypeId::ERROR_TYPE), + allow_reregistration: false, }, position, &mut checking_data.diagnostics_container, + &mut checking_data.local_type_mappings, checking_data.options.record_all_assignments_and_reads, ); } + + // add variable to scope if let Some((variable, mutability)) = exported_variable { let constant = match mutability { VariableMutability::Constant => { @@ -224,6 +241,7 @@ pub fn import_items< .position .with_source(environment.get_source()), }; + crate::utilities::notify!("{:?}", part.r#as.to_owned()); let existing = environment.variables.insert(part.r#as.to_owned(), v); if let Some(_existing) = existing { todo!("diagnostic") @@ -232,11 +250,12 @@ pub fn import_items< if let Scope::Module { ref mut exported, .. } = environment.context_type.scope { - exported.named.push((part.r#as.to_owned(), (variable, mutability))); + exported.named.insert(part.r#as.to_owned(), (variable, mutability)); } } } + // add type to scope if let Some(ty) = exported_type { let existing = environment.named_types.insert(part.r#as.to_owned(), ty); assert!(existing.is_none(), "TODO exception"); @@ -251,9 +270,11 @@ pub fn import_items< constant: true, space: None, initial_value: Some(TypeId::ERROR_TYPE), + allow_reregistration: false, }, declared_at, &mut checking_data.diagnostics_container, + &mut checking_data.local_type_mappings, checking_data.options.record_all_assignments_and_reads, ); } @@ -261,9 +282,10 @@ pub fn import_items< } ImportKind::All { under, position } => { let value = if let Ok(Ok(ref exports)) = exports { - checking_data.types.register_type(Type::SpecialObject( - crate::features::objects::SpecialObjects::Import(exports.clone()), - )) + let import_object = crate::Type::SpecialObject( + crate::features::objects::SpecialObject::Import(exports.clone()), + ); + checking_data.types.register_type(import_object) } else { crate::utilities::notify!("TODO :?"); TypeId::ERROR_TYPE @@ -274,18 +296,20 @@ pub fn import_items< constant: true, space: None, initial_value: Some(value), + allow_reregistration: false, }, position.with_source(current_source), &mut checking_data.diagnostics_container, + &mut checking_data.local_type_mappings, checking_data.options.record_all_assignments_and_reads, ); } ImportKind::Everything => { if let Ok(Ok(ref exports)) = exports { - for (name, (variable, mutability)) in &exports.named { + for (name, (variable, mutability)) in exports.named.iter() { // TODO are variables put into scope? if let Scope::Module { ref mut exported, .. } = environment.context_type.scope { - exported.named.push((name.clone(), (*variable, *mutability))); + exported.named.insert(name.clone(), (*variable, *mutability)); } } } else { @@ -430,3 +454,18 @@ pub fn import_file( None => Err(CouldNotOpenFile(PathBuf::from(to_import.to_owned()))), } } + +// pub fn get_possibles_message_for_imports(possibles: &[&str]) -> Vec { +// possibles +// .iter() +// .filter(|file| !file.ends_with(".d.ts")) +// .filter_map(|file| file.strip_suffix(".ts")) +// .map(|file| { +// if file.starts_with("./") || file.starts_with("../") { +// file.to_string() +// } else { +// "./".to_string() + file +// } +// }) +// .collect::>() +// } diff --git a/checker/src/features/objects.rs b/checker/src/features/objects.rs index b00ae0b6..422043c1 100644 --- a/checker/src/features/objects.rs +++ b/checker/src/features/objects.rs @@ -1,16 +1,15 @@ use source_map::SpanWithSource; use crate::{ - context::Environment, + context::LocalInformation, types::{ + calling::ThisValue, properties::{PropertyKey, PropertyValue, Publicity}, TypeStore, }, - FunctionId, LocalInformation, TypeId, + FunctionId, TypeId, }; -use super::functions::ThisValue; - /// Helper for building objects easy // TODO slice indexes pub struct ObjectBuilder { @@ -31,13 +30,13 @@ impl ObjectBuilder { pub fn append( &mut self, - environment: &mut Environment, publicity: Publicity, under: PropertyKey<'static>, value: PropertyValue, position: SpanWithSource, + info: &mut LocalInformation, ) { - environment.info.register_property(self.object, publicity, under, value, true, position); + info.register_property(self.object, publicity, under, value, position); } #[must_use] @@ -48,29 +47,38 @@ impl ObjectBuilder { /// These are objects (`typeof * = "object"`) but have special behavior #[derive(Clone, Debug, binary_serialize_derive::BinarySerializable)] -pub enum SpecialObjects { +pub enum SpecialObject { /// Hold state of the runtime - Promise { events: () }, + Promise { + from: FunctionId, + position: TypeId, + }, /// Hold state of the runtime - Generator { position: () }, + Generator { + from: FunctionId, + position: TypeId, + }, /// Needs overrides for calling, getting etc Proxy(Proxy), /// Not a [Constant] as `typeof /hi/ === "object"` and it has state - RegularExpression(String), + RegularExpression { + content: TypeId, + // groups: Option, + }, /// This cannot be a regular object because of is because of let mutations Import(super::modules::Exported), - /// Yeah here + /// Yeah here. Also for classes + /// TODO not all functions have `ThisValue` Function(FunctionId, ThisValue), - /// Mainly for printing - ClassConstructor { - name: String, - constructor: FunctionId, - /// For `instanceof` thing - prototype: TypeId, - }, + Null, } /// Properties of handler called (`over` passed as first argument) +/// +/// Has traps for `getPrototypeOf()`, `setPrototypeOf()`, `isExtensible()`, +/// `preventExtensions()`, `getOwnPropertyDescriptor()`, `defineProperty()`, `has()`, +/// `get()`, `set()`, `deleteProperty()`, `ownKeys()` and function methods +/// `apply()` and `construct()` #[derive(Copy, Clone, Debug, binary_serialize_derive::BinarySerializable)] pub struct Proxy { pub over: TypeId, diff --git a/checker/src/features/operations.rs b/checker/src/features/operations.rs index 71236973..8c3fe0ab 100644 --- a/checker/src/features/operations.rs +++ b/checker/src/features/operations.rs @@ -13,7 +13,7 @@ use crate::{ CheckingData, Constant, Decidable, Environment, Type, TypeId, }; -use super::objects::SpecialObjects; +use super::objects::SpecialObject; /// For these **binary** operations both operands are synthesised #[derive(Clone, Copy, Debug, binary_serialize_derive::BinarySerializable)] @@ -52,6 +52,11 @@ pub fn evaluate_pure_binary_operation_handle_errors< ) -> 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, @@ -62,6 +67,11 @@ pub fn evaluate_pure_binary_operation_handle_errors< 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, @@ -77,10 +87,7 @@ pub fn evaluate_pure_binary_operation_handle_errors< &checking_data.types, false, ), - position: lhs_pos - .without_source() - .union(rhs_pos.without_source()) - .with_source(environment.get_source()), + position, }, ); TypeId::ERROR_TYPE @@ -88,6 +95,7 @@ pub fn evaluate_pure_binary_operation_handle_errors< } } PureBinaryOperation::EqualityAndInequality(operator) => { + // Cannot error, but can be always true or false evaluate_equality_inequality_operation( lhs, &operator, @@ -95,11 +103,11 @@ pub fn evaluate_pure_binary_operation_handle_errors< &mut checking_data.types, checking_data.options.strict_casts, ) - .unwrap_or(TypeId::ERROR_TYPE) } } } +/// TODO proper err pub fn evaluate_mathematical_operation( lhs: TypeId, operator: MathematicalAndBitwise, @@ -163,7 +171,7 @@ pub fn evaluate_mathematical_operation( let value = ordered_float::NotNan::try_from(value); let ty = match value { Ok(value) => types.new_constant_type(Constant::Number(value)), - Err(_) => TypeId::NAN_TYPE, + Err(_) => TypeId::NAN, }; Ok(ty) } @@ -182,10 +190,10 @@ pub fn evaluate_mathematical_operation( // TODO check sides if is_dependent { let constructor = crate::types::Constructor::BinaryOperator { lhs, operator, rhs }; - return Ok(types.register_type(crate::Type::Constructor(constructor))); + Ok(types.register_type(crate::Type::Constructor(constructor))) + } else { + attempt_constant_math_operator(lhs, operator, rhs, types, strict_casts) } - - attempt_constant_math_operator(lhs, operator, rhs, types, strict_casts) } /// Not canonical / reducible form of [`CanonicalEqualityAndInequality`]. @@ -215,7 +223,12 @@ pub fn evaluate_equality_inequality_operation( mut rhs: TypeId, types: &mut TypeStore, strict_casts: bool, -) -> Result { +) -> TypeId { + // `NaN == t` is always true + if lhs == TypeId::NAN || rhs == TypeId::NAN { + return TypeId::FALSE; + } + match operator { EqualityAndInequality::StrictEqual => { // crate::utilities::notify!("{:?} === {:?}", lhs, rhs); @@ -230,17 +243,18 @@ pub fn evaluate_equality_inequality_operation( operator: CanonicalEqualityAndInequality::StrictEqual, rhs, }; - return Ok(types.register_type(crate::Type::Constructor(constructor))); - } - match attempt_constant_equality(lhs, rhs, types) { - Ok(ty) => Ok(ty), - Err(()) => { - unreachable!( - "should have been caught `is_dependent` above, {:?} === {:?}", - types.get_type_by_id(lhs), - types.get_type_by_id(rhs) - ) + types.register_type(crate::Type::Constructor(constructor)) + } else { + match attempt_constant_equality(lhs, rhs, types) { + Ok(ty) => ty, + Err(()) => { + unreachable!( + "should have been caught `is_dependent` above, {:?} === {:?}", + types.get_type_by_id(lhs), + types.get_type_by_id(rhs) + ) + } } } } @@ -252,52 +266,54 @@ pub fn evaluate_equality_inequality_operation( strict_casts: bool, ) -> Result { // Similar but reversed semantics to add - Ok(types.new_constant_type( - match (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) { - ( - Type::Constant(Constant::String(a)), - Type::Constant(Constant::String(b)), - ) => Constant::Boolean(a < b), - (Type::Constant(c1), Type::Constant(c2)) => { - let lhs = cast_as_number(c1, strict_casts)?; - let rhs = cast_as_number(c2, strict_casts)?; - Constant::Boolean(lhs < rhs) - } - _ => return Err(()), - }, - )) + match (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) { + (Type::Constant(Constant::String(a)), Type::Constant(Constant::String(b))) => { + // Yah rust includes string alphanumerical equivalence of strings + Ok(types.new_constant_type(Constant::Boolean(a < b))) + } + (Type::Constant(c1), Type::Constant(c2)) => { + let lhs = cast_as_number(c1, strict_casts)?; + let rhs = cast_as_number(c2, strict_casts)?; + Ok(types.new_constant_type(Constant::Boolean(lhs < rhs))) + } + _ => 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, - }) = types.get_type_by_id(lhs) + // Tidies some things for counting loop iterations { - 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)) + if let Type::Constructor(Constructor::BinaryOperator { + lhs: op_lhs, + operator, + rhs: op_rhs, + }) = types.get_type_by_id(lhs) { - crate::utilities::notify!("Shifted LT"); - lhs = *op_lhs; - rhs = types.register_type(Type::Constant(Constant::Number(lt - add))); + 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 constructor = Constructor::CanonicalRelationOperator { lhs, operator: CanonicalEqualityAndInequality::LessThan, rhs, }; - return Ok(types.register_type(crate::Type::Constructor(constructor))); + types.register_type(crate::Type::Constructor(constructor)) + } else { + attempt_less_than(lhs, rhs, types, strict_casts).unwrap() } - - attempt_less_than(lhs, rhs, types, strict_casts) } // equal OR less than EqualityAndInequality::LessThanOrEqual => { @@ -307,10 +323,10 @@ pub fn evaluate_equality_inequality_operation( rhs, types, strict_casts, - )?; + ); if equality_result == TypeId::TRUE { - Ok(equality_result) + equality_result } else if equality_result == TypeId::FALSE { evaluate_equality_inequality_operation( lhs, @@ -326,8 +342,8 @@ pub fn evaluate_equality_inequality_operation( rhs, types, strict_casts, - )?; - Ok(new_logical_or_type(equality_result, less_than_result, types)) + ); + new_logical_or_type(equality_result, less_than_result, types) } } EqualityAndInequality::StrictNotEqual => { @@ -337,7 +353,7 @@ pub fn evaluate_equality_inequality_operation( rhs, types, strict_casts, - )?; + ); evaluate_pure_unary_operator( PureUnary::LogicalNot, equality_result, @@ -347,7 +363,7 @@ pub fn evaluate_equality_inequality_operation( } EqualityAndInequality::Equal => { crate::utilities::notify!("TODO equal operator"); - Err(()) + TypeId::OPEN_BOOLEAN_TYPE } EqualityAndInequality::NotEqual => { let equality_result = evaluate_equality_inequality_operation( @@ -356,7 +372,7 @@ pub fn evaluate_equality_inequality_operation( rhs, types, strict_casts, - )?; + ); evaluate_pure_unary_operator( PureUnary::LogicalNot, equality_result, @@ -364,7 +380,7 @@ pub fn evaluate_equality_inequality_operation( strict_casts, ) } - // Swap operands! + // Swapping operands! EqualityAndInequality::GreaterThan => evaluate_equality_inequality_operation( rhs, &EqualityAndInequality::LessThan, @@ -372,7 +388,7 @@ pub fn evaluate_equality_inequality_operation( types, strict_casts, ), - // Swap operands! + // Swapping operands! EqualityAndInequality::GreaterThanOrEqual => evaluate_equality_inequality_operation( rhs, &EqualityAndInequality::LessThanOrEqual, @@ -383,11 +399,29 @@ pub fn evaluate_equality_inequality_operation( } } +pub fn is_null_or_undefined(ty: TypeId, types: &mut TypeStore) -> TypeId { + let is_null = evaluate_equality_inequality_operation( + ty, + &EqualityAndInequality::StrictEqual, + TypeId::NULL_TYPE, + types, + false, + ); + let is_undefined = evaluate_equality_inequality_operation( + ty, + &EqualityAndInequality::StrictEqual, + TypeId::UNDEFINED_TYPE, + types, + false, + ); + types.new_logical_or_type(is_null, is_undefined) +} + #[derive(Copy, Clone, Debug)] pub enum LogicalOperator { And, Or, - /// TODO is this canocial? + /// TODO is this canonical? NullCoalescing, } @@ -402,13 +436,14 @@ pub fn evaluate_logical_operation_with_expression< rhs: &'a A::Expression<'a>, checking_data: &mut CheckingData, environment: &mut Environment, + expecting: TypeId, ) -> Result { match operator { LogicalOperator::And => Ok(new_conditional_context( environment, lhs, |env: &mut Environment, data: &mut CheckingData| { - A::synthesise_expression(rhs, TypeId::ANY_TYPE, env, data) + A::synthesise_expression(rhs, expecting, env, data) }, Some(|_env: &mut Environment, _data: &mut CheckingData| lhs.0), checking_data, @@ -418,25 +453,19 @@ pub fn evaluate_logical_operation_with_expression< lhs, |_env: &mut Environment, _data: &mut CheckingData| lhs.0, Some(|env: &mut Environment, data: &mut CheckingData| { - A::synthesise_expression(rhs, TypeId::ANY_TYPE, env, data) + A::synthesise_expression(rhs, expecting, env, data) }), checking_data, )), LogicalOperator::NullCoalescing => { - let is_lhs_null = evaluate_equality_inequality_operation( - lhs.0, - &EqualityAndInequality::StrictEqual, - TypeId::NULL_TYPE, - &mut checking_data.types, - checking_data.options.strict_casts, - )?; + let null_or_undefined = is_null_or_undefined(lhs.0, &mut checking_data.types); Ok(new_conditional_context( environment, - (is_lhs_null, lhs.1), - |env: &mut Environment, data: &mut CheckingData| { - A::synthesise_expression(rhs, TypeId::ANY_TYPE, env, data) - }, - Some(|_env: &mut Environment, _data: &mut CheckingData| lhs.0), + (null_or_undefined, lhs.1), + |_env: &mut Environment, _data: &mut CheckingData| lhs.0, + Some(|env: &mut Environment, data: &mut CheckingData| { + A::synthesise_expression(rhs, expecting, env, data) + }), checking_data, )) } @@ -456,40 +485,41 @@ pub fn evaluate_pure_unary_operator( operand: TypeId, types: &mut TypeStore, strict_casts: bool, -) -> Result { +) -> TypeId { if operand == TypeId::ERROR_TYPE { - return Ok(operand); + return operand; } match operator { PureUnary::LogicalNot => { if let Decidable::Known(value) = is_type_truthy_falsy(operand, types) { if value { - Ok(TypeId::FALSE) + TypeId::FALSE } else { - Ok(TypeId::TRUE) + TypeId::TRUE } } else { - Ok(types.new_logical_negation_type(operand)) + types.new_logical_negation_type(operand) } } PureUnary::Negation | PureUnary::BitwiseNot => { if let Type::Constant(cst) = types.get_type_by_id(operand) { - let value = cast_as_number(cst, strict_casts)?; + let value = cast_as_number(cst, strict_casts).expect("hmm"); let value = match operator { PureUnary::LogicalNot => unreachable!(), PureUnary::Negation => -value, PureUnary::BitwiseNot => f64::from(!(value as i32)), }; let value = ordered_float::NotNan::try_from(value); - Ok(match value { + match value { Ok(value) => types.new_constant_type(Constant::Number(value)), - Err(_) => TypeId::NAN_TYPE, - }) + Err(_) => TypeId::NAN, + } } else { - Ok(types.register_type(Type::Constructor( - crate::types::Constructor::UnaryOperator { operator, operand }, - ))) + types.register_type(Type::Constructor(crate::types::Constructor::UnaryOperator { + operator, + operand, + })) } } } @@ -505,13 +535,16 @@ fn attempt_constant_equality( ) -> Result { let are_equal = if lhs == rhs { true + } else if matches!(lhs, TypeId::NULL_TYPE | TypeId::UNDEFINED_TYPE) { + // If above `==`` failed => false (as always have same `TypeId`) + 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) { cst1 == cst2 - } else if let (Type::Object(..) | Type::SpecialObject(SpecialObjects::Function(..)), _) - | (_, Type::Object(..) | Type::SpecialObject(SpecialObjects::Function(..))) = (lhs, rhs) + } 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 false diff --git a/checker/src/features/template_literal.rs b/checker/src/features/template_literal.rs index 643a8c68..9a18fd2a 100644 --- a/checker/src/features/template_literal.rs +++ b/checker/src/features/template_literal.rs @@ -2,11 +2,13 @@ use source_map::SpanWithSource; use crate::{ context::invocation::CheckThings, - diagnostics::TypeCheckError, features::objects::ObjectBuilder, types::{ - calling::{application_result_to_return_type, CallingInput}, - cast_as_string, SynthesisedArgument, TypeStore, + calling::{ + application_result_to_return_type, Callable, CallingContext, CallingInput, + SynthesisedArgument, + }, + cast_as_string, TypeStore, }, CheckingData, Constant, Environment, Type, TypeId, }; @@ -76,12 +78,12 @@ where p @ TemplateLiteralPart::Static(_) => { let value = part_to_type(p, environment, checking_data); static_parts.append( - environment, crate::types::properties::Publicity::Public, crate::types::properties::PropertyKey::from_usize(static_part_count.into()), crate::PropertyValue::Value(value), // TODO should static parts should have position? position, + &mut environment.info, ); static_part_count += 1; } @@ -109,11 +111,11 @@ where // TODO: Should there be a position here? static_parts.append( - environment, crate::types::properties::Publicity::Public, crate::types::properties::PropertyKey::String("length".into()), crate::types::properties::PropertyValue::Value(length), position, + &mut environment.info, ); } @@ -132,27 +134,23 @@ where let input = CallingInput { called_with_new: crate::types::calling::CalledWithNew::None, call_site: position, - call_site_type_arguments: None, + max_inline: checking_data.options.max_inline_count, }; - match crate::types::calling::call_type( - tag, + let mut diagnostics = Default::default(); + let result = Callable::Type(tag).call( arguments, - &input, + input, environment, - &mut check_things, + (&mut check_things, &mut diagnostics), &mut checking_data.types, - ) { + ); + diagnostics + .append_to(CallingContext::TemplateLiteral, &mut checking_data.diagnostics_container); + match result { Ok(res) => { application_result_to_return_type(res.result, environment, &mut checking_data.types) } - Err(error) => { - error.errors.into_iter().for_each(|error| { - checking_data - .diagnostics_container - .add_error(TypeCheckError::TemplateLiteralError(error)); - }); - error.returned_type - } + Err(error) => error.returned_type, } } else { // Bit weird but makes Rust happy diff --git a/checker/src/features/variables.rs b/checker/src/features/variables.rs index 14afb899..a8ea9068 100644 --- a/checker/src/features/variables.rs +++ b/checker/src/features/variables.rs @@ -3,11 +3,11 @@ use source_map::{Span, SpanWithSource}; use crate::context::{environment::ContextLocation, AssignmentError, VariableRegisterArguments}; -use crate::diagnostics::{PropertyRepresentation, TypeCheckError, TypeStringRepresentation}; +use crate::diagnostics::{PropertyKeyRepresentation, TypeCheckError, TypeStringRepresentation}; use crate::subtyping::{type_is_subtype_object, SubTypeResult}; use crate::{ types::{ - printing::print_type, + logical::{Logical, LogicalOrValid}, properties::{ get_property_key_names_on_a_single_type, get_property_unbound, PropertyKey, Publicity, }, @@ -15,7 +15,7 @@ use crate::{ }, CheckingData, VariableId, }; -use crate::{Environment, Instance, Logical}; +use crate::{Environment, Instance}; use std::fmt::Debug; /// A variable, that can be referenced. Can be a including class (prototypes) and functions @@ -30,6 +30,7 @@ pub enum VariableOrImport { /// be turned into a [`VariableId`] declared_at: SpanWithSource, context: ContextLocation, + allow_reregistration: bool, }, MutableImport { of: VariableId, @@ -111,14 +112,14 @@ pub fn check_variable_initialization value, - Logical::Pure(_) => todo!(), - Logical::Or { .. } => todo!(), - Logical::Implies { .. } => todo!(), + if let LogicalOrValid::Logical(value) = value { + match value { + Logical::Pure(crate::PropertyValue::Value(value)) => value, + Logical::Pure(_) => todo!(), + Logical::Or { .. } => todo!(), + Logical::Implies { .. } => todo!(), + Logical::BasedOnKey { .. } => todo!(), + } + } else { + TypeId::ERROR_TYPE } } else { + let keys; + let possibles = if let PropertyKey::String(s) = under { + keys = get_property_key_names_on_a_single_type( + space, + &checking_data.types, + environment, + ); + let mut possibles = + crate::get_closest(keys.iter().map(AsRef::as_ref), s).unwrap_or(vec![]); + possibles.sort_unstable(); + possibles + } else { + Vec::new() + }; checking_data.diagnostics_container.add_error(TypeCheckError::PropertyDoesNotExist { - property: match under.clone() { - PropertyKey::String(s) => PropertyRepresentation::StringKey(s.to_string()), - PropertyKey::Type(t) => PropertyRepresentation::Type(print_type( - t, - &checking_data.types, - environment, - false, - )), - }, + property: PropertyKeyRepresentation::new(under, environment, &checking_data.types), on: TypeStringRepresentation::from_type_id( space, environment, &checking_data.types, false, ), - site: position, - possibles: get_property_key_names_on_a_single_type( - space, - &mut checking_data.types, - environment, - ) - .iter() - .map(AsRef::as_ref) - .collect::>(), + position, + possibles, }); TypeId::ERROR_TYPE } @@ -191,10 +197,15 @@ pub fn get_new_register_argument_under` as this callback can return binary file /// TODO this shouldn't take `&self`. Should be just `T::read_file`, doesn't need any data @@ -65,10 +64,6 @@ where } } -pub use source_map::{self, SourceId, Span}; - -use crate::subtyping::State; - use levenshtein::levenshtein; pub trait ASTImplementation: Sized { @@ -119,8 +114,8 @@ pub trait ASTImplementation: Sized { ); #[allow(clippy::needless_lifetimes)] - fn synthesise_definition_file<'a, T: crate::ReadFromFS>( - file: Self::DefinitionFile<'a>, + fn synthesise_definition_module<'a, T: crate::ReadFromFS>( + module: &Self::DefinitionFile<'a>, source: SourceId, root: &RootContext, checking_data: &mut CheckingData, @@ -142,6 +137,12 @@ pub trait ASTImplementation: Sized { checking_data: &mut crate::CheckingData, ) -> TypeId; + fn synthesise_type_parameter_extends( + parameter: &Self::TypeParameter<'_>, + environment: &mut Environment, + checking_data: &mut crate::CheckingData, + ) -> TypeId; + fn synthesise_type_annotation<'a, T: crate::ReadFromFS>( annotation: &'a Self::TypeAnnotation<'a>, environment: &mut Environment, @@ -219,7 +220,11 @@ where } } - pub(crate) fn get_file(&mut self, path: &Path) -> Option { + pub(crate) fn get_file( + &mut self, + path: &Path, + chronometer: Option<&mut Chronometer>, + ) -> Option { // TODO only internal code should be able to do this if let Some("bin") = path.extension().and_then(|s| s.to_str()) { return self.file_reader.read_file(path).map(|s| File::Binary(s.clone())); @@ -238,9 +243,12 @@ where Some(File::Source(source, self.files.get_file_content(source))) } else { // Load into system + let current = chronometer.is_some().then(std::time::Instant::now); let content = self.file_reader.read_file(path)?; - let content = String::from_utf8(content); - if let Ok(content) = content { + if let Ok(content) = String::from_utf8(content) { + if let Some(current) = current { + chronometer.unwrap().fs += current.elapsed(); + } let source_id = self.files.new_source_id(path.to_path_buf(), content); Some(File::Source(source_id, self.files.get_file_content(source_id))) } else { @@ -279,6 +287,20 @@ pub trait GenericTypeParameter { fn get_name(&self) -> &str; } +#[derive(Default)] +pub struct Chronometer { + /// In binary .d.ts files + pub cached: Duration, + /// read actions + pub fs: Duration, + /// parsing. (TODO only of first file) + pub parse: Duration, + /// type checking (inc binding). TODO this includes parsing of imports + pub check: Duration, + /// parsed and type checked lines + pub lines: usize, +} + /// Contains logic for **checking phase** (none of the later steps) /// All data is global, non local to current scope /// TODO some of these should be mutex / ref cell @@ -298,6 +320,8 @@ pub struct CheckingData<'a, FSResolver, ModuleAST: ASTImplementation> { // pub(crate) events: EventsStore, pub types: TypeStore, + pub(crate) chronometer: Chronometer, + /// Do not repeat emitting unimplemented parts unimplemented_items: HashSet<&'static str>, } @@ -320,11 +344,12 @@ where Self { options, - local_type_mappings: Default::default(), - diagnostics_container: Default::default(), modules, + diagnostics_container: Default::default(), + local_type_mappings: Default::default(), types: Default::default(), unimplemented_items: Default::default(), + chronometer: Default::default(), } } @@ -347,58 +372,6 @@ where pub fn add_expression_mapping(&mut self, span: SpanWithSource, instance: Instance) { self.local_type_mappings.expressions_to_instances.push(span, instance); } - - pub fn check_satisfies( - &mut self, - expr_ty: TypeId, - to_satisfy: TypeId, - at: SpanWithSource, - environment: &mut Environment, - ) { - pub(crate) fn check_satisfies( - expr_ty: TypeId, - to_satisfy: TypeId, - types: &TypeStore, - environment: &mut Environment, - ) -> bool { - // TODO `behavior.allow_error = true` would be better - if expr_ty == TypeId::ERROR_TYPE { - false - } else { - let mut state = State { - already_checked: Default::default(), - mode: Default::default(), - contributions: Default::default(), - others: subtyping::SubTypingOptions { allow_errors: false }, - object_constraints: None, - }; - let result = - subtyping::type_is_subtype(to_satisfy, expr_ty, &mut state, environment, types); - - matches!(result, subtyping::SubTypeResult::IsSubType) - } - } - - if !check_satisfies(expr_ty, to_satisfy, &self.types, environment) { - let expected = diagnostics::TypeStringRepresentation::from_type_id( - to_satisfy, - environment, - &self.types, - false, - ); - let found = diagnostics::TypeStringRepresentation::from_type_id( - expr_ty, - environment, - &self.types, - false, - ); - self.diagnostics_container.add_error(TypeCheckError::NotSatisfied { - at, - expected, - found, - }); - } - } } /// Used for transformers and other things after checking!!!! @@ -407,23 +380,24 @@ pub struct CheckOutput { pub module_contents: MapFileStore, pub modules: HashMap>, pub diagnostics: crate::DiagnosticsContainer, - pub top_level_information: crate::LocalInformation, + pub top_level_information: LocalInformation, + pub chronometer: crate::Chronometer, } impl CheckOutput { #[must_use] pub fn get_type_at_position(&self, path: &str, pos: u32, debug: bool) -> Option { let source = self.module_contents.get_source_at_path(path.as_ref())?; - self.modules.get(&source).expect("no module").get_instance_at_position(pos).map( - |instance| { - crate::types::printing::print_type( - instance.get_value_on_ref(), - &self.types, - &self.top_level_information, - debug, - ) - }, - ) + let module = &self.modules.get(&source)?; + + module.get_instance_at_position(pos).map(|instance| { + crate::types::printing::print_type( + instance.get_value_on_ref(), + &self.types, + &ModuleInformation { top: &self.top_level_information, module: &module.info }, + debug, + ) + }) } #[must_use] @@ -440,7 +414,10 @@ impl CheckOutput { crate::types::printing::print_type( instance.get_value_on_ref(), &self.types, - &self.top_level_information, + &ModuleInformation { + top: &self.top_level_information, + module: &module.info, + }, debug, ), SpanWithSource { start: range.start, end: range.end, source }, @@ -466,6 +443,7 @@ impl CheckOutput { modules: Default::default(), diagnostics: Default::default(), top_level_information: Default::default(), + chronometer: Default::default(), } } } @@ -473,7 +451,7 @@ impl CheckOutput { #[allow(clippy::needless_pass_by_value)] pub fn check_project( entry_points: Vec, - type_definition_files: HashSet, + type_definition_files: Vec, resolver: T, options: TypeCheckOptions, parser_requirements: A::ParserRequirements, @@ -485,7 +463,13 @@ pub fn check_project( let mut root = crate::context::RootContext::new_with_primitive_references(); crate::utilities::notify!("--- Reading definition files from {:?} ---", type_definition_files); + + // Hide any debug messages from here + if !checking_data.options.debug_dts { + crate::utilities::pause_debug_mode(); + } add_definition_files_to_root(type_definition_files, &mut root, &mut checking_data); + crate::utilities::unpause_debug_mode(); if checking_data.diagnostics_container.has_error() { return CheckOutput { @@ -494,6 +478,7 @@ pub fn check_project( modules: Default::default(), diagnostics: checking_data.diagnostics_container, top_level_information: Default::default(), + chronometer: checking_data.chronometer, }; } @@ -501,6 +486,8 @@ pub fn check_project( for point in &entry_points { // eprintln!("Trying to get {point} from {:?}", checking_data.modules.files.get_paths()); + let current = checking_data.options.measure_time.then(std::time::Instant::now); + let entry_content = if let Some(source) = checking_data.modules.files.get_source_at_path(point) { @@ -512,13 +499,20 @@ pub fn check_project( } else { None }; + if let Some(current) = current { + checking_data.chronometer.fs += current.elapsed(); + } if let Some((source, content)) = entry_content { let module = parse_source(point, source, content, &mut checking_data); + let current = checking_data.options.measure_time.then(std::time::Instant::now); match module { Ok(module) => { - root.new_module_context(source, module, &mut checking_data); + let _module = root.new_module_context(source, module, &mut checking_data); + if let Some(current) = current { + checking_data.chronometer.check += current.elapsed(); + } } Err(err) => { checking_data.diagnostics_container.add_error(err); @@ -548,6 +542,7 @@ pub fn check_project( options: _, types, unimplemented_items: _, + chronometer, } = checking_data; CheckOutput { @@ -556,6 +551,7 @@ pub fn check_project( modules: modules.synthesised_modules, diagnostics: diagnostics_container, top_level_information: root.info, + chronometer, } } @@ -565,6 +561,15 @@ fn parse_source( content: String, checking_data: &mut CheckingData, ) -> Result<::Module<'static>, ::ParseError> { + if checking_data.options.measure_time { + let code_lines = + content.lines().filter(|c| !(c.is_empty() || c.trim_start().starts_with('/'))).count(); + checking_data.chronometer.lines += code_lines; + } + + // TODO pause check timing + let current = checking_data.options.measure_time.then(std::time::Instant::now); + // TODO abstract using similar to import logic let is_js = path.extension().and_then(|s| s.to_str()).map_or(false, |s| s.ends_with("js")); @@ -574,12 +579,18 @@ fn parse_source( checking_data.options.lsp_mode, ); - A::module_from_string( + let result = A::module_from_string( source, content, parse_options, &mut checking_data.modules.parser_requirements, - ) + ); + + if let Some(current) = current { + checking_data.chronometer.parse += current.elapsed(); + } + + result } const CACHE_MARKER: &[u8] = b"ezno-cache-file"; @@ -595,15 +606,18 @@ pub(crate) struct Cache { } pub(crate) fn add_definition_files_to_root( - type_definition_files: HashSet, + type_definition_files: Vec, root: &mut RootContext, checking_data: &mut CheckingData, ) { let length = type_definition_files.len(); for path in type_definition_files { + let chronometer = + checking_data.options.measure_time.then_some(&mut checking_data.chronometer); + let file = if path == PathBuf::from(crate::INTERNAL_DEFINITION_FILE_PATH) { File::Binary(crate::INTERNAL_DEFINITION_FILE.to_owned()) - } else if let Some(file) = checking_data.modules.get_file(&path) { + } else if let Some(file) = checking_data.modules.get_file(&path, chronometer) { file } else { checking_data.diagnostics_container.add_error(Diagnostic::Global { @@ -614,63 +628,44 @@ pub(crate) fn add_definition_files_to_root { - crate::utilities::notify!("Using cache :)"); - assert_eq!(length, 1, "only a single cache is current supported"); - - let vec = content[CACHE_MARKER.len()..(CACHE_MARKER.len() + U32_BYTES as usize)] - .to_owned(); - - let at_end = - ::deserialize(&mut vec.into_iter(), SourceId::NULL); - - let source_id = { - // Get source and content which is at the end. - let mut drain = content - .drain((CACHE_MARKER.len() + U32_BYTES as usize + at_end as usize)..); - - // Okay as end - let (_source_id, path) = - <(SourceId, String) as BinarySerializable>::deserialize( - &mut drain, - SourceId::NULL, - ); - - let get_source_at_path = - checking_data.modules.files.get_source_at_path(Path::new(&path)); - - if let Some(source_id) = get_source_at_path { - eprintln!("reusing source id {source_id:?}"); - source_id - } else { - // Collect from end - let source_content = String::from_utf8(drain.collect::>()).unwrap(); - checking_data.modules.files.new_source_id(path.into(), source_content) - } - }; - - let mut bytes = content.drain((CACHE_MARKER.len() + U32_BYTES as usize)..); - - // TODO WIP - let Cache { variables, named_types, info, types } = - Cache::deserialize(&mut bytes, source_id); - - root.variables = variables; - root.named_types = named_types; - root.info = info; - checking_data.types = types; + File::Binary(data) => { + let current = checking_data.options.measure_time.then(std::time::Instant::now); + deserialize_cache(length, data, checking_data, root); + if let Some(current) = current { + checking_data.chronometer.cached += current.elapsed(); + } } - File::Source(source_id, content) => { + File::Source(source_id, source) => { + if checking_data.options.measure_time { + let code_lines = source + .lines() + .filter(|c| !(c.is_empty() || c.trim_start().starts_with('/'))) + .count(); + checking_data.chronometer.lines += code_lines; + } + + let current = checking_data.options.measure_time.then(std::time::Instant::now); let result = A::definition_module_from_string( source_id, - content, + source, &mut checking_data.modules.parser_requirements, ); + if let Some(current) = current { + checking_data.chronometer.parse += current.elapsed(); + } match result { Ok(tdm) => { + let current = + checking_data.options.measure_time.then(std::time::Instant::now); + let (names, info) = - A::synthesise_definition_file(tdm, source_id, root, checking_data); + A::synthesise_definition_module(&tdm, source_id, root, checking_data); + + // TODO bad. should be per file + if let Some(current) = current { + checking_data.chronometer.check += current.elapsed(); + } root.variables.extend(names.variables); root.named_types.extend(names.named_types); @@ -687,6 +682,53 @@ pub(crate) fn add_definition_files_to_root( + length: usize, + mut content: Vec, + checking_data: &mut CheckingData, + root: &mut RootContext, +) { + crate::utilities::notify!("Using cache :)"); + assert_eq!(length, 1, "only a single cache is current supported"); + + let end_content = + content[CACHE_MARKER.len()..(CACHE_MARKER.len() + U32_BYTES as usize)].to_owned(); + + let at_end = + ::deserialize(&mut end_content.into_iter(), SourceId::NULL); + + let source_id = { + // Get source and content which is at the end. + let mut drain = + content.drain((CACHE_MARKER.len() + U32_BYTES as usize + at_end as usize)..); + + // Okay as end + let (_source_id, path) = + <(SourceId, String) as BinarySerializable>::deserialize(&mut drain, SourceId::NULL); + + let get_source_at_path = checking_data.modules.files.get_source_at_path(Path::new(&path)); + + if let Some(source_id) = get_source_at_path { + eprintln!("reusing source id {source_id:?}"); + source_id + } else { + // Collect from end + let source_content = String::from_utf8(drain.collect::>()).unwrap(); + checking_data.modules.files.new_source_id(path.into(), source_content) + } + }; + + let mut bytes = content.drain((CACHE_MARKER.len() + U32_BYTES as usize)..); + + // TODO WIP + let Cache { variables, named_types, info, types } = Cache::deserialize(&mut bytes, source_id); + + root.variables = variables; + root.named_types = named_types; + root.info = info; + checking_data.types = types; +} + const U32_BYTES: u32 = u32::BITS / u8::BITS; pub fn generate_cache( @@ -699,21 +741,20 @@ pub fn generate_cache( let mut root = crate::context::RootContext::new_with_primitive_references(); - add_definition_files_to_root( - HashSet::from_iter([on.to_path_buf()]), - &mut root, - &mut checking_data, - ); + { + add_definition_files_to_root(vec![on.to_path_buf()], &mut root, &mut checking_data); - assert!( - !checking_data.diagnostics_container.has_error(), - "found error in definition file {:#?}", - checking_data.diagnostics_container.get_diagnostics() - ); + assert!( + !checking_data.diagnostics_container.has_error(), + "found error in definition file {:#?}", + checking_data.diagnostics_container.get_diagnostics() + ); + } let mut buf = CACHE_MARKER.to_vec(); - buf.extend_from_slice(&0_u32.to_le_bytes()); + // This reserves a u32 bytes which marks where the content lives + buf.extend_from_slice(&[0u8; (u32::BITS / u8::BITS) as usize]); let cache = Cache { variables: root.variables, @@ -721,98 +762,27 @@ pub fn generate_cache( info: root.info, types: checking_data.types, }; - cache.serialize(&mut buf); - - let cache_len: usize = buf.len() - CACHE_MARKER.len() - U32_BYTES as usize; - // Set length - buf[CACHE_MARKER.len()..(CACHE_MARKER.len() + U32_BYTES as usize)] - .copy_from_slice(&(cache_len as u32).to_le_bytes()); - - // TODO not great - let Some(File::Source(source, content)) = checking_data.modules.get_file(on) else { panic!() }; - - let path = on.to_str().unwrap().to_owned(); - (source, path).serialize(&mut buf); - buf.extend_from_slice(content.as_bytes()); - - buf -} -/// Small map for 1-5 items -/// Also should be rewindable -#[derive(Debug, Clone, binary_serialize_derive::BinarySerializable)] -pub struct Map(pub Vec<(K, V)>); - -impl Default for Map { - fn default() -> Self { - Self(Default::default()) - } -} - -impl Map -where - K: PartialEq, -{ - pub fn get(&self, want: &K) -> Option<&V> { - self.0.iter().rev().find_map(|(key, value)| (want == key).then_some(value)) - } - - pub fn get_mut(&mut self, want: &K) -> Option<&mut V> { - self.0.iter_mut().rev().find_map(|(key, value)| (want == key).then_some(value)) - } - - #[must_use] - pub fn iter(&self) -> impl ExactSizeIterator { - self.0.iter() - } - - pub fn iter_mut(&mut self) -> impl ExactSizeIterator { - self.0.iter_mut() - } - - #[must_use] - pub fn values(&self) -> impl ExactSizeIterator { - self.0.iter().map(|(_, v)| v) - } - - /// *assumes `id` not already inside* - pub fn insert(&mut self, id: K, value: V) { - self.0.push((id, value)); - } - #[must_use] - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - #[must_use] - pub fn len(&self) -> usize { - self.0.len() - } - - pub fn drop_range(&mut self, range: std::ops::RangeFrom) { - self.0.drain(range); - } -} - -impl std::iter::IntoIterator for Map { - type Item = (K, V); + cache.serialize(&mut buf); - type IntoIter = as std::iter::IntoIterator>::IntoIter; + // Add content + { + let cache_len: usize = buf.len() - CACHE_MARKER.len() - U32_BYTES as usize; + // Set length + buf[CACHE_MARKER.len()..(CACHE_MARKER.len() + U32_BYTES as usize)] + .copy_from_slice(&(cache_len as u32).to_le_bytes()); - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} + // TODO not great + let Some(File::Source(source, content)) = checking_data.modules.get_file(on, None) else { + panic!() + }; -impl std::iter::FromIterator<(K, V)> for Map { - fn from_iter>(iter: T) -> Self { - Self(Vec::from_iter(iter)) + let path = on.to_str().unwrap().to_owned(); + (source, path).serialize(&mut buf); + buf.extend_from_slice(content.as_bytes()); } -} -impl std::iter::Extend<(K, V)> for Map { - fn extend>(&mut self, iter: T) { - self.0.extend(iter); - } + buf } pub fn get_closest<'a, 'b>( diff --git a/checker/src/options.rs b/checker/src/options.rs index 74429d70..fdf792db 100644 --- a/checker/src/options.rs +++ b/checker/src/options.rs @@ -24,7 +24,7 @@ pub struct TypeCheckOptions { pub debug_types: bool, /// Enables `as` casts - pub allow_cast: bool, + pub allow_type_casts: bool, /// For post type check optimisations and LSP. Stores both expressions and type annotations pub store_type_mappings: bool, @@ -37,6 +37,21 @@ pub struct TypeCheckOptions { /// Can be used for linting pub record_all_assignments_and_reads: bool, + + /// Technically the `i` in `for (let i = 0; i < ...)` can be reassigned to `any` type. But this behavior isn't great + /// and adds work for the inference engine. So this instead picks a basic type instead. This will + /// raise errors in valid javascript + pub infer_sensible_constraints_in_for_loops: bool, + + /// Evaluate exports to detect dead code + pub evaluate_exports: bool, + + pub max_inline_count: u16, + + pub measure_time: bool, + + /// Printing internal diagnostics in dts + pub debug_dts: bool, } impl Default for TypeCheckOptions { @@ -52,8 +67,13 @@ impl Default for TypeCheckOptions { store_type_mappings: false, lsp_mode: false, record_all_assignments_and_reads: false, + infer_sensible_constraints_in_for_loops: true, // TODO false at some point hopefully! - allow_cast: true, + allow_type_casts: true, + evaluate_exports: false, + max_inline_count: 300, + measure_time: false, + debug_dts: false, } } } diff --git a/checker/src/synthesis/README.md b/checker/src/synthesis/README.md index c2a1156d..81f4941d 100644 --- a/checker/src/synthesis/README.md +++ b/checker/src/synthesis/README.md @@ -1,4 +1,3 @@ -Contains logic between [Ezno's parser](https://github.com/kaleidawave/ezno/tree/main/parser) and the checkers API. +Contains logic between [Ezno's parser](https://github.com/kaleidawave/ezno/tree/main/parser) and the checker API (found in other directories above). If parsing is building abstract syntax tree from a string, synthesis is building types (and other information from abstract syntax tree) - diff --git a/checker/src/synthesis/assignments.rs b/checker/src/synthesis/assignments.rs index 5ad9948a..867622f6 100644 --- a/checker/src/synthesis/assignments.rs +++ b/checker/src/synthesis/assignments.rs @@ -11,13 +11,13 @@ use crate::{ Assignable, AssignableArrayDestructuringField, AssignableObjectDestructuringField, AssignableSpread, Reference, }, - synthesis::expressions::synthesise_expression, types::properties::{PropertyKey, Publicity}, CheckingData, TypeId, }; use super::{ - expressions::synthesise_multiple_expression, parser_property_key_to_checker_property_key, + expressions::{synthesise_expression, synthesise_multiple_expression}, + parser_property_key_to_checker_property_key, }; pub(super) trait SynthesiseToAssignable { @@ -89,7 +89,7 @@ fn synthesise_object_to_reference< .map(|item| match item.get_ast_ref() { parser::ObjectDestructuringField::Name(name, _, default_value, position) => { AssignableObjectDestructuringField::Mapped { - on: synthesise_object_property_key(name, environment), + key: synthesise_object_property_key(name, environment), name: synthesise_object_shorthand_assignable( name, environment, @@ -106,7 +106,7 @@ fn synthesise_object_to_reference< default_value, position, } => { - let on = parser_property_key_to_checker_property_key( + let key = parser_property_key_to_checker_property_key( from, environment, checking_data, @@ -114,7 +114,7 @@ fn synthesise_object_to_reference< ); AssignableObjectDestructuringField::Mapped { - on, + key, name: SynthesiseToAssignable::synthesise_to_assignable( name.get_ast_ref(), environment, @@ -241,14 +241,14 @@ pub(crate) fn synthesise_access_to_reference( Reference::Property { on: parent_ty, with: PropertyKey::String(Cow::Owned(property.clone())), - span: position.with_source(environment.get_source()), + position: position.with_source(environment.get_source()), publicity, } } parser::PropertyReference::Marker(_) => Reference::Property { on: parent_ty, with: PropertyKey::new_empty_property_key(), - span: position.with_source(environment.get_source()), + position: position.with_source(environment.get_source()), publicity: Publicity::Public, }, } @@ -265,7 +265,7 @@ pub(crate) fn synthesise_access_to_reference( Reference::Property { on: parent_ty, with: PropertyKey::from_type(key_ty, &checking_data.types), - span: position.with_source(environment.get_source()), + position: position.with_source(environment.get_source()), publicity: crate::types::properties::Publicity::Public, } } diff --git a/checker/src/synthesis/classes.rs b/checker/src/synthesis/classes.rs index 9a9b9fc9..75d185ac 100644 --- a/checker/src/synthesis/classes.rs +++ b/checker/src/synthesis/classes.rs @@ -5,19 +5,12 @@ use parser::{ }; use crate::{ - context::{information::InformationChain, Environment, VariableRegisterArguments}, + context::{Environment, InformationChain, LocalInformation}, diagnostics::TypeCheckError, features::functions::{ - function_to_property, synthesise_function, ClassPropertiesToRegister, FunctionBehavior, + function_to_property, synthesise_function, ClassPropertiesToRegister, FunctionRegisterBehavior, GetterSetter, SynthesisableFunction, }, - synthesis::{ - definitions::get_internal_function_effect_from_decorators, - functions::{build_overloaded_function, synthesise_shape}, - parser_property_key_to_checker_property_key, - type_annotations::synthesise_type_annotation, - variables::register_variable_identifier, - }, types::{ classes::ClassValue, properties::{PropertyKey, Publicity}, @@ -26,59 +19,127 @@ use crate::{ CheckingData, FunctionId, PropertyValue, Scope, Type, TypeId, }; -use super::{block::synthesise_block, expressions::synthesise_expression}; +use super::{ + block::synthesise_block, + definitions::get_internal_function_effect_from_decorators, + expressions::synthesise_expression, + functions::{build_overloaded_function, synthesise_shape}, + parser_property_key_to_checker_property_key, + type_annotations::synthesise_type_annotation, +}; /// Doesn't have any metadata yet /// /// Returns the constructor for expressions! /// /// TODO this duplicates work done during lifting +/// returns the new constructor +#[must_use] pub(super) fn synthesise_class_declaration< T: crate::ReadFromFS, P: parser::ExpressionOrStatementPosition + super::StatementOrExpressionVariable, >( class: &ClassDeclaration

, + existing_id: Option, + expected: TypeId, environment: &mut Environment, checking_data: &mut CheckingData, ) -> TypeId { - let is_declare = class.name.is_declare(); - let name = P::as_option_str(&class.name).map_or_else(String::new, str::to_owned); + // crate::utilities::notify!("hmm {:?}", (&checking_data.local_type_mappings.types_to_types, class.position)); - { - // TODO what about no name - // TODO type needs to be hoisted - // let parameters = - // if let Some(ref type_parameters) = class.type_parameters { todo!() } else { None }; - // TODO - // let nominal = true; - // let ty = Type::NamedRooted { name, parameters, nominal }; - // let class_type = checking_data.types.register_type(ty); - } + // Will leak hoisted properties on existing class ...? + if let Some(class_type) = existing_id { + let class_type2 = checking_data.types.get_type_by_id(class_type); - // crate::utilities::notify!("hmm {:?}", (&checking_data.local_type_mappings.types_to_types, class.position)); + let Type::Class { name: _, type_parameters } = class_type2 else { + unreachable!("expecting class type {:?}", class_type2) + }; - let existing_id = - checking_data.local_type_mappings.types_to_types.get_exact(class.position).copied(); + if let Some(type_parameters) = type_parameters { + let mut sub_environment = environment.new_lexical_environment(Scope::TypeAlias); + for parameter in type_parameters { + let parameter_ty = checking_data.types.get_type_by_id(*parameter); + let Type::RootPolyType(PolyNature::StructureGeneric { name, extends: _ }) = + parameter_ty + else { + unreachable!("{parameter_ty:?}") + }; - // Will leak hoisted properties on existing class ...? - let class_prototype = if let Some(existing_id) = existing_id { - existing_id + sub_environment.named_types.insert(name.clone(), *parameter); + } + let result = synthesise_class_declaration_extends_and_members( + class, + (class_type, expected), + &mut sub_environment, + checking_data, + ); + { + let LocalInformation { current_properties, prototypes, mut events, .. } = + sub_environment.info; + environment.info.events.append(&mut events); + environment.info.current_properties.extend(current_properties); + environment.info.prototypes.extend(prototypes); + } + result + } else { + synthesise_class_declaration_extends_and_members( + class, + (class_type, expected), + environment, + checking_data, + ) + } } else { // For classes in expression position - crate::utilities::notify!("TODO class expression parameters"); - checking_data.types.register_type(Type::Class { name: name.clone(), parameters: None }) - }; + crate::utilities::notify!("TODO class expression type parameters"); + let name = + P::as_option_str(&class.name).map_or_else(|| "(anonymous)".to_owned(), str::to_owned); - let extends = class.extends.as_ref().map(|extends| { - let ty = synthesise_expression(extends, environment, checking_data, TypeId::ANY_TYPE); + let class_type = checking_data + .types + .register_type(Type::Class { name: name.clone(), type_parameters: None }); - if let TypeId::NULL_TYPE = ty { + synthesise_class_declaration_extends_and_members( + class, + (class_type, expected), + environment, + checking_data, + ) + } +} + +/// `expected` for name +/// returns the new constructor +#[must_use] +fn synthesise_class_declaration_extends_and_members< + T: crate::ReadFromFS, + P: parser::ExpressionOrStatementPosition + super::StatementOrExpressionVariable, +>( + class: &ClassDeclaration

, + (class_type, expected): (TypeId, TypeId), + environment: &mut Environment, + checking_data: &mut CheckingData, +) -> TypeId { + let is_declare = class.name.is_declare(); + 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); + + let extends = class.extends.as_ref().map(|extends_expression| { + let extends = + synthesise_expression(extends_expression, environment, checking_data, TypeId::ANY_TYPE); + + crate::utilities::notify!("{:?}", (extends, checking_data.types.get_type_by_id(extends))); + + if let TypeId::NULL_TYPE = extends { checking_data.raise_unimplemented_error( "extends `null` edge case", - extends.get_position().with_source(environment.get_source()), + extends_expression.get_position().with_source(environment.get_source()), ); } - ty + + extends }); let class_constructor = class.members.iter().find_map(|member| { @@ -108,16 +169,13 @@ pub(super) fn synthesise_class_declaration< for member in &class.members { match &member.on { ClassMember::Method(false, method) => { - let publicity = match method.name.get_ast_ref() { - ParserPropertyKey::Identifier( - _, - _, - parser::property_key::PublicOrPrivate::Private, - ) => Publicity::Private, - _ => Publicity::Public, + let publicity = if method.name.get_ast_ref().is_private() { + Publicity::Private + } else { + Publicity::Public }; - let property_key = parser_property_key_to_checker_property_key( + let key = parser_property_key_to_checker_property_key( method.name.get_ast_ref(), environment, checking_data, @@ -126,10 +184,10 @@ pub(super) fn synthesise_class_declaration< // TODO abstract let (getter_setter, is_async, is_generator) = match &method.header { - MethodHeader::Get => (GetterSetter::Getter, false, false), - MethodHeader::Set => (GetterSetter::Setter, false, false), + MethodHeader::Get => (Some(GetterSetter::Getter), false, false), + MethodHeader::Set => (Some(GetterSetter::Setter), false, false), MethodHeader::Regular { is_async, generator } => { - (GetterSetter::None, *is_async, generator.is_some()) + (None, *is_async, generator.is_some()) } }; @@ -145,6 +203,9 @@ pub(super) fn synthesise_class_declaration< None }; + let internal = internal_marker.is_some(); + let has_defined_this = method.parameters.leading.0.is_some(); + let behavior = FunctionRegisterBehavior::ClassMethod { is_async, is_generator, @@ -152,8 +213,13 @@ pub(super) fn synthesise_class_declaration< super_type: None, // TODO expecting: TypeId::ANY_TYPE, + this_shape: if internal && !has_defined_this { + TypeId::ANY_TYPE + } else { + class_prototype + }, internal_marker, - this_shape: class_prototype, + name: key.into_name_type(&mut checking_data.types), }; let function = synthesise_function(method, behavior, environment, checking_data); @@ -170,21 +236,16 @@ pub(super) fn synthesise_class_declaration< environment.info.register_property( class_prototype, publicity, - property_key, + key, property, - // Is dynamic environment - true, position, ); } ClassMember::Property(false, property) => { - let publicity = match property.key.get_ast_ref() { - ParserPropertyKey::Identifier( - _, - _, - parser::property_key::PublicOrPrivate::Private, - ) => Publicity::Private, - _ => Publicity::Public, + let publicity = if property.key.get_ast_ref().is_private() { + Publicity::Private + } else { + Publicity::Public }; let key = parser_property_key_to_checker_property_key( property.key.get_ast_ref(), @@ -192,6 +253,20 @@ pub(super) fn synthesise_class_declaration< checking_data, true, ); + + // TODO temp fix for array.length + if let Some(ref property_type) = property.type_annotation { + let value = + synthesise_type_annotation(property_type, environment, checking_data); + let value = PropertyValue::Value(value); + environment.info.register_property_on_type( + class_type, + Publicity::Public, + key.clone(), + value, + ); + } + // TODO restriction properties.push(ClassValue { publicity, key, value: property.value.as_deref() }); } @@ -213,10 +288,42 @@ pub(super) fn synthesise_class_declaration< ); static_property_keys.push(key); } - _ => {} + ClassMember::Indexer { 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); + + if *is_readonly { + checking_data.raise_unimplemented_error( + "readonly class index", + position.with_source(environment.get_source()), + ); + } + + // TODO check declare + + // TODO WIP + crate::utilities::notify!("Indexing (again) for '{}'", name); + let value = PropertyValue::Value(value); + environment.info.register_property_on_type( + class_type, + Publicity::Public, + PropertyKey::Type(key), + value, + ); + } + _item => { + // crate::utilities::notify!("Skipping {:?}", _item); + } } } + let name_as_type_id = if let Some(name) = class.name.as_option_str() { + checking_data.types.new_constant_type(crate::Constant::String(name.to_owned())) + } else { + crate::features::functions::extract_name(expected, &checking_data.types, environment) + }; + // TODO abstract let constructor = if let Some((decorators, constructor)) = class_constructor { let internal_marker = if is_declare { @@ -230,6 +337,7 @@ pub(super) fn synthesise_class_declaration< super_type: extends, properties: ClassPropertiesToRegister { properties }, internal_marker, + name: name_as_type_id, }; synthesise_function(constructor, behavior, environment, checking_data) } else { @@ -238,6 +346,7 @@ pub(super) fn synthesise_class_declaration< function_id, class_prototype, extends, + name_as_type_id, ClassPropertiesToRegister { properties }, environment, checking_data, @@ -245,8 +354,13 @@ pub(super) fn synthesise_class_declaration< ) }; - let class_variable_type = - checking_data.types.new_class_constructor_type(name, constructor, class_prototype); + // TODO abstract + let function_id = constructor.id; + let class_variable_type = checking_data.types.new_class_constructor_type(constructor); + // Adds event + environment.register_constructable_function(class_variable_type, function_id); + + crate::utilities::notify!("At end {:?}", environment.context_type.free_variables); { // Static items and blocks @@ -255,13 +369,10 @@ pub(super) fn synthesise_class_declaration< for member in &class.members { match &member.on { ClassMember::Method(true, method) => { - let publicity_kind = match method.name.get_ast_ref() { - ParserPropertyKey::Identifier( - _, - _, - parser::property_key::PublicOrPrivate::Private, - ) => Publicity::Private, - _ => Publicity::Public, + let publicity = if method.name.get_ast_ref().is_private() { + Publicity::Private + } else { + Publicity::Public }; let internal_marker = if let (true, ParserPropertyKey::Identifier(name, _, _)) = @@ -277,13 +388,18 @@ pub(super) fn synthesise_class_declaration< }; let (getter_setter, is_async, is_generator) = match &method.header { - MethodHeader::Get => (GetterSetter::Getter, false, false), - MethodHeader::Set => (GetterSetter::Setter, false, false), + MethodHeader::Get => (Some(GetterSetter::Getter), false, false), + MethodHeader::Set => (Some(GetterSetter::Setter), false, false), MethodHeader::Regular { is_async, generator } => { - (GetterSetter::None, *is_async, generator.is_some()) + (None, *is_async, generator.is_some()) } }; + let key = static_property_keys.pop().unwrap(); + + let internal = internal_marker.is_some(); + let has_defined_this = method.parameters.leading.0.is_some(); + let behavior = FunctionRegisterBehavior::ClassMethod { is_async, is_generator, @@ -292,8 +408,13 @@ pub(super) fn synthesise_class_declaration< // TODO expecting: TypeId::ANY_TYPE, // Important that it points to the marker - this_shape: class_variable_type, + this_shape: if internal && !has_defined_this { + TypeId::ANY_TYPE + } else { + class_variable_type + }, internal_marker, + name: key.into_name_type(&mut checking_data.types), }; let function = @@ -306,27 +427,20 @@ pub(super) fn synthesise_class_declaration< is_declare, ); - let key = static_property_keys.pop().unwrap(); - environment.info.register_property( class_variable_type, - publicity_kind, + publicity, key, property, - // TODO - true, // TODO not needed right? method.position.with_source(environment.get_source()), ); } ClassMember::Property(true, property) => { - let publicity_kind = match property.key.get_ast_ref() { - ParserPropertyKey::Identifier( - _, - _, - parser::property_key::PublicOrPrivate::Private, - ) => Publicity::Private, - _ => Publicity::Public, + let publicity = if property.key.get_ast_ref().is_private() { + Publicity::Private + } else { + Publicity::Public }; let value = if let Some(ref value) = property.value { @@ -345,11 +459,9 @@ pub(super) fn synthesise_class_declaration< environment.info.register_property( class_variable_type, - publicity_kind, + publicity, static_property_keys.pop().unwrap(), PropertyValue::Value(value), - // TODO - true, // TODO not needed right? property.position.with_source(environment.get_source()), ); @@ -379,59 +491,53 @@ pub(super) fn synthesise_class_declaration< /// /// Builds the type of the class pub(super) fn register_statement_class_with_members( + class_type: TypeId, class: &ClassDeclaration, environment: &mut Environment, checking_data: &mut CheckingData, ) { - { - const CLASS_VARIABLE_CONSTANT: bool = true; - // crate::utilities::notify!("registering {:?}", class.name.identifier); - - register_variable_identifier( - &class.name.identifier, - environment, - checking_data, - VariableRegisterArguments { - constant: CLASS_VARIABLE_CONSTANT, - // No value yet, classes are equiv to `const` - initial_value: None, - space: None, - }, - ); - } - - let class_type = *checking_data - .local_type_mappings - .types_to_types - .get_exact(class.get_position()) - .expect("class type not lifted"); + let class_type2 = checking_data.types.get_type_by_id(class_type); - if let Some(ref extends) = class.extends { - let extends = get_extends_as_simple_type(extends, environment, checking_data); - - crate::utilities::notify!("Hoisting class with extends {:?}", extends); - if let Some(ty) = extends { - environment.info.prototypes.insert(class_type, ty); - } - } - - let get_type_by_id = checking_data.types.get_type_by_id(class_type); - - let Type::Class { name: _, parameters } = get_type_by_id else { - unreachable!("expected class type {:?}", get_type_by_id) + let Type::Class { name: _, type_parameters } = class_type2 else { + unreachable!("expecting class type {:?}", class_type2) }; - // TODO also remove - if let Some(parameters) = parameters { - for parameter in parameters { + if let Some(type_parameters) = type_parameters { + let mut sub_environment = environment.new_lexical_environment(Scope::TypeAlias); + for parameter in type_parameters { let parameter_ty = checking_data.types.get_type_by_id(*parameter); - let Type::RootPolyType(PolyNature::StructureGeneric { name, constrained: _ }) = + let Type::RootPolyType(PolyNature::StructureGeneric { name, extends: _ }) = parameter_ty else { unreachable!("{parameter_ty:?}") }; - environment.named_types.insert(name.clone(), *parameter); + sub_environment.named_types.insert(name.clone(), *parameter); + } + register_extends_and_member(class, class_type, &mut sub_environment, checking_data); + { + let crate::context::LocalInformation { current_properties, prototypes, .. } = + sub_environment.info; + environment.info.current_properties.extend(current_properties); + environment.info.prototypes.extend(prototypes); + } + } else { + register_extends_and_member(class, class_type, environment, checking_data); + } +} + +fn register_extends_and_member( + class: &ClassDeclaration, + class_type: TypeId, + environment: &mut Environment, + checking_data: &mut CheckingData, +) { + if let Some(ref extends) = class.extends { + let extends = get_extends_as_simple_type(extends, environment, checking_data); + + crate::utilities::notify!("Hoisting class with extends {:?}", extends); + if let Some(extends) = extends { + environment.info.prototypes.insert(class_type, extends); } } @@ -442,6 +548,12 @@ pub(super) fn register_statement_class_with_members( while let Some(member) = members_iter.next() { match &member.on { ClassMember::Method(initial_is_static, method) => { + let publicity = if method.name.get_ast_ref().is_private() { + Publicity::Private + } else { + Publicity::Public + }; + // TODO refactor. Maybe do in reverse? let (overloads, actual) = if method.body.0.is_none() { let mut overloads = Vec::new(); @@ -449,13 +561,8 @@ pub(super) fn register_statement_class_with_members( overloads.push(shape); // Read declarations until - while let Some(overload_declaration) = members_iter.next_if(|t| { - matches!( - &t.on, - ClassMember::Method(is_static, m) - if initial_is_static == is_static - && m.name == method.name - && !m.has_body()) + while let Some(overload_declaration) = members_iter.next_if(|dec_mem| { + next_key_matches(&dec_mem.on, method.name.get_ast_ref(), *initial_is_static) }) { let ClassMember::Method(_, method) = &overload_declaration.on else { unreachable!() @@ -465,14 +572,8 @@ pub(super) fn register_statement_class_with_members( } let upcoming = members_iter.peek().and_then(|next| { - matches!( - &next.on, - ClassMember::Method(is_static, m) - if initial_is_static == is_static - && m.name == method.name - && m.has_body() - ) - .then_some(&next.on) + next_key_matches(&next.on, method.name.get_ast_ref(), *initial_is_static) + .then_some(&next.on) }); if let Some(ClassMember::Method(_, method)) = upcoming { @@ -482,25 +583,51 @@ pub(super) fn register_statement_class_with_members( let actual = overloads.pop().unwrap(); (overloads, actual) } else { - todo!("error that missing body") + checking_data.diagnostics_container.add_error( + TypeCheckError::FunctionWithoutBodyNotAllowedHere { + position: ASTNode::get_position(method) + .with_source(environment.get_source()), + }, + ); + return; } } else { let actual = synthesise_shape(method, environment, checking_data); (Vec::new(), actual) }; + let name = + if let parser::PropertyKey::Identifier(name, ..) = method.name.get_ast_ref() { + name + } else { + // TODO skip decorator + "no_name" + }; + let internal_effect = get_internal_function_effect_from_decorators( + &member.decorators, + name, + environment, + ); + let value = build_overloaded_function( FunctionId(environment.get_source(), method.position.start), - FunctionBehavior::Method { + crate::types::functions::FunctionBehavior::Method { free_this_id: TypeId::ANY_TYPE, is_async: method.header.is_async(), is_generator: method.header.is_generator(), + // TODO + name: TypeId::ANY_TYPE, }, overloads, actual, environment, &mut checking_data.types, &mut checking_data.diagnostics_container, + if let Some(ie) = internal_effect { + ie.into() + } else { + crate::types::functions::FunctionEffect::Unknown + }, ); let under = crate::synthesis::parser_property_key_to_checker_property_key( @@ -510,16 +637,18 @@ pub(super) fn register_statement_class_with_members( false, ); - environment.info.register_property( - class_type, - Publicity::Public, - under, - PropertyValue::Value(value), - false, - method.position.with_source(environment.get_source()), - ); + if *initial_is_static { + crate::utilities::notify!("TODO static item?"); + } else { + environment.info.register_property_on_type( + class_type, + publicity, + under, + PropertyValue::Value(value), + ); + } } - ClassMember::Property(_is_static, property) => { + ClassMember::Property(is_static, property) => { let under = crate::synthesis::parser_property_key_to_checker_property_key( property.key.get_ast_ref(), environment, @@ -531,33 +660,45 @@ pub(super) fn register_statement_class_with_members( } else { TypeId::ANY_TYPE }; - environment.info.register_property( - class_type, - Publicity::Public, - under, - PropertyValue::Value(value), - false, - property.position.with_source(environment.get_source()), - ); + let publicity = if property.key.get_ast_ref().is_private() { + Publicity::Private + } else { + Publicity::Public + }; + if *is_static { + crate::utilities::notify!("TODO static item?"); + } else { + environment.info.register_property_on_type( + class_type, + publicity, + under, + PropertyValue::Value(value), + ); + } } - ClassMember::Indexer { - name: _, - indexer_type, - return_type, - is_readonly: _, - position, - } => { - crate::utilities::notify!("Warn if not declare"); + ClassMember::Indexer { 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); - environment.info.register_property( + + if *is_readonly { + checking_data.raise_unimplemented_error( + "readonly class index", + position.with_source(environment.get_source()), + ); + } + + // TODO check declare + + // TODO WIP + crate::utilities::notify!("Indexing for '{}'", name); + let value = PropertyValue::Value(value); + crate::utilities::notify!("{:?}", class_type); + environment.info.register_property_on_type( class_type, Publicity::Public, PropertyKey::Type(key), - PropertyValue::Value(value), - false, - position.with_source(environment.get_source()), + value, ); } ClassMember::Constructor(c) => { @@ -604,3 +745,17 @@ fn get_extends_as_simple_type( None } } + +fn next_key_matches( + member: &ClassMember, + initial_name: &parser::PropertyKey, + initial_is_static: bool, +) -> bool { + if let ClassMember::Method(is_static, method) = member { + initial_is_static == *is_static + && method.name.get_ast_ref() == initial_name + && !method.has_body() + } else { + false + } +} diff --git a/checker/src/synthesis/declarations.rs b/checker/src/synthesis/declarations.rs index d6f661fa..ef884204 100644 --- a/checker/src/synthesis/declarations.rs +++ b/checker/src/synthesis/declarations.rs @@ -1,4 +1,4 @@ -use parser::{declarations::VariableDeclaration, Declaration}; +use parser::{declarations::VariableDeclaration, ASTNode, Declaration}; use crate::{ context::Environment, diagnostics::TypeCheckError, features::variables::VariableMutability, @@ -15,6 +15,7 @@ pub(super) fn synthesise_variable_declaration( environment: &mut Environment, checking_data: &mut CheckingData, exported: bool, + infer_constraint: bool, ) { match declaration { VariableDeclaration::ConstDeclaration { declarations, .. } => { @@ -24,6 +25,7 @@ pub(super) fn synthesise_variable_declaration( environment, checking_data, exported.then_some(VariableMutability::Constant), + infer_constraint, ); } } @@ -37,11 +39,13 @@ pub(super) fn synthesise_variable_declaration( .map(|(first, _)| *first); VariableMutability::Mutable { reassignment_constraint: restriction } }); + synthesise_variable_declaration_item( variable_declaration, environment, checking_data, exported, + infer_constraint, ); } } @@ -55,28 +59,57 @@ pub(crate) fn synthesise_declaration( ) { match declaration { Declaration::Variable(declaration) => { - synthesise_variable_declaration(declaration, environment, checking_data, false); + synthesise_variable_declaration(declaration, environment, checking_data, false, false); } Declaration::Class(class) => { - synthesise_class_declaration(&class.on, environment, checking_data); + use super::StatementOrExpressionVariable; + + let existing_id = checking_data + .local_type_mappings + .types_to_types + .get_exact(class.on.name.identifier.get_position()) + .copied(); + + // Adding variable is done inside + let constructor = synthesise_class_declaration( + &class.on, + existing_id, + TypeId::ANY_TYPE, + environment, + checking_data, + ); + + if let Some(variable) = class.on.name.get_variable_id(environment.get_source()) { + environment.info.variable_current_value.insert(variable, constructor); + } } - Declaration::DeclareVariable(_) - | Declaration::Function(_) - | Declaration::Enum(_) - | Declaration::Interface(_) - | Declaration::TypeAlias(_) - | Declaration::Namespace(_) - | Declaration::Import(_) => {} Declaration::Export(exported) => match &exported.on { parser::declarations::ExportDeclaration::Variable { exported, position: _ } => { match exported { // Skipped as this is done earlier parser::declarations::export::Exportable::Class(class) => { + let existing_id = checking_data + .local_type_mappings + .types_to_types + .get_exact(class.name.identifier.get_position()) + .copied(); // TODO mark as exported - synthesise_class_declaration(class, environment, checking_data); + let _ = synthesise_class_declaration( + class, + existing_id, + TypeId::ANY_TYPE, + environment, + checking_data, + ); } parser::declarations::export::Exportable::Variable(variable) => { - synthesise_variable_declaration(variable, environment, checking_data, true); + synthesise_variable_declaration( + variable, + environment, + checking_data, + true, + false, + ); } parser::declarations::export::Exportable::Parts(parts) => { for part in parts { @@ -92,10 +125,10 @@ pub(crate) fn synthesise_declaration( environment.context_type.scope { if let Ok(value) = value { - exported.named.push(( + exported.named.insert( pair.r#as.to_owned(), (value.0.get_id(), value.0.get_mutability()), - )); + ); } } } @@ -131,9 +164,20 @@ pub(crate) fn synthesise_declaration( ); } } - parser::declarations::ExportDeclaration::DefaultFunction { .. } => { - todo!() + parser::declarations::ExportDeclaration::DefaultFunction { position, .. } => { + checking_data.diagnostics_container.add_error( + TypeCheckError::FunctionWithoutBodyNotAllowedHere { + position: position.with_source(environment.get_source()), + }, + ); } }, + Declaration::DeclareVariable(_) + | Declaration::Function(_) + | Declaration::Enum(_) + | Declaration::Interface(_) + | Declaration::TypeAlias(_) + | Declaration::Namespace(_) + | Declaration::Import(_) => {} } } diff --git a/checker/src/synthesis/definitions.rs b/checker/src/synthesis/definitions.rs index 56b63418..dcda810e 100644 --- a/checker/src/synthesis/definitions.rs +++ b/checker/src/synthesis/definitions.rs @@ -1,199 +1,56 @@ use parser::{ - ASTNode, Declaration, Expression, ExpressionOrStatementPosition, Statement, - StatementOrDeclaration, + ast::{export::Exportable, ExportDeclaration}, + ASTNode, Declaration, Decorated, Expression, StatementOrDeclaration, }; use source_map::SourceId; +use super::classes::synthesise_class_declaration; + use crate::{ - context::{Names, RootContext, VariableRegisterArguments}, - diagnostics::TypeCheckWarning, - features::functions::synthesise_declare_statement_function, - synthesis::{ - classes::{register_statement_class_with_members, synthesise_class_declaration}, - type_annotations::synthesise_type_annotation, - EznoParser, - }, + context::{Environment, LocalInformation, Names, RootContext}, types::InternalFunctionEffect, - Environment, LocalInformation, TypeId, VariableId, + TypeId, }; -const FUNCTION_REASSIGNMENT_CONSTANT: bool = true; - /// Interprets a definition module (.d.ts) and produces a [Environment]. Consumes the [`TypeDefinitionModule`] /// TODO remove unwraps here and add to the existing error handler pub(super) fn type_definition_file( - definition: parser::Module, + definition: &parser::Module, source: SourceId, checking_data: &mut crate::CheckingData, root: &RootContext, ) -> (Names, LocalInformation) { - use parser::declarations::{DeclareVariableDeclaration, TypeAlias}; - let mut environment = root.new_lexical_environment(crate::Scope::DefinitionModule { source }); - - // Hoisting names of interfaces, namespaces and types - for statement in &definition.items { - // TODO classes and exports - match statement { - StatementOrDeclaration::Declaration(Declaration::Interface(interface)) => { - let ty = environment.register_interface( - interface.on.name.identifier.as_option_str().unwrap_or_default(), - interface.on.is_nominal, - interface.on.type_parameters.as_deref(), - interface.on.extends.as_deref(), - interface.on.position.with_source(source), - checking_data, - ); - checking_data - .local_type_mappings - .types_to_types - .push(interface.on.get_position(), ty); - } - StatementOrDeclaration::Declaration(Declaration::Class(class)) => { - let ty = environment.register_class::( - class.on.name.as_option_str().unwrap_or_default(), - class.on.type_parameters.as_deref(), - class.on.extends.as_deref(), - &mut checking_data.types, - ); - checking_data.local_type_mappings.types_to_types.push(class.on.get_position(), ty); - } - StatementOrDeclaration::Declaration(Declaration::TypeAlias(alias)) => { - let ty = environment.new_alias( - alias.name.identifier.as_option_str().unwrap_or_default(), - alias.parameters.as_deref(), - &alias.references, - alias.get_position(), - checking_data, - ); - checking_data.local_type_mappings.types_to_types.push(alias.get_position(), ty); - } - _ => {} - } - } - - for declaration in &definition.items { - // TODO more - match declaration { - StatementOrDeclaration::Declaration(Declaration::DeclareVariable( - DeclareVariableDeclaration { keyword: _, declarations, position: _, decorators: _ }, - )) => { - for declaration in declarations { - // TODO is it ever `None`...? - let constraint = declaration.type_annotation.as_ref().map_or( - TypeId::ANY_TYPE, - |annotation| { - synthesise_type_annotation(annotation, &mut environment, checking_data) - }, - ); - - let initial_value = Some(checking_data.types.register_type( - crate::Type::RootPolyType(crate::types::PolyNature::Open(constraint)), - )); - crate::synthesis::variables::register_variable( - declaration.name.get_ast_ref(), - &mut environment, - checking_data, - VariableRegisterArguments { constant: true, space: None, initial_value }, - ); - } - } - StatementOrDeclaration::Declaration(Declaration::Interface(interface)) => { - let ty = *checking_data - .local_type_mappings - .types_to_types - .get(interface.on.get_position().start) - .unwrap(); - - super::interfaces::synthesise_signatures( - interface.on.type_parameters.as_deref(), - interface.on.extends.as_deref(), - &interface.on.members, - super::interfaces::OnToType(ty), - &mut environment, - checking_data, - ); - } - StatementOrDeclaration::Declaration(Declaration::Class(class)) => { - register_statement_class_with_members(&class.on, &mut environment, checking_data); - } - StatementOrDeclaration::Declaration(Declaration::Function(function)) => { - crate::synthesis::variables::register_variable_identifier( - &function.on.name.identifier, - &mut environment, - checking_data, - VariableRegisterArguments { - constant: FUNCTION_REASSIGNMENT_CONSTANT, - space: None, - initial_value: None, - }, - ); - } - StatementOrDeclaration::Declaration(Declaration::TypeAlias(TypeAlias { .. })) => { - crate::utilities::notify!("Don't think anything needed here"); - } - StatementOrDeclaration::Statement( - Statement::Comment(..) | Statement::Empty(..) | Statement::AestheticSemiColon(..), - ) => {} - item => { - crate::utilities::notify!("unknown {:?}", item); - checking_data.diagnostics_container.add_warning( - TypeCheckWarning::InvalidOrUnimplementedDefinitionFileItem( - item.get_position().with_source(environment.get_source()), - ), - ); - } - } - } - - for declaration in definition.items { - match declaration { - StatementOrDeclaration::Declaration(Declaration::Class(class)) => { - let class_type = - synthesise_class_declaration(&class.on, &mut environment, checking_data); - let variable_id = VariableId( - environment.get_source(), - class.on.name.identifier.get_position().start, - ); - environment.info.variable_current_value.insert(variable_id, class_type); - } - StatementOrDeclaration::Declaration(Declaration::Function(function)) => { - if !function.on.name.declare { - crate::utilities::notify!("TODO warning"); - } - - let variable_id = crate::VariableId( - environment.get_source(), - function.on.name.identifier.get_position().start, - ); - - let is_async = function.on.header.is_async(); - let is_generator = function.on.header.is_generator(); - let location = function.on.header.get_location().map(|location| match location { - parser::functions::FunctionLocationModifier::Server => "server".to_owned(), - parser::functions::FunctionLocationModifier::Worker => "worker".to_owned(), - }); - - let internal_marker = get_internal_function_effect_from_decorators( - &function.decorators, - function.on.name.as_option_str().unwrap(), - &environment, - ); - - synthesise_declare_statement_function( - variable_id, - // TODO - false, - is_async, - is_generator, - location, - internal_marker, - &function.on, - &mut environment, - checking_data, - ); + super::hoisting::hoist_statements(&definition.items, &mut environment, checking_data); + + for item in &definition.items { + if let StatementOrDeclaration::Declaration( + Declaration::Class(Decorated { on: class, .. }) + | Declaration::Export(Decorated { + on: ExportDeclaration::Variable { exported: Exportable::Class(class), position: _ }, + .. + }), + ) = item + { + use super::StatementOrExpressionVariable; + + let class_type = *checking_data + .local_type_mappings + .types_to_types + .get_exact(class.name.identifier.get_position()) + .expect("class type not lifted"); + + let constructor = synthesise_class_declaration( + class, + Some(class_type), + TypeId::ANY_TYPE, + &mut environment, + checking_data, + ); + + if let Some(variable) = class.name.get_variable_id(environment.get_source()) { + environment.info.variable_current_value.insert(variable, constructor); } - _ => {} } } diff --git a/checker/src/synthesis/expressions.rs b/checker/src/synthesis/expressions.rs index 29ee0af7..06a44634 100644 --- a/checker/src/synthesis/expressions.rs +++ b/checker/src/synthesis/expressions.rs @@ -1,55 +1,60 @@ -use std::{borrow::Cow, convert::TryInto}; +use std::{borrow::Cow, convert::TryInto, str::FromStr}; use parser::{ ast::TypeOrConst, expressions::{ object_literal::{ObjectLiteral, ObjectLiteralMember}, operators::{ - BinaryOperator, IncrementOrDecrement, UnaryOperator, UnaryPrefixAssignmentOperator, + BinaryOperator, IncrementOrDecrement as ParserIncrementOrDecrement, UnaryOperator, + UnaryPrefixAssignmentOperator, }, ArrayElement, FunctionArgument, MultipleExpression, SpecialOperators, SuperReference, TemplateLiteral, }, functions::MethodHeader, - ASTNode, Expression, + ASTNode, Expression, ExpressionOrStatementPosition, }; -use source_map::SpanWithSource; +use source_map::{Nullable, SpanWithSource}; use crate::{ - context::Logical, + context::Environment, diagnostics::{TypeCheckError, TypeCheckWarning, TypeStringRepresentation}, features::{ - self, await_expression, + self, + assignments::{AssignmentKind, AssignmentReturnStatus, IncrementOrDecrement}, + await_expression, conditional::new_conditional_context, functions::{ function_to_property, register_arrow_function, register_expression_function, synthesise_function, GetterSetter, }, - variables::VariableWithValue, - }, - types::{ - calling::{CallingInput, UnsynthesisedArgument}, - printing::{print_property_key, print_type}, - properties::{get_properties_on_single_type, get_property_unbound, PropertyKey}, - Constructor, - }, - Decidable, PropertyValue, -}; - -use crate::{ - features::{ + in_operator, objects::ObjectBuilder, + operations::is_null_or_undefined, operations::{ evaluate_logical_operation_with_expression, evaluate_pure_binary_operation_handle_errors, evaluate_pure_unary_operator, EqualityAndInequality, MathematicalAndBitwise, PureUnary, }, template_literal::synthesise_template_literal_expression, + variables::VariableWithValue, + }, + types::{ + calling::CalledWithNew, + properties::Publicity, + {Constant, TypeId}, + }, + types::{ + calling::{CallingInput, UnsynthesisedArgument}, + get_larger_type, + logical::{Logical, LogicalOrValid}, + printing::{print_property_key, print_type}, + properties::{ + get_properties_on_single_type, get_property_unbound, AccessMode, PropertyKey, + }, + Constructor, }, - types::calling::CalledWithNew, - types::properties::Publicity, - types::{Constant, TypeId}, - CheckingData, Environment, Instance, SpecialExpressions, + CheckingData, Decidable, Instance, PropertyValue, SpecialExpressions, }; use super::{ @@ -88,9 +93,6 @@ pub(super) fn synthesise_expression( Expression::StringLiteral(value, ..) => { return checking_data.types.new_constant_type(Constant::String(value.clone())) } - Expression::RegexLiteral { pattern, flags: _, position: _ } => { - return checking_data.types.new_regex(pattern.clone()); - } Expression::NumberLiteral(value, ..) => { let not_nan = if let Ok(v) = f64::try_from(value.clone()) { v.try_into().unwrap() @@ -98,6 +100,9 @@ pub(super) fn synthesise_expression( crate::utilities::notify!("TODO big int"); return TypeId::ERROR_TYPE; }; + // if not_nan == 6. { + // crate::utilities::notify!("{:?}", environment.get_all_named_types()); + // } return checking_data.types.new_constant_type(Constant::Number(not_nan)); } Expression::BooleanLiteral(value, ..) => { @@ -110,19 +115,23 @@ pub(super) fn synthesise_expression( environment: &mut Environment, checking_data: &mut CheckingData, ) -> Option<(PropertyKey<'static>, TypeId)> { - element.0.as_ref().map(|element| match element { + element.0.as_ref().and_then(|element| match element { FunctionArgument::Standard(element) => { // TODO based off above let expecting = TypeId::ANY_TYPE; let expression_type = synthesise_expression(element, environment, checking_data, expecting); - ( - PropertyKey::from_usize(match idx { - Decidable::Known(idx) => *idx, - Decidable::Unknown(_) => todo!(), - }), - expression_type, - ) + let property = match idx { + Decidable::Known(idx) => PropertyKey::from_usize(*idx), + Decidable::Unknown(_) => { + checking_data.raise_unimplemented_error( + "property after array spread", + element.get_position().with_source(environment.get_source()), + ); + PropertyKey::Type(TypeId::NUMBER_TYPE) + } + }; + Some((property, expression_type)) } FunctionArgument::Spread(_expr, position) => { { @@ -132,15 +141,19 @@ pub(super) fn synthesise_expression( ); } crate::utilities::notify!("Skipping spread"); - ( - PropertyKey::from_usize(match idx { - Decidable::Known(idx) => *idx, - Decidable::Unknown(_) => todo!(), - }), - TypeId::ERROR_TYPE, - ) + let property = match idx { + Decidable::Known(idx) => PropertyKey::from_usize(*idx), + Decidable::Unknown(_) => { + checking_data.raise_unimplemented_error( + "property after array spread", + element.get_position().with_source(environment.get_source()), + ); + PropertyKey::Type(TypeId::NUMBER_TYPE) + } + }; + Some((property, TypeId::ERROR_TYPE)) } - FunctionArgument::Comment { .. } => todo!(), + FunctionArgument::Comment { .. } => None, }) } @@ -160,11 +173,11 @@ pub(super) fn synthesise_expression( synthesise_array_item(&Decidable::Known(idx), value, environment, checking_data) { basis.append( - environment, Publicity::Public, key, crate::types::properties::PropertyValue::Value(value), spread_expression_position, + &mut environment.info, ); } } @@ -178,11 +191,11 @@ pub(super) fn synthesise_expression( // TODO: Should there be a position here? basis.append( - environment, Publicity::Public, PropertyKey::String("length".into()), value, expression.get_position().with_source(environment.get_source()), + &mut environment.info, ); } @@ -237,7 +250,7 @@ pub(super) fn synthesise_expression( &**rhs, checking_data, environment, - // TODO unwrap + expecting, // TODO unwrap ) .unwrap(); } @@ -302,7 +315,24 @@ pub(super) fn synthesise_expression( Expression::UnaryOperation { operand, operator, position } => { match operator { UnaryOperator::Plus => { - todo!("cast to number") + let operand_type = synthesise_expression( + operand, + environment, + checking_data, + TypeId::ANY_TYPE, + ); + return if get_larger_type(operand_type, &checking_data.types) + == TypeId::NUMBER_TYPE + { + // TODO add warning here + operand_type + } else { + checking_data.raise_unimplemented_error( + "Unary plus operator", + position.with_source(environment.get_source()), + ); + TypeId::ERROR_TYPE + }; } UnaryOperator::Negation | UnaryOperator::BitwiseNot | UnaryOperator::LogicalNot => { let operand_type = synthesise_expression( @@ -317,15 +347,12 @@ pub(super) fn synthesise_expression( UnaryOperator::LogicalNot => PureUnary::LogicalNot, _ => unreachable!(), }; - Instance::RValue( - evaluate_pure_unary_operator( - operator, - operand_type, - &mut checking_data.types, - checking_data.options.strict_casts, - ) - .unwrap(), - ) + Instance::RValue(evaluate_pure_unary_operator( + operator, + operand_type, + &mut checking_data.types, + checking_data.options.strict_casts, + )) } UnaryOperator::Await => { // TODO get promise T @@ -376,16 +403,31 @@ pub(super) fn synthesise_expression( TypeId::ANY_TYPE, ); match property { - parser::PropertyReference::Standard { - property, - is_private: _is_private, - } => { - let result = environment.delete_property( + parser::PropertyReference::Standard { property, is_private } => { + let publicity = if *is_private { + Publicity::Private + } else { + Publicity::Public + }; + let property = + PropertyKey::String(Cow::Owned(property.clone())); + + let position = position.with_source(environment.get_source()); + match crate::features::delete_operator( + (publicity, property), on, - &PropertyKey::String(Cow::Owned(property.clone())), - parent.get_position().with_source(environment.get_source()), - ); - return if result { TypeId::TRUE } else { TypeId::FALSE }; + position, + environment, + &mut checking_data.types, + ) { + Ok(result) => Instance::RValue(result), + Err(err) => { + checking_data.diagnostics_container.add_error( + TypeCheckError::CannotDeleteProperty(err), + ); + return TypeId::ERROR_TYPE; + } + } } parser::PropertyReference::Marker(_) => { crate::utilities::notify!("Deleting property marker found"); @@ -407,13 +449,23 @@ pub(super) fn synthesise_expression( TypeId::ANY_TYPE, ); + let position = position.with_source(environment.get_source()); let property = PropertyKey::from_type(indexer, &checking_data.types); - let result = environment.delete_property( + match crate::features::delete_operator( + (Publicity::Public, property), being_indexed, - &property, - indexee.get_position().with_source(environment.get_source()), - ); - return if result { TypeId::TRUE } else { TypeId::FALSE }; + position, + environment, + &mut checking_data.types, + ) { + Ok(result) => Instance::RValue(result), + Err(err) => { + checking_data + .diagnostics_container + .add_error(TypeCheckError::CannotDeleteProperty(err)); + return TypeId::ERROR_TYPE; + } + } } _ => { crate::utilities::notify!("Deleting non property raise warning"); @@ -440,9 +492,9 @@ pub(super) fn synthesise_expression( let lhs = SynthesiseToAssignable::synthesise_to_assignable(lhs, environment, checking_data); - return environment.assign_to_assignable_handle_errors( + return environment.assign_handle_errors( lhs, - crate::features::assignments::AssignmentKind::Assign, + AssignmentKind::Assign, Some(&**rhs), *position, checking_data, @@ -452,7 +504,7 @@ pub(super) fn synthesise_expression( let lhs = SynthesiseToAssignable::synthesise_to_assignable(lhs, environment, checking_data); - return environment.assign_to_assignable_handle_errors( + return environment.assign_handle_errors( lhs, operator_to_assignment_kind(*operator), Some(&**rhs), @@ -468,20 +520,26 @@ pub(super) fn synthesise_expression( ); match operator { - UnaryPrefixAssignmentOperator::Invert => todo!(), + UnaryPrefixAssignmentOperator::Invert => { + checking_data.raise_unimplemented_error( + "Invert operator", + position.with_source(environment.get_source()), + ); + return TypeId::ERROR_TYPE; + } UnaryPrefixAssignmentOperator::IncrementOrDecrement(direction) => { - return environment.assign_to_assignable_handle_errors( + return environment.assign_handle_errors( lhs, - crate::features::assignments::AssignmentKind::IncrementOrDecrement( + AssignmentKind::IncrementOrDecrement( match direction { - IncrementOrDecrement::Increment => { - crate::features::assignments::IncrementOrDecrement::Increment + ParserIncrementOrDecrement::Increment => { + IncrementOrDecrement::Increment } - IncrementOrDecrement::Decrement => { - crate::features::assignments::IncrementOrDecrement::Decrement + ParserIncrementOrDecrement::Decrement => { + IncrementOrDecrement::Decrement } }, - crate::features::assignments::AssignmentReturnStatus::New, + AssignmentReturnStatus::New, ), None::<&Expression>, *position, @@ -499,20 +557,15 @@ pub(super) fn synthesise_expression( match operator { parser::expressions::operators::UnaryPostfixAssignmentOperator(direction) => { let direction = match direction { - IncrementOrDecrement::Increment => { - crate::features::assignments::IncrementOrDecrement::Increment - } - IncrementOrDecrement::Decrement => { - crate::features::assignments::IncrementOrDecrement::Decrement - } + ParserIncrementOrDecrement::Increment => IncrementOrDecrement::Increment, + ParserIncrementOrDecrement::Decrement => IncrementOrDecrement::Decrement, }; - let operator = - crate::features::assignments::AssignmentKind::IncrementOrDecrement( - direction, - crate::features::assignments::AssignmentReturnStatus::Previous, - ); + let operator = AssignmentKind::IncrementOrDecrement( + direction, + AssignmentReturnStatus::Previous, + ); - return environment.assign_to_assignable_handle_errors( + return environment.assign_handle_errors( lhs, operator, None::<&Expression>, @@ -531,10 +584,10 @@ pub(super) fn synthesise_expression( match get_variable_or_alternatives { Ok(variable) => Instance::LValue(variable), - Err(_err) => Instance::RValue(TypeId::ERROR_TYPE), + Err(_) => Instance::RValue(TypeId::ERROR_TYPE), } } - Expression::PropertyAccess { parent, position, property, is_optional: _, .. } => { + Expression::PropertyAccess { parent, position, property, is_optional, .. } => { let on = synthesise_expression(parent, environment, checking_data, TypeId::ANY_TYPE); let (property, publicity) = match property { parser::PropertyReference::Standard { property, is_private } => ( @@ -547,43 +600,102 @@ pub(super) fn synthesise_expression( } }; - let result = environment.get_property_handle_errors( - on, - publicity, - &property, - checking_data, - position.with_source(environment.get_source()), - true, - ); - - match result { - Ok(instance) => instance, - Err(()) => return TypeId::ERROR_TYPE, + let site = position.with_source(environment.get_source()); + if *is_optional { + let null_or_undefined = is_null_or_undefined(on, &mut checking_data.types); + Instance::RValue(new_conditional_context( + environment, + (null_or_undefined, parent.get_position()), + |_env: &mut Environment, _data: &mut CheckingData| { + TypeId::UNDEFINED_TYPE + }, + Some(|env: &mut Environment, data: &mut CheckingData| { + let result = env.get_property_handle_errors( + on, + publicity, + &property, + data, + site, + AccessMode::Regular, + ); + match result { + Ok(i) => i.get_value(), + Err(()) => TypeId::ERROR_TYPE, + } + }), + checking_data, + )) + } else { + let result = environment.get_property_handle_errors( + on, + publicity, + &property, + checking_data, + site, + AccessMode::Regular, + ); + match result { + Ok(i) => Instance::RValue(i.get_value()), + Err(()) => { + return TypeId::ERROR_TYPE; + } + } } } - Expression::Index { indexee, indexer, position, .. } => { + Expression::Index { indexee, indexer, position, is_optional, .. } => { let being_indexed = synthesise_expression(indexee, environment, checking_data, TypeId::ANY_TYPE); - let indexer = synthesise_multiple_expression( - indexer, - environment, - checking_data, - TypeId::ANY_TYPE, - ); - - // TODO handle differently? - let result = environment.get_property_handle_errors( - being_indexed, - Publicity::Public, - &PropertyKey::from_type(indexer, &checking_data.types), - checking_data, - position.with_source(environment.get_source()), - true, - ); + let site = position.with_source(environment.get_source()); - match result { - Ok(instance) => instance, - Err(()) => return TypeId::ERROR_TYPE, + if *is_optional { + let null_or_undefined = + is_null_or_undefined(being_indexed, &mut checking_data.types); + Instance::RValue(new_conditional_context( + environment, + (null_or_undefined, indexee.get_position()), + |_env: &mut Environment, _data: &mut CheckingData| { + TypeId::UNDEFINED_TYPE + }, + Some(|env: &mut Environment, data: &mut CheckingData| { + // Indexer is actually side effected here + let indexer = + synthesise_multiple_expression(indexer, env, data, TypeId::ANY_TYPE); + let result = env.get_property_handle_errors( + being_indexed, + Publicity::Public, + &PropertyKey::from_type(indexer, &data.types), + data, + site, + AccessMode::Regular, + ); + match result { + Ok(i) => i.get_value(), + Err(()) => TypeId::ERROR_TYPE, + } + }), + checking_data, + )) + } else { + let indexer = synthesise_multiple_expression( + indexer, + environment, + checking_data, + TypeId::ANY_TYPE, + ); + let result = environment.get_property_handle_errors( + being_indexed, + Publicity::Public, + &PropertyKey::from_type(indexer, &checking_data.types), + checking_data, + site, + AccessMode::Regular, + ); + match result { + Ok(i) => Instance::RValue(i.get_value()), + Err(()) => { + return TypeId::ERROR_TYPE; + } + } } } Expression::ThisReference(pos) => Instance::RValue( @@ -599,6 +711,13 @@ pub(super) fn synthesise_expression( &checking_data.types, position.with_source(environment.get_source()), ); + + crate::utilities::notify!( + "{:?}", + checking_data.types.get_type_by_id(super_type) + ); + + // TODO this gives normal errors. Maybe add something about super here let (result, special) = call_function( super_type, CalledWithNew::Super { this_type }, @@ -620,15 +739,29 @@ pub(super) fn synthesise_expression( Instance::RValue(result) } - SuperReference::PropertyAccess { property: _ } => todo!(), - SuperReference::Index { indexer: _ } => todo!(), + SuperReference::PropertyAccess { property: _ } => { + checking_data.raise_unimplemented_error( + "Property access on super", + position.with_source(environment.get_source()), + ); + return TypeId::ERROR_TYPE; + } + SuperReference::Index { indexer: _ } => { + checking_data.raise_unimplemented_error( + "Index on super", + position.with_source(environment.get_source()), + ); + return TypeId::ERROR_TYPE; + } } } else { crate::utilities::notify!("TODO error"); Instance::RValue(TypeId::ERROR_TYPE) } } - Expression::NewTarget(..) => todo!(), + Expression::NewTarget(..) => { + return TypeId::NEW_TARGET_ARG; + } Expression::FunctionCall { function, type_arguments, arguments, position, .. } => { let on = synthesise_expression(function, environment, checking_data, TypeId::ANY_TYPE); @@ -699,6 +832,7 @@ pub(super) fn synthesise_expression( is_async, is_generator, location, + function.name.as_option_str().map(ToOwned::to_owned), function, environment, checking_data, @@ -709,15 +843,25 @@ pub(super) fn synthesise_expression( Expression::JSXRoot(jsx_root) => { Instance::RValue(synthesise_jsx_root(jsx_root, environment, checking_data)) } + Expression::RegexLiteral { pattern, flags: _, position: _ } => { + let content = checking_data.types.new_constant_type(Constant::String(pattern.clone())); + Instance::RValue(checking_data.types.register_type(crate::Type::SpecialObject( + crate::types::SpecialObject::RegularExpression { content }, + ))) + } Expression::Comment { on, .. } => { return synthesise_expression(on, environment, checking_data, expecting); } Expression::ParenthesizedExpression(inner_expression, _) => Instance::RValue( synthesise_multiple_expression(inner_expression, environment, checking_data, expecting), ), - Expression::ClassExpression(class) => { - Instance::RValue(synthesise_class_declaration(class, environment, checking_data)) - } + Expression::ClassExpression(class) => Instance::RValue(synthesise_class_declaration( + class, + None, + expecting, + environment, + checking_data, + )), Expression::Marker { marker_id: _, position: _ } => { crate::utilities::notify!("Marker expression found"); return TypeId::ERROR_TYPE; @@ -726,7 +870,7 @@ pub(super) fn synthesise_expression( SpecialOperators::AsCast { value, rhs } => { let to_cast = synthesise_expression(value, environment, checking_data, expecting); - if checking_data.options.allow_cast { + if checking_data.options.allow_type_casts { match rhs { TypeOrConst::Type(type_annotation) => { let cast_to = synthesise_type_annotation( @@ -737,7 +881,7 @@ pub(super) fn synthesise_expression( // TODO let as_cast = - features::as_cast(to_cast, cast_to, &mut checking_data.types); + features::tsc::as_cast(to_cast, cast_to, &mut checking_data.types); match as_cast { Ok(result) => return result, @@ -780,33 +924,40 @@ pub(super) fn synthesise_expression( let value = synthesise_expression(value, environment, checking_data, satisfying); - checking_data.check_satisfies( + features::tsc::check_satisfies( value, satisfying, ASTNode::get_position(expression).with_source(environment.get_source()), environment, + checking_data, ); return value; } SpecialOperators::In { lhs, rhs } => { - let lhs = match lhs { - parser::expressions::InExpressionLHS::PrivateProperty(_) => { - checking_data.raise_unimplemented_error( - "in on private", - position.with_source(environment.get_source()), - ); - return TypeId::ERROR_TYPE; + let (publicity, key) = match lhs { + parser::expressions::InExpressionLHS::PrivateProperty(key) => { + (Publicity::Private, PropertyKey::String(Cow::Borrowed(key))) } parser::expressions::InExpressionLHS::Expression(lhs) => { - synthesise_expression(lhs, environment, checking_data, TypeId::ANY_TYPE) + let key = synthesise_expression( + lhs, + environment, + checking_data, + TypeId::ANY_TYPE, + ); + (Publicity::Public, PropertyKey::from_type(key, &checking_data.types)) } }; + let rhs = synthesise_expression(rhs, environment, checking_data, TypeId::ANY_TYPE); - let result = environment - .property_in(rhs, &PropertyKey::from_type(lhs, &checking_data.types)); - Instance::RValue(if result { TypeId::TRUE } else { TypeId::FALSE }) + Instance::RValue(in_operator( + (publicity, &key), + rhs, + environment, + &mut checking_data.types, + )) } SpecialOperators::InstanceOf { lhs, rhs } => { let lhs = synthesise_expression(lhs, environment, checking_data, expecting); @@ -818,10 +969,19 @@ pub(super) fn synthesise_expression( &mut checking_data.types, )) } - SpecialOperators::NonNullAssertion(_) => todo!(), + SpecialOperators::NonNullAssertion(on) => { + let lhs = synthesise_expression(on, environment, checking_data, expecting); + Instance::RValue( + features::tsc::non_null_assertion(lhs, &mut checking_data.types).unwrap(), + ) + } SpecialOperators::Is { value: _, type_annotation: _ } => { // Special non-standard - todo!() + checking_data.raise_unimplemented_error( + "is expression", + position.with_source(environment.get_source()), + ); + return TypeId::ERROR_TYPE; } }, Expression::ImportMeta(_) => { @@ -850,8 +1010,7 @@ pub(super) fn synthesise_expression( fn operator_to_assignment_kind( operator: parser::expressions::operators::BinaryAssignmentOperator, -) -> crate::features::assignments::AssignmentKind { - use crate::features::assignments::AssignmentKind; +) -> AssignmentKind { use parser::expressions::operators::BinaryAssignmentOperator; match operator { @@ -913,9 +1072,9 @@ fn call_function( environment: &mut Environment, checking_data: &mut CheckingData, call_site: parser::Span, - expected: TypeId, + expecting: TypeId, ) -> (TypeId, Option) { - let generic_type_arguments = type_arguments.as_ref().map(|type_arguments| { + let call_site_type_arguments = type_arguments.as_ref().map(|type_arguments| { type_arguments .iter() .map(|generic_type_argument| { @@ -927,6 +1086,11 @@ fn call_function( .collect::>() }); + let comment = parser::Expression::VariableReference( + String::from_str("undefined").unwrap(), + source_map::BaseSpan::NULL, + ); + let arguments = arguments .map(|arguments| { arguments @@ -938,7 +1102,9 @@ fn call_function( FunctionArgument::Standard(e) => { UnsynthesisedArgument { spread: false, expression: e } } - FunctionArgument::Comment { .. } => todo!(), + FunctionArgument::Comment { .. } => { + UnsynthesisedArgument { spread: false, expression: &comment } + } }) .collect::>() }) @@ -946,15 +1112,16 @@ fn call_function( crate::types::calling::call_type_handle_errors( function_type_id, + call_site_type_arguments, &arguments, CallingInput { called_with_new, call_site: call_site.with_source(environment.get_source()), - call_site_type_arguments: generic_type_arguments, + max_inline: checking_data.options.max_inline_count, }, environment, checking_data, - expected, + expecting, ) } @@ -963,11 +1130,16 @@ pub(super) fn synthesise_object_literal( checking_data: &mut CheckingData, environment: &mut Environment, position: SpanWithSource, - expected: TypeId, + expecting: TypeId, ) -> TypeId { let mut object_builder = ObjectBuilder::new(None, &mut checking_data.types, position, &mut environment.info); + // { + // let ty = print_type(expecting, &checking_data.types, environment, true); + // crate::utilities::notify!("expecting in obj={}", ty); + // } + for member in members { let member_position = member.get_position().with_source(environment.get_source()); match member { @@ -975,7 +1147,7 @@ pub(super) fn synthesise_object_literal( continue; } ObjectLiteralMember::Spread(spread, pos) => { - let spread = synthesise_expression(spread, environment, checking_data, expected); + let spread = synthesise_expression(spread, environment, checking_data, expecting); // TODO use what about string, what about enumerable ... @@ -985,16 +1157,18 @@ pub(super) fn synthesise_object_literal( spread, &checking_data.types, environment, + true, + TypeId::ANY_TYPE, ); for (_, key, value) in get_properties_on_type { - // TODO evaluate getters & check enumerability + // TODO evaluate getters & check whether enumerable object_builder.append( - environment, Publicity::Public, key, value, pos.with_source(environment.get_source()), + &mut environment.info, ); } } @@ -1008,11 +1182,15 @@ pub(super) fn synthesise_object_literal( *truthy_result, &checking_data.types, environment, + true, + TypeId::ANY_TYPE, ); let otherwise_properties = get_properties_on_single_type( *otherwise_result, &checking_data.types, environment, + true, + TypeId::ANY_TYPE, ); crate::utilities::notify!( "Here {:?} {:?} {:?} {:?}", @@ -1052,25 +1230,26 @@ pub(super) fn synthesise_object_literal( }; object_builder.append( - environment, Publicity::Public, key.clone(), PropertyValue::Value(value), position, + &mut environment.info, ); } } else { - crate::utilities::notify!("Here"); + // crate::utilities::notify!("Here in conditional spread"); + for (_, key, value) in truthy_properties { object_builder.append( - environment, Publicity::Public, key, PropertyValue::ConditionallyExists { - on: *condition, + condition: *condition, truthy: Box::new(value), }, member_position, + &mut environment.info, ); } let negation = @@ -1078,14 +1257,14 @@ pub(super) fn synthesise_object_literal( for (_, key, value) in otherwise_properties { object_builder.append( - environment, Publicity::Public, key, PropertyValue::ConditionallyExists { - on: negation, + condition: negation, truthy: Box::new(value), }, member_position, + &mut environment.info, ); } } @@ -1132,11 +1311,11 @@ pub(super) fn synthesise_object_literal( }; object_builder.append( - environment, Publicity::Public, key, crate::types::properties::PropertyValue::Value(value), member_position, + &mut environment.info, ); } ObjectLiteralMember::Property { key, value, position, .. } => { @@ -1150,21 +1329,22 @@ pub(super) fn synthesise_object_literal( let position_with_source = position.with_source(environment.get_source()); let maybe_property_expecting = get_property_unbound( - (expected, None), + (expecting, None), (Publicity::Public, &key, None), + false, environment, &checking_data.types, ); - if expected != TypeId::ANY_TYPE - && expected != TypeId::OBJECT_TYPE + if expecting != TypeId::ANY_TYPE + && expecting != TypeId::OBJECT_TYPE && maybe_property_expecting.is_err() { checking_data.diagnostics_container.add_warning( TypeCheckWarning::ExcessProperty { position: position_with_source, expected_type: TypeStringRepresentation::from_type_id( - expected, + expecting, environment, &checking_data.types, checking_data.options.debug_types, @@ -1182,16 +1362,27 @@ pub(super) fn synthesise_object_literal( // TODO needs improvement let property_expecting = maybe_property_expecting .ok() - .and_then( - |l| if let Logical::Pure(l) = l { Some(l.as_get_type()) } else { None }, - ) + .and_then(|l| { + if let LogicalOrValid::Logical(Logical::Pure(l)) = l { + Some(l.as_get_type(&checking_data.types)) + } else { + crate::utilities::notify!("TODO expecting {:?}", l); + None + } + }) .unwrap_or(TypeId::ANY_TYPE); let value = synthesise_expression(value, environment, checking_data, property_expecting); let value = crate::types::properties::PropertyValue::Value(value); - object_builder.append(environment, Publicity::Public, key, value, member_position); + object_builder.append( + Publicity::Public, + key, + value, + member_position, + &mut environment.info, + ); // let property_name: PropertyName<'static> = property_key.into(); @@ -1222,38 +1413,47 @@ pub(super) fn synthesise_object_literal( // TODO needs improvement let property_expecting = get_property_unbound( - (expected, None), + (expecting, None), (Publicity::Public, &key, None), + false, environment, &checking_data.types, ) .ok() - .and_then(|l| if let Logical::Pure(l) = l { Some(l.as_get_type()) } else { None }) + .and_then(|l| { + if let LogicalOrValid::Logical(Logical::Pure(l)) = l { + Some(l.as_get_type(&checking_data.types)) + } else { + crate::utilities::notify!("TODO {:?}", l); + None + } + }) .unwrap_or(TypeId::ANY_TYPE); let behavior = crate::features::functions::FunctionRegisterBehavior::ObjectMethod { is_async: method.header.is_async(), is_generator: method.header.is_generator(), expecting: property_expecting, + name: key.into_name_type(&mut checking_data.types), }; let function = synthesise_function(method, behavior, environment, checking_data); let kind = match &method.header { - MethodHeader::Get => GetterSetter::Getter, - MethodHeader::Set => GetterSetter::Setter, - MethodHeader::Regular { .. } => GetterSetter::None, + MethodHeader::Get => Some(GetterSetter::Getter), + MethodHeader::Set => Some(GetterSetter::Setter), + MethodHeader::Regular { .. } => None, }; let property = function_to_property(kind, function, &mut checking_data.types, false); object_builder.append( - environment, Publicity::Public, key, property, member_position, + &mut environment.info, ); } } diff --git a/checker/src/synthesis/extensions/jsx.rs b/checker/src/synthesis/extensions/jsx.rs index 9770d17a..94bd2d8b 100644 --- a/checker/src/synthesis/extensions/jsx.rs +++ b/checker/src/synthesis/extensions/jsx.rs @@ -10,9 +10,11 @@ use crate::{ features::objects::ObjectBuilder, synthesis::expressions::synthesise_expression, types::{ - calling::{call_type, CallingInput}, + calling::{ + application_result_to_return_type, Callable, CalledWithNew, CallingContext, + CallingInput, SynthesisedArgument, + }, properties::PropertyKey, - SynthesisedArgument, }, CheckingData, Constant, Environment, TypeId, }; @@ -24,7 +26,13 @@ pub(crate) fn synthesise_jsx_root( ) -> TypeId { match jsx_root { JSXRoot::Element(element) => synthesise_jsx_element(element, environment, checking_data), - JSXRoot::Fragment(_) => todo!(), + JSXRoot::Fragment(fragment) => { + checking_data.raise_unimplemented_error( + "JSX fragment", + fragment.get_position().with_source(environment.get_source()), + ); + TypeId::ERROR_TYPE + } } } @@ -53,11 +61,11 @@ pub(crate) fn synthesise_jsx_element( let (name, attribute_value) = synthesise_attribute(attribute, environment, checking_data); let attribute_position = attribute.get_position().with_source(environment.get_source()); attributes_object.append( - environment, crate::types::properties::Publicity::Public, name, crate::PropertyValue::Value(attribute_value), attribute_position, + &mut environment.info, ); // let constraint = environment @@ -154,11 +162,11 @@ pub(crate) fn synthesise_jsx_element( let child_position = child.get_position().with_source(environment.get_source()); let child = synthesise_jsx_child(child, environment, checking_data); synthesised_child_nodes.append( - environment, crate::types::properties::Publicity::Public, property, crate::PropertyValue::Value(child), child_position, + &mut environment.info, ); // TODO spread ?? @@ -173,11 +181,11 @@ pub(crate) fn synthesise_jsx_element( // TODO: Should there be a position here? synthesised_child_nodes.append( - environment, crate::types::properties::Publicity::Public, crate::types::properties::PropertyKey::String("length".into()), crate::types::properties::PropertyValue::Value(length), element.get_position().with_source(environment.get_source()), + &mut environment.info, ); } // } @@ -189,11 +197,15 @@ pub(crate) fn synthesise_jsx_element( let position = element.get_position().with_source(environment.get_source()); let jsx_function = - match environment.get_variable_handle_error(JSX_NAME, position, checking_data) { - Ok(ty) => ty.1, - Err(_) => { - todo!() - } + if let Ok(ty) = environment.get_variable_handle_error(JSX_NAME, position, checking_data) { + ty.1 + } else { + checking_data.diagnostics_container.add_error(TypeCheckError::CouldNotFindVariable { + variable: JSX_NAME, + possibles: Vec::default(), + position, + }); + TypeId::ERROR_TYPE }; let tag_name_argument = SynthesisedArgument { @@ -218,31 +230,26 @@ pub(crate) fn synthesise_jsx_element( let mut check_things = CheckThings { debug_types: checking_data.options.debug_types }; let calling_input = CallingInput { - called_with_new: crate::types::calling::CalledWithNew::None, + called_with_new: CalledWithNew::None, call_site: position, - call_site_type_arguments: None, + max_inline: checking_data.options.max_inline_count, }; - match call_type( - jsx_function, + + let mut diagnostics = Default::default(); + let result = Callable::Type(jsx_function).call( args, - &calling_input, + calling_input, environment, - &mut check_things, + (&mut check_things, &mut diagnostics), &mut checking_data.types, - ) { - Ok(res) => crate::types::calling::application_result_to_return_type( - res.result, - environment, - &mut checking_data.types, - ), - Err(error) => { - error.errors.into_iter().for_each(|error| { - checking_data - .diagnostics_container - .add_error(TypeCheckError::JSXCallingError(error)); - }); - error.returned_type + ); + diagnostics.append_to(CallingContext::JSX, &mut checking_data.diagnostics_container); + + match result { + Ok(res) => { + application_result_to_return_type(res.result, environment, &mut checking_data.types) } + Err(error) => error.returned_type, } // else { @@ -413,13 +420,20 @@ fn synthesise_jsx_child( JSXNode::Element(element) => synthesise_jsx_element(element, environment, checking_data), JSXNode::InterpolatedExpression(expression, _expression_position) => { match &**expression { - parser::ast::FunctionArgument::Spread(_, _) => todo!(), + parser::ast::FunctionArgument::Spread(_, pos) => { + checking_data.raise_unimplemented_error( + "spread JSX child", + pos.with_source(environment.get_source()), + ); + TypeId::UNDEFINED_TYPE + } parser::ast::FunctionArgument::Standard(expression) => { crate::utilities::notify!("Cast JSX interpolated value?"); synthesise_expression(expression, environment, checking_data, TypeId::ANY_TYPE) } parser::ast::FunctionArgument::Comment { .. } => { - todo!() + // TODO? + TypeId::UNDEFINED_TYPE } } // function intoNode(data) { @@ -483,8 +497,20 @@ fn synthesise_attribute( (name, synthesise_expression(expression, environment, checking_data, TypeId::ANY_TYPE)) } JSXAttribute::BooleanAttribute(name, _) => (name, TypeId::TRUE), - JSXAttribute::Spread(_, _) => todo!(), - JSXAttribute::Shorthand(_) => todo!(), + JSXAttribute::Spread(_, pos) => { + checking_data.raise_unimplemented_error( + "spread JSX attribute", + pos.with_source(environment.get_source()), + ); + return (PropertyKey::String(Cow::Borrowed("err")), TypeId::ERROR_TYPE); + } + JSXAttribute::Shorthand(expr) => { + checking_data.raise_unimplemented_error( + "shorthand JSX attribute", + expr.get_position().with_source(environment.get_source()), + ); + return (PropertyKey::String(Cow::Borrowed("err")), TypeId::ERROR_TYPE); + } }; (PropertyKey::String(Cow::Owned(key.clone())), value) diff --git a/checker/src/synthesis/functions.rs b/checker/src/synthesis/functions.rs index 6ae05d48..b0e47b11 100644 --- a/checker/src/synthesis/functions.rs +++ b/checker/src/synthesis/functions.rs @@ -10,12 +10,11 @@ use parser::{ use crate::{ context::{Context, ContextType, Scope, VariableRegisterArguments}, - features::functions::{ - synthesise_function_default_value, FunctionBehavior, ReturnType, SynthesisableFunction, - }, + features::functions::{synthesise_function_default_value, ReturnType, SynthesisableFunction}, types::{ functions::{ - FunctionType, SynthesisedParameter, SynthesisedParameters, SynthesisedRestParameter, + FunctionBehavior, FunctionType, SynthesisedParameter, SynthesisedParameters, + SynthesisedRestParameter, }, generics::GenericTypeParameters, PartiallyAppliedGenerics, Type, TypeId, @@ -172,10 +171,9 @@ impl SynthesisableFunctionBody for ExpressionOrBlock { ) { match self { ExpressionOrBlock::Expression(expression) => { - environment.return_value( - &crate::context::environment::Returnable::ArrowFunctionBody(&**expression), - checking_data, - ); + let arrow_function_body = + crate::context::environment::Returnable::ArrowFunctionBody(&**expression); + environment.return_value(&arrow_function_body, checking_data); } ExpressionOrBlock::Block(block) => { block.synthesise_function_body(environment, checking_data); @@ -257,6 +255,8 @@ pub(super) fn synthesise_type_annotation_function_parameters { let out = synthesise_function_default_value( @@ -413,6 +408,8 @@ fn synthesise_function_parameters< constant: false, space: Some(parameter_constraint), initial_value: Some(variable_ty), + // :<) + allow_reregistration: true, }, ); @@ -457,7 +454,7 @@ fn synthesise_function_parameters< let variable_ty = checking_data.types.new_function_parameter(parameter_constraint); - environment.info.object_constraints.insert(variable_ty, parameter_constraint); + // environment.info.object_constraints.insert(variable_ty, parameter_constraint); register_variable( &rest_parameter.name, @@ -468,6 +465,8 @@ fn synthesise_function_parameters< constant: false, space: Some(parameter_constraint), initial_value: Some(variable_ty), + // :<) + allow_reregistration: true, }, ); @@ -532,9 +531,18 @@ pub(super) fn variable_field_to_string(param: &VariableField) -> String { parser::PropertyKey::Identifier(ident, _, _) => { buf.push_str(ident); } - parser::PropertyKey::StringLiteral(_, _, _) => todo!(), - parser::PropertyKey::NumberLiteral(_, _) => todo!(), - parser::PropertyKey::Computed(_, _) => todo!(), + parser::PropertyKey::StringLiteral(s, _, _) => { + buf.push('"'); + buf.push_str(s); + buf.push('"'); + } + parser::PropertyKey::NumberLiteral(n, _) => { + buf.push_str(n.clone().as_js_string().as_str()); + } + parser::PropertyKey::Computed(_, _) => { + // TODO maybe could do better here? + buf.push_str("[...]"); + } } buf.push_str(": "); buf.push_str(&variable_field_to_string(name.get_ast_ref())); @@ -712,6 +720,7 @@ pub(super) fn synthesise_shape( /// TODO WIP /// TODO also check generics? +#[allow(clippy::too_many_arguments)] pub(super) fn build_overloaded_function( id: FunctionId, behavior: FunctionBehavior, @@ -720,6 +729,7 @@ pub(super) fn build_overloaded_function( environment: &Environment, types: &mut crate::types::TypeStore, diagnostics: &mut crate::DiagnosticsContainer, + effect: crate::types::FunctionEffect, ) -> TypeId { use crate::diagnostics::{TypeCheckError, TypeStringRepresentation}; use crate::types::subtyping::{type_is_subtype, State, SubTypeResult}; @@ -733,7 +743,7 @@ pub(super) fn build_overloaded_function( type_parameters: actual.0, parameters: actual.1, return_type: actual.2.map_or(TypeId::ANY_TYPE, |rt| rt.0), - effect: crate::types::FunctionEffect::Unknown, + effect, }; let actual_func = types.new_hoisted_function_type(as_function); @@ -820,6 +830,7 @@ pub(super) fn build_overloaded_function( type_parameters: overload.0, parameters: overload.1, return_type: overload.2.map_or(TypeId::ANY_TYPE, |rt| rt.0), + // existing is base? effect: crate::types::FunctionEffect::Unknown, }; diff --git a/checker/src/synthesis/hoisting.rs b/checker/src/synthesis/hoisting.rs index 18d83c7a..e223aac2 100644 --- a/checker/src/synthesis/hoisting.rs +++ b/checker/src/synthesis/hoisting.rs @@ -3,11 +3,12 @@ use std::iter; use parser::{ declarations::{export::Exportable, DeclareVariableDeclaration, ExportDeclaration}, ASTNode, Declaration, Decorated, ExpressionOrStatementPosition, Statement, - StatementOrDeclaration, VariableIdentifier, + StatementOrDeclaration, StatementPosition, VariableIdentifier, }; use crate::{ - context::{Environment, VariableRegisterArguments}, + context::{environment::DeclareInterfaceResult, Environment, VariableRegisterArguments}, + diagnostics::TypeCheckError, features::{ functions::{ synthesise_declare_statement_function, synthesise_hoisted_statement_function, @@ -16,74 +17,176 @@ use crate::{ modules::{import_items, ImportKind, NamePair}, variables::VariableMutability, }, - synthesis::{ - classes::register_statement_class_with_members, - type_annotations::get_annotation_from_declaration, - }, + synthesis::type_annotations::get_annotation_from_declaration, CheckingData, ReadFromFS, TypeId, }; -use super::{variables::register_variable, EznoParser}; +use super::{ + definitions::get_internal_function_effect_from_decorators, variables::register_variable, + EznoParser, +}; pub(crate) fn hoist_statements( items: &[StatementOrDeclaration], environment: &mut Environment, checking_data: &mut CheckingData, ) { - // First stage: imports and types + // First stage: imports (both types and names) and types for item in items { if let StatementOrDeclaration::Declaration(declaration) = item { match declaration { - parser::Declaration::DeclareVariable(_) - | parser::Declaration::Variable(_) - | parser::Declaration::Function(_) => {} - parser::Declaration::Enum(r#enum) => checking_data.raise_unimplemented_error( + Declaration::Enum(r#enum) => checking_data.raise_unimplemented_error( "enum", r#enum.on.position.with_source(environment.get_source()), ), - parser::Declaration::Namespace(ns) => checking_data.raise_unimplemented_error( + Declaration::Namespace(ns) => checking_data.raise_unimplemented_error( "namespace", ns.position.with_source(environment.get_source()), ), - parser::Declaration::Interface(interface) => { - let ty = environment.register_interface( - interface.on.name.as_option_str().unwrap_or_default(), - interface.on.is_nominal, - interface.on.type_parameters.as_deref(), - interface.on.extends.as_deref(), - interface.on.position.with_source(environment.get_source()), - checking_data, + Declaration::Interface(Decorated { on: interface, .. }) + | Declaration::Export(Decorated { + on: + ExportDeclaration::Variable { + exported: Exportable::Interface(interface), + position: _, + }, + .. + }) => { + let result = environment.declare_interface::( + interface.name.as_option_str().unwrap_or_default(), + interface.type_parameters.as_deref(), + interface.extends.as_deref(), + &mut checking_data.types, ); - checking_data - .local_type_mappings - .types_to_types - .push(interface.on.get_position(), ty); + + if let Ok(DeclareInterfaceResult::Merging { ty: _, in_same_context: true }) = + &result + { + checking_data.diagnostics_container.add_warning( + crate::diagnostics::TypeCheckWarning::MergingInterfaceInSameContext { + position: interface.position.with_source(environment.get_source()), + }, + ); + } + + if let Ok( + DeclareInterfaceResult::Merging { ty, in_same_context: _ } + | DeclareInterfaceResult::New(ty), + ) = result + { + checking_data + .local_type_mappings + .types_to_types + .push(interface.get_position(), ty); + + if let Declaration::Export(_) = declaration { + if let crate::Scope::Module { ref mut exported, .. } = + environment.context_type.scope + { + exported.named_types.insert( + interface.name.as_option_str().unwrap_or_default().to_owned(), + ty, + ); + } + } + } else { + checking_data.diagnostics_container.add_error( + crate::diagnostics::TypeCheckError::TypeAlreadyDeclared { + name: interface.name.as_option_str().unwrap_or_default().to_owned(), + position: interface + .get_position() + .with_source(environment.get_source()), + }, + ); + } } - parser::Declaration::Class(class) => { - let ty = environment.register_class::( - class.on.name.as_option_str().unwrap_or_default(), - class.on.type_parameters.as_deref(), - class.on.extends.as_deref(), + Declaration::Class(Decorated { on: class, .. }) + | Declaration::Export(Decorated { + on: + ExportDeclaration::Variable { exported: Exportable::Class(class), position: _ }, + .. + }) => { + let result = environment.declare_class::( + class.name.as_option_str().unwrap_or_default(), + class.type_parameters.as_deref(), + // class.extends.as_deref(), &mut checking_data.types, ); - checking_data - .local_type_mappings - .types_to_types - .push(class.on.get_position(), ty); + + if let Ok(ty) = result { + checking_data + .local_type_mappings + .types_to_types + .push(class.name.identifier.get_position(), ty); + + if let Declaration::Export(_) = declaration { + if let crate::Scope::Module { ref mut exported, .. } = + environment.context_type.scope + { + exported.named_types.insert( + class.name.as_option_str().unwrap_or_default().to_owned(), + ty, + ); + } + } + } else { + checking_data.diagnostics_container.add_error( + crate::diagnostics::TypeCheckError::TypeAlreadyDeclared { + name: class.name.as_option_str().unwrap_or_default().to_owned(), + position: class + .get_position() + .with_source(environment.get_source()), + }, + ); + } } - parser::Declaration::TypeAlias(alias) => { - let ty = environment.new_alias( + Declaration::TypeAlias(alias) + | Declaration::Export(Decorated { + on: + ExportDeclaration::Variable { + exported: Exportable::TypeAlias(alias), + position: _, + }, + .. + }) => { + let result = environment.declare_alias::( alias.name.as_option_str().unwrap_or_default(), alias.parameters.as_deref(), - &alias.references, + // &alias.references, alias.get_position(), - checking_data, + &mut checking_data.types, ); - checking_data.local_type_mappings.types_to_types.push(alias.get_position(), ty); + if let Ok(ty) = result { + checking_data + .local_type_mappings + .types_to_types + .push(alias.get_position(), ty); + + if let Declaration::Export(_) = declaration { + if let crate::Scope::Module { ref mut exported, .. } = + environment.context_type.scope + { + exported.named_types.insert( + alias.name.as_option_str().unwrap_or_default().to_owned(), + ty, + ); + } + } + } else { + checking_data.diagnostics_container.add_error( + crate::diagnostics::TypeCheckError::TypeAlreadyDeclared { + name: alias.name.as_option_str().unwrap_or_default().to_owned(), + position: alias + .get_position() + .with_source(environment.get_source()), + }, + ); + } } - parser::Declaration::Import(import) => { + Declaration::Import(import) => { let items = match &import.items { parser::declarations::import::ImportedItems::Parts(parts) => { + crate::utilities::notify!("{:?}", parts); crate::features::modules::ImportKind::Parts( parts.iter().flatten().filter_map(import_part_to_name_pair), ) @@ -95,7 +198,10 @@ pub(crate) fn hoist_statements( position: *position, } } - VariableIdentifier::Marker(_, _) => todo!(), + VariableIdentifier::Marker(_, _) => { + // TODO I think this is best + continue; + } }, }; let default_import = import.default.as_ref().and_then(|default_identifier| { @@ -117,116 +223,63 @@ pub(crate) fn hoist_statements( import.is_type_annotation_import_only, ); } - parser::Declaration::Export(export) => { - if let ExportDeclaration::Variable { exported, position } = &export.on { - // Imports & types - match exported { - Exportable::ImportAll { r#as, from } => { - let kind = match r#as { - Some(VariableIdentifier::Standard(name, position)) => { - ImportKind::All { under: name, position: *position } - } - Some(VariableIdentifier::Marker(_, _)) => todo!(), - None => ImportKind::Everything, - }; - - import_items::, _, _>( - environment, - from.get_path().unwrap(), - *position, - None, - kind, - checking_data, - true, - // TODO - false, - ); - } - Exportable::ImportParts { - parts, from, type_definitions_only, .. - } => { - let parts = parts.iter().filter_map(export_part_to_name_pair); + Declaration::Export(Decorated { + on: + ExportDeclaration::Variable { + exported: Exportable::ImportAll { r#as, from }, + position, + }, + .. + }) => { + let kind = match r#as { + Some(VariableIdentifier::Standard(name, position)) => { + ImportKind::All { under: name, position: *position } + } + Some(VariableIdentifier::Marker(_, _)) => { + // TODO + continue; + } + None => ImportKind::Everything, + }; - import_items( - environment, - from.get_path().unwrap(), - *position, - None, - crate::features::modules::ImportKind::Parts(parts), - checking_data, - true, - *type_definitions_only, - ); - } - Exportable::Interface(interface) => { - let ty = environment.register_interface( - interface.name.as_option_str().unwrap_or_default(), - interface.is_nominal, - interface.type_parameters.as_deref(), - interface.extends.as_deref(), - interface.position.with_source(environment.get_source()), - checking_data, - ); - checking_data - .local_type_mappings - .types_to_types - .push(interface.get_position(), ty); - - if let crate::Scope::Module { ref mut exported, .. } = - environment.context_type.scope - { - let name = interface - .name - .as_option_str() - .unwrap_or_default() - .to_owned(); - exported.named_types.push((name, ty)); - } - } - Exportable::Class(class) => { - let ty = environment.register_class::( - class.name.as_option_str().unwrap_or_default(), - class.type_parameters.as_deref(), - class.extends.as_deref(), - &mut checking_data.types, - ); - checking_data - .local_type_mappings - .types_to_types - .push(class.get_position(), ty); - - if let crate::Scope::Module { ref mut exported, .. } = - environment.context_type.scope - { - exported.named_types.push(( - class.name.as_option_str().unwrap_or_default().to_owned(), - ty, - )); - } - } - Exportable::TypeAlias(alias) => { - let ty = environment.new_alias::<_, EznoParser>( - alias.name.as_option_str().unwrap_or_default(), - alias.parameters.as_deref(), - &alias.references, - alias.get_position(), - checking_data, - ); + import_items::, _, _>( + environment, + from.get_path().unwrap(), + *position, + None, + kind, + checking_data, + true, + // TODO + false, + ); + } + Declaration::Export(Decorated { + on: + ExportDeclaration::Variable { + exported: + Exportable::ImportParts { parts, from, type_definitions_only, .. }, + position, + }, + .. + }) => { + let parts = parts.iter().filter_map(export_part_to_name_pair); - if let crate::Scope::Module { ref mut exported, .. } = - environment.context_type.scope - { - exported.named_types.push(( - alias.name.as_option_str().unwrap_or_default().to_owned(), - ty, - )); - } - } - // Other exported things are skipped - _ => {} - } - } + import_items( + environment, + from.get_path().unwrap(), + *position, + None, + crate::features::modules::ImportKind::Parts(parts), + checking_data, + true, + *type_definitions_only, + ); } + Declaration::DeclareVariable(_) + | Declaration::Variable(_) + | Declaration::Function(_) + | Declaration::Export(..) => {} } } } @@ -238,7 +291,6 @@ pub(crate) fn hoist_statements( StatementOrDeclaration::Statement(stmt) => { if let Statement::VarVariable(stmt) = stmt { for declaration in &stmt.declarations { - crate::utilities::notify!("declaration.name {:?}", declaration.name); let constraint = get_annotation_from_declaration( declaration, environment, @@ -253,28 +305,245 @@ pub(crate) fn hoist_statements( space: constraint, // Important! initial_value: Some(TypeId::UNDEFINED_TYPE), + // `var` declarations can be redeclared! + allow_reregistration: true, }, ); } } } StatementOrDeclaration::Declaration(dec) => match dec { - parser::Declaration::Namespace(ns) => checking_data.raise_unimplemented_error( + Declaration::Namespace(ns) => checking_data.raise_unimplemented_error( "namespace", ns.position.with_source(environment.get_source()), ), - parser::Declaration::Variable(declaration) => { + Declaration::Variable(declaration) + | Declaration::Export(Decorated { + on: + ExportDeclaration::Variable { + exported: Exportable::Variable(declaration), + position: _, + }, + .. + }) => { hoist_variable_declaration(declaration, environment, checking_data); } - parser::Declaration::Function(func) => { - if let Some(VariableIdentifier::Standard(name, ..)) = - func.on.name.as_option_variable_identifier() + Declaration::Class(Decorated { on: class, .. }) + | Declaration::Export(Decorated { + on: + ExportDeclaration::Variable { exported: Exportable::Class(class), position: _ }, + .. + }) => { + let name_position = + class.name.identifier.get_position().with_source(environment.get_source()); + + let ty = *checking_data + .local_type_mappings + .types_to_types + .get(name_position.start) + .unwrap(); + + // Lift members + super::classes::register_statement_class_with_members( + ty, + class, + environment, + checking_data, + ); + + let name = StatementPosition::as_option_str(&class.name).unwrap_or_default(); + let argument = VariableRegisterArguments { + // TODO functions are constant references + constant: true, + space: Some(ty), + initial_value: None, + allow_reregistration: false, + }; + + environment.register_variable_handle_error( + name, + argument, + name_position, + &mut checking_data.diagnostics_container, + &mut checking_data.local_type_mappings, + checking_data.options.record_all_assignments_and_reads, + ); + } + Declaration::Export(Decorated { + on: ExportDeclaration::DefaultFunction { position, .. }, + .. + }) => { + // TODO under definition file + checking_data.diagnostics_container.add_error( + TypeCheckError::FunctionWithoutBodyNotAllowedHere { + position: position.with_source(environment.get_source()), + }, + ); + continue; + } + Declaration::TypeAlias(alias) + | Declaration::Export(Decorated { + on: + ExportDeclaration::Variable { + exported: Exportable::TypeAlias(alias), + position: _, + }, + .. + }) => { + let ty = checking_data + .local_type_mappings + .types_to_types + .get(alias.get_position().start) + .unwrap(); + + environment.register_alias( + *ty, + alias.parameters.as_deref(), + &alias.references, + alias.get_position(), + checking_data, + ); + } + Declaration::Interface(Decorated { on: interface, .. }) + | Declaration::Export(Decorated { + on: + ExportDeclaration::Variable { + exported: Exportable::Interface(interface), + position: _, + }, + .. + }) => { + use crate::types::{PolyNature, Type}; + use crate::ASTImplementation; + use crate::Scope; + + let ty = *checking_data + .local_type_mappings + .types_to_types + .get(interface.get_position().start) + .unwrap(); + + if let Type::Interface { parameters, .. } = + checking_data.types.get_type_by_id(ty) + { + if let Some(parameters) = parameters { + let mut sub_environment = + environment.new_lexical_environment(Scope::TypeAlias); + let parameters = parameters.clone(); + for parameter in parameters.iter().copied() { + let Type::RootPolyType(PolyNature::StructureGeneric { + name, .. + }) = checking_data.types.get_type_by_id(parameter) + else { + unreachable!( + "{:?}", + checking_data.types.get_type_by_id(parameter) + ) + }; + sub_environment.named_types.insert(name.clone(), parameter); + } + for (parameter, ast_parameter) in parameters + .into_iter() + .zip(interface.type_parameters.as_ref().unwrap()) + { + // TODO + if let Some(ref extends) = ast_parameter.extends { + let new_to = EznoParser::synthesise_type_annotation( + extends, + &mut sub_environment, + checking_data, + ); + checking_data.types.update_generic_extends(parameter, new_to); + } + } + + // TODO cyclic checking + if let Some(ref extends) = interface.extends { + let mut iter = extends.iter(); + let mut extends = EznoParser::synthesise_type_annotation( + iter.next().unwrap(), + &mut sub_environment, + checking_data, + ); + for annotation in iter { + let new = EznoParser::synthesise_type_annotation( + annotation, + &mut sub_environment, + checking_data, + ); + extends = + checking_data.types.new_and_type(extends, new).unwrap(); + } + checking_data.types.set_extends_on_interface(ty, extends); + } + + super::interfaces::synthesise_signatures( + interface.type_parameters.as_deref(), + interface.extends.as_deref(), + &interface.members, + super::interfaces::OnToType(ty), + &mut sub_environment, + checking_data, + ); + + // TODO temp as object types use the same environment.properties representation + { + let crate::LocalInformation { + current_properties, prototypes, .. + } = sub_environment.info; + environment.info.current_properties.extend(current_properties); + environment.info.prototypes.extend(prototypes); + } + } else { + // TODO cyclic checking + if let Some(ref extends) = interface.extends { + let mut iter = extends.iter(); + let mut extends = EznoParser::synthesise_type_annotation( + iter.next().unwrap(), + environment, + checking_data, + ); + for annotation in iter { + let new = EznoParser::synthesise_type_annotation( + annotation, + environment, + checking_data, + ); + extends = + checking_data.types.new_and_type(extends, new).unwrap(); + } + checking_data.types.set_extends_on_interface(ty, extends); + } + + super::interfaces::synthesise_signatures( + interface.type_parameters.as_deref(), + interface.extends.as_deref(), + &interface.members, + super::interfaces::OnToType(ty), + environment, + checking_data, + ); + } + } + } + Declaration::Function(Decorated { on: function, decorators, .. }) + | Declaration::Export(Decorated { + on: + ExportDeclaration::Variable { + exported: Exportable::Function(function), + position: _, + }, + decorators, + .. + }) => { + if let Some(VariableIdentifier::Standard(name, name_position)) = + function.name.as_option_variable_identifier() { // Copied from classes hoisting - let (overloads, actual) = if func.on.body.0.is_none() { + let (overloads, actual) = if function.body.0.is_none() { let mut overloads = Vec::new(); let shape = super::functions::synthesise_shape( - &func.on, + function, environment, checking_data, ); @@ -282,19 +551,16 @@ pub(crate) fn hoist_statements( // Read declarations until while let Some(overload_declaration) = second_items.next_if(|t| { - matches!( - t, - StatementOrDeclaration::Declaration( parser::Declaration::Function(func)) - if func.on.name.as_option_str().is_some_and(|n| n == name) && !func.on.has_body()) + matches!( t, StatementOrDeclaration::Declaration( Declaration::Function(Decorated { on: func, .. })) if func.name.as_option_str().is_some_and(|n| n == name) && !func.has_body()) }) { let parser::StatementOrDeclaration::Declaration( - parser::Declaration::Function(func), + Declaration::Function(Decorated { on: function, .. }), ) = &overload_declaration else { unreachable!() }; let shape = super::functions::synthesise_shape( - &func.on, + function, environment, checking_data, ); @@ -304,100 +570,107 @@ pub(crate) fn hoist_statements( let upcoming = second_items.peek().and_then(|next| { matches!( next, - StatementOrDeclaration::Declaration( parser::Declaration::Function(func)) + StatementOrDeclaration::Declaration(Declaration::Function(Decorated { on: func, .. })) if - func.on.name.as_option_str().is_some_and(|n| n == name) - && func.on.has_body() + func.name.as_option_str().is_some_and(|n| n == name) + && func.has_body() ) .then_some(next) }); if let Some(StatementOrDeclaration::Declaration( - Declaration::Function(func), + Declaration::Function(Decorated { on: function, .. }), )) = upcoming { let actual = super::functions::synthesise_shape( - &func.on, + function, environment, checking_data, ); (overloads, actual) - } else if func.on.name.declare { + } else if function.name.declare { let actual = overloads.pop().unwrap(); (overloads, actual) } else { - todo!("error that missing body or not declare") + // TODO what about `checking_data.options.lsp_mode`? + checking_data.diagnostics_container.add_error( + TypeCheckError::FunctionWithoutBodyNotAllowedHere { + position: ASTNode::get_position(function) + .with_source(environment.get_source()), + }, + ); + continue; } } else { let actual = super::functions::synthesise_shape( - &func.on, + function, environment, checking_data, ); (Vec::new(), actual) }; + let internal_effect = get_internal_function_effect_from_decorators( + decorators, + name, + environment, + ); let value = super::functions::build_overloaded_function( - crate::FunctionId(environment.get_source(), func.on.position.start), - crate::features::functions::FunctionBehavior::Function { - free_this_id: TypeId::ERROR_TYPE, + crate::FunctionId(environment.get_source(), function.position.start), + crate::types::functions::FunctionBehavior::Function { + this_id: TypeId::ERROR_TYPE, prototype: TypeId::ERROR_TYPE, - is_async: func.on.header.is_async(), - is_generator: func.on.header.is_generator(), + is_async: function.header.is_async(), + is_generator: function.header.is_generator(), + name: TypeId::ERROR_TYPE, }, overloads, actual, environment, &mut checking_data.types, &mut checking_data.diagnostics_container, + if let Some(ie) = internal_effect { + ie.into() + } else { + crate::types::FunctionEffect::Unknown + }, ); - let k = crate::VariableId(environment.get_source(), func.on.position.start); + let variable_id = + crate::VariableId(environment.get_source(), name_position.start); checking_data .local_type_mappings .variables_to_constraints .0 - .insert(k, value); + .insert(variable_id, value); let argument = VariableRegisterArguments { // TODO functions are constant references constant: true, space: Some(value), initial_value: Some(value), + allow_reregistration: false, }; + let name_position = name_position.with_source(environment.get_source()); environment.register_variable_handle_error( name, argument, - func.get_position().with_source(environment.get_source()), + name_position, &mut checking_data.diagnostics_container, + &mut checking_data.local_type_mappings, checking_data.options.record_all_assignments_and_reads, ); } } - parser::Declaration::Enum(r#enum) => { + // Declaration::Interface(Decorated { on: r#enum, .. }) + Declaration::Enum(r#enum) => { checking_data.raise_unimplemented_error( "enum", r#enum.position.with_source(environment.get_source()), ); } - parser::Declaration::Interface(interface) => { - let ty = *checking_data - .local_type_mappings - .types_to_types - .get(interface.on.get_position().start) - .unwrap(); - - super::interfaces::synthesise_signatures( - interface.on.type_parameters.as_deref(), - interface.on.extends.as_deref(), - &interface.on.members, - super::interfaces::OnToType(ty), - environment, - checking_data, - ); - } - parser::Declaration::DeclareVariable(DeclareVariableDeclaration { + Declaration::DeclareVariable(DeclareVariableDeclaration { keyword: _, declarations, position: _, @@ -429,123 +702,74 @@ pub(crate) fn hoist_statements( constant: true, space: None, initial_value: Some(ty), + allow_reregistration: false, }, ); } } - - parser::Declaration::Export(exported) => match &exported.on { - parser::declarations::ExportDeclaration::Variable { exported, position: _ } => { - match exported { - Exportable::Function(func) => { - let declared_at = ASTNode::get_position(func) - .with_source(environment.get_source()); - - if let Some(VariableIdentifier::Standard(name, ..)) = - func.name.as_option_variable_identifier() - { - let argument = VariableRegisterArguments { - // TODO based on keyword - constant: true, - space: None, - initial_value: None, - }; - environment.register_variable_handle_error( - name, - argument, - declared_at, - &mut checking_data.diagnostics_container, - checking_data.options.record_all_assignments_and_reads, - ); - } - } - Exportable::Variable(declaration) => { - // TODO mark exported - hoist_variable_declaration(declaration, environment, checking_data); - } - Exportable::Interface(interface) => { - let ty = *checking_data - .local_type_mappings - .types_to_types - .get(interface.get_position().start) - .unwrap(); - - super::interfaces::synthesise_signatures( - interface.type_parameters.as_deref(), - interface.extends.as_deref(), - &interface.members, - super::interfaces::OnToType(ty), - environment, - checking_data, - ); - } - Exportable::Class(class) => { - register_statement_class_with_members( - class, - environment, - checking_data, - ); - } - Exportable::TypeAlias(_) | Exportable::ImportAll { .. } => {} - // TODO - Exportable::Parts(..) | Exportable::ImportParts { .. } => { - crate::utilities::notify!("TODO"); - } - } - } - parser::declarations::ExportDeclaration::Default { .. } => {} - parser::declarations::ExportDeclaration::DefaultFunction { .. } => { - todo!() - } - }, - parser::Declaration::Class(class) => { - register_statement_class_with_members(&class.on, environment, checking_data); - } - parser::Declaration::TypeAlias(_) | parser::Declaration::Import(_) => {} + _ => {} }, StatementOrDeclaration::Imported { .. } | StatementOrDeclaration::Marker(_, _) => {} } } // Third stage: functions - let mut third_stage_items = items.iter().peekable(); - while let Some(item) = third_stage_items.next() { - match item { - StatementOrDeclaration::Declaration(Declaration::Function(function)) => { - let variable_id = - crate::VariableId(environment.get_source(), item.get_position().start); - let is_async = function.on.header.is_async(); - let is_generator = function.on.header.is_generator(); - let location = function.on.header.get_location().map(|location| match location { + let third_stage_items = items.iter().peekable(); + for item in third_stage_items { + if let StatementOrDeclaration::Declaration( + Declaration::Function(Decorated { on: function, decorators, .. }) + | Declaration::Export(Decorated { + on: + ExportDeclaration::Variable { + exported: Exportable::Function(function), + position: _, + }, + decorators, + .. + }), + ) = item + { + if let Some(VariableIdentifier::Standard(name, name_position)) = + function.name.as_option_variable_identifier() + { + let variable_id = crate::VariableId(environment.get_source(), name_position.start); + let is_async = function.header.is_async(); + let is_generator = function.header.is_generator(); + let location = function.header.get_location().map(|location| match location { parser::functions::FunctionLocationModifier::Server => "server".to_owned(), parser::functions::FunctionLocationModifier::Worker => "worker".to_owned(), }); - if function.on.name.declare { - let (overloaded, _last) = if function.on.has_body() { - (false, function) - } else { - let last = third_stage_items.find(|f| { - if let StatementOrDeclaration::Declaration(Declaration::Function( - function, - )) = f - { - function.on.has_body() - } else { - false - } - }); - - if let Some(StatementOrDeclaration::Declaration(Declaration::Function( - function, - ))) = last - { - (true, function) - } else { - // Some error with non-overloads - continue; - } - }; + let value = if function.name.declare { + let (overloaded, _last) = (false, function); + // if function.has_body() { + // } else { + // let last = third_stage_items.find(|f| { + // if let StatementOrDeclaration::Declaration(Declaration::Function( + // Decorated { on: function, .. }, + // )) = f + // { + // function.has_body() + // } else { + // false + // } + // }); + // if let Some(StatementOrDeclaration::Declaration(Declaration::Function( + // Decorated { on: function, .. }, + // ))) = last + // { + // (true, function) + // } else { + // // Some error with non-overloads + // continue; + // } + // }; + + let internal_marker = get_internal_function_effect_from_decorators( + decorators, + function.name.as_option_str().unwrap(), + environment, + ); synthesise_declare_statement_function( variable_id, @@ -553,36 +777,37 @@ pub(crate) fn hoist_statements( is_async, is_generator, location, - None, - &function.on, + name.clone(), + internal_marker, + function, environment, checking_data, - ); + ) } else { - let (overloaded, _last) = if function.on.has_body() { - (false, function) - } else { - let last = third_stage_items.find(|f| { - if let StatementOrDeclaration::Declaration(Declaration::Function( - function, - )) = f - { - function.on.has_body() - } else { - false - } - }); - - if let Some(StatementOrDeclaration::Declaration(Declaration::Function( - function, - ))) = last - { - (true, function) - } else { - // Some error with non-overloads - continue; - } - }; + let (overloaded, _last) = (false, function); + // if function.has_body() { + // } else { + // let last = third_stage_items.take_while(|f| { + // if let StatementOrDeclaration::Declaration(Declaration::Function( + // Decorated { on: function, .. }, + // )) = f + // { + // function.has_body() + // } else { + // false + // } + // }); + + // if let Some(StatementOrDeclaration::Declaration(Declaration::Function( + // Decorated { on: function, .. }, + // ))) = last + // { + // (true, function) + // } else { + // // Some error with non-overloads + // continue; + // } + // }; synthesise_hoisted_statement_function( variable_id, @@ -590,56 +815,29 @@ pub(crate) fn hoist_statements( is_async, is_generator, location, - &function.on, + name.clone(), + function, environment, checking_data, - ); - } - } - StatementOrDeclaration::Declaration(Declaration::Export(Decorated { - on: - ExportDeclaration::Variable { - exported: Exportable::Function(function), - position: _, - }, - .. - })) => { - let variable_id = - crate::VariableId(environment.get_source(), item.get_position().start); - - let is_async = function.header.is_async(); - let is_generator = function.header.is_generator(); - let location = function.header.get_location().map(|location| match location { - parser::functions::FunctionLocationModifier::Server => "server".to_owned(), - parser::functions::FunctionLocationModifier::Worker => "worker".to_owned(), - }); - - synthesise_hoisted_statement_function( - variable_id, - // TODO - false, - is_async, - is_generator, - location, - function, - environment, - checking_data, - ); - - if let crate::Scope::Module { ref mut exported, .. } = - environment.context_type.scope - { - // TODO check existing? - if let Some(VariableIdentifier::Standard(name, ..)) = - function.name.as_option_variable_identifier() + ) + }; + + checking_data + .local_type_mappings + .variables_to_constraints + .0 + .insert(variable_id, value); + + if let StatementOrDeclaration::Declaration(Declaration::Export(_)) = item { + if let crate::Scope::Module { ref mut exported, .. } = + environment.context_type.scope { exported .named - .push((name.clone(), (variable_id, VariableMutability::Constant))); + .insert(name.clone(), (variable_id, VariableMutability::Constant)); } } } - _ => (), } } } @@ -658,7 +856,10 @@ fn import_part_to_name_pair(item: &parser::declarations::ImportPart) -> Option item, - parser::declarations::ImportExportName::Marker(_) => todo!(), + parser::declarations::ImportExportName::Marker(_) => { + // TODO I think okay + return None; + } }, r#as: name, position: *position, @@ -690,7 +891,9 @@ pub(super) fn export_part_to_name_pair( r#as: match alias { parser::declarations::ImportExportName::Reference(item) | parser::declarations::ImportExportName::Quoted(item, _) => item, - parser::declarations::ImportExportName::Marker(_) => todo!(), + parser::declarations::ImportExportName::Marker(_) => { + return None; + } }, position: *position, }) @@ -728,6 +931,7 @@ pub(super) fn hoist_variable_declaration( space: constraint, // Value set later initial_value: None, + allow_reregistration: false, }, ); } @@ -746,6 +950,7 @@ pub(super) fn hoist_variable_declaration( space: constraint, // Value set later initial_value: None, + allow_reregistration: false, }, ); } diff --git a/checker/src/synthesis/interfaces.rs b/checker/src/synthesis/interfaces.rs index cf006657..60817370 100644 --- a/checker/src/synthesis/interfaces.rs +++ b/checker/src/synthesis/interfaces.rs @@ -5,11 +5,12 @@ use source_map::SpanWithSource; use crate::{ context::{Context, Environment}, - features::functions::{self, GetterSetter}, + features::functions::GetterSetter, synthesis::parser_property_key_to_checker_property_key, types::{ - properties::{PropertyKey, PropertyValue, Publicity}, - FunctionType, Type, + calling::Callable, + properties::{Descriptor, PropertyKey, PropertyValue, Publicity}, + references_key_of, FunctionType, Type, }, CheckingData, Scope, TypeId, }; @@ -18,11 +19,29 @@ use super::{ functions::synthesise_function_annotation, type_annotations::synthesise_type_annotation, }; +/// inverse of readonly. Closer to JS semantics +pub struct Writable(pub TypeId); + +impl Writable { + fn from_readonly(is_readonly: bool) -> Self { + Self(if is_readonly { TypeId::FALSE } else { TypeId::TRUE }) + } +} + +/// inverse of optional. Closer to implementation +pub struct IsDefined(pub TypeId); + +impl IsDefined { + fn from_optionality(is_optional: bool) -> Self { + Self(if is_optional { TypeId::OPEN_BOOLEAN_TYPE } else { TypeId::TRUE }) + } +} + pub(crate) trait SynthesiseInterfaceBehavior { fn register( &mut self, - key: ParserPropertyKeyType, - value: InterfaceValue, + key: InterfaceKey, + value: (InterfaceValue, IsDefined, Writable), checking_data: &mut CheckingData, environment: &mut Environment, position: SpanWithSource, @@ -31,46 +50,34 @@ pub(crate) trait SynthesiseInterfaceBehavior { fn interface_type(&self) -> Option; } -pub(crate) enum InterfaceValue { - Function(FunctionType, GetterSetter), - Value(TypeId), - Optional(TypeId), -} - -pub(crate) enum ParserPropertyKeyType<'a> { +pub(crate) enum InterfaceKey<'a> { ClassProperty(&'a ParserPropertyKey), // ObjectProperty(&'a ParserPropertyKey), Type(TypeId), } +pub(crate) enum InterfaceValue { + Function(FunctionType, Option), + Value(TypeId), +} + pub(crate) struct OnToType(pub(crate) TypeId); impl SynthesiseInterfaceBehavior for OnToType { fn register( &mut self, - key: ParserPropertyKeyType, - value: InterfaceValue, + key: InterfaceKey, + (value, always_defined, writable): (InterfaceValue, IsDefined, Writable), checking_data: &mut CheckingData, environment: &mut Environment, - position: SpanWithSource, + _position: SpanWithSource, ) { let (publicity, under) = match key { - ParserPropertyKeyType::ClassProperty(key) => { + InterfaceKey::ClassProperty(key) => { // TODO let perform_side_effect_computed = true; ( - if matches!( - key, - parser::PropertyKey::Identifier( - _, - _, - parser::property_key::PublicOrPrivate::Private - ) - ) { - Publicity::Private - } else { - Publicity::Public - }, + if key.is_private() { Publicity::Private } else { Publicity::Public }, parser_property_key_to_checker_property_key( key, environment, @@ -79,33 +86,48 @@ impl SynthesiseInterfaceBehavior for OnToType { ), ) } - // ParserPropertyKeyType::ObjectProperty(key) => ( - // Publicity::Public, - // parser_property_key_to_checker_property_key(key, environment, checking_data), - // ), - ParserPropertyKeyType::Type(ty) => (Publicity::Public, PropertyKey::Type(ty)), + InterfaceKey::Type(ty) => (Publicity::Public, PropertyKey::Type(ty)), }; - let ty = match value { - InterfaceValue::Function(function, getter_setter) => match getter_setter { - GetterSetter::Getter => PropertyValue::Getter(Box::new(function)), - GetterSetter::Setter => PropertyValue::Setter(Box::new(function)), - GetterSetter::None => { - let function_id = function.id; - checking_data.types.functions.insert(function.id, function); - let ty = Type::FunctionReference(function_id); - PropertyValue::Value(checking_data.types.register_type(ty)) - } - }, - InterfaceValue::Value(value) => PropertyValue::Value(value), - // optional properties (`?:`) is implemented here: - InterfaceValue::Optional(value) => PropertyValue::ConditionallyExists { - on: TypeId::BOOLEAN_TYPE, - truthy: PropertyValue::Value(value).into(), - }, + let value = + match value { + InterfaceValue::Function(function, getter_setter) => match getter_setter { + Some(GetterSetter::Getter) => PropertyValue::Getter( + Callable::new_from_function(function, &mut checking_data.types), + ), + Some(GetterSetter::Setter) => PropertyValue::Setter( + Callable::new_from_function(function, &mut checking_data.types), + ), + None => { + let function_id = function.id; + checking_data.types.functions.insert(function.id, function); + let ty = Type::FunctionReference(function_id); + PropertyValue::Value(checking_data.types.register_type(ty)) + } + }, + InterfaceValue::Value(value) => PropertyValue::Value(value), + }; + let value = if let Writable(TypeId::TRUE) = writable { + value + } else { + let descriptor = Descriptor { + writable: writable.0, + enumerable: TypeId::TRUE, + configurable: TypeId::TRUE, + }; + PropertyValue::Configured { on: Box::new(value), descriptor } + }; + // optional properties (`?:`) is implemented here: + let value = if let IsDefined(TypeId::TRUE) = always_defined { + value + } else { + // crate::utilities::notify!("always_defined.0 {:?}", always_defined.0); + PropertyValue::ConditionallyExists { + condition: always_defined.0, + truthy: Box::new(value), + } }; - // None position should be fine here - environment.info.register_property(self.0, publicity, under, ty, false, position); + environment.info.register_property_on_type(self.0, publicity, under, value); } fn interface_type(&self) -> Option { @@ -137,7 +159,7 @@ pub(super) fn synthesise_signatures { // Fix for performing const annotations. TODO want to do better @@ -146,19 +168,22 @@ pub(super) fn synthesise_signatures GetterSetter::Getter, - parser::functions::MethodHeader::Set => GetterSetter::Setter, - parser::functions::MethodHeader::Regular { .. } => GetterSetter::None, + parser::functions::MethodHeader::Get => Some(GetterSetter::Getter), + parser::functions::MethodHeader::Set => Some(GetterSetter::Setter), + parser::functions::MethodHeader::Regular { .. } => None, }; let position_with_source = position.with_source(environment.get_source()); @@ -174,8 +199,12 @@ pub(super) fn synthesise_signatures { - if *is_readonly { - checking_data.raise_unimplemented_error( - "readonly items", - position.with_source(environment.get_source()), - ); - } - let value = synthesise_type_annotation(type_annotation, environment, checking_data); - let value = if *is_optional { - InterfaceValue::Optional(value) - } else { - InterfaceValue::Value(value) - }; - interface_register_behavior.register( - ParserPropertyKeyType::ClassProperty(name), - value, + InterfaceKey::ClassProperty(name), + ( + InterfaceValue::Value(value), + IsDefined::from_optionality(*is_optional), + Writable::from_readonly(*is_readonly), + ), checking_data, environment, position.with_source(environment.get_source()), @@ -216,15 +236,22 @@ pub(super) fn synthesise_signatures { // 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); + + let value = InterfaceValue::Value(value); + interface_register_behavior.register( - ParserPropertyKeyType::Type(key), - InterfaceValue::Value(value), + InterfaceKey::Type(key), + ( + value, + IsDefined::from_optionality(false), + Writable::from_readonly(*is_readonly), + ), checking_data, environment, position.with_source(environment.get_source()), @@ -254,43 +281,91 @@ pub(super) fn synthesise_signatures { + // For mapped types: https://www.typescriptlang.org/docs/handbook/2/mapped-types.html let matching_type = synthesise_type_annotation(matching_type, environment, checking_data); let (key, value) = { // TODO special scope here - let mut environment = environment.new_lexical_environment(Scope::Block {}); + let mut sub_environment = + environment.new_lexical_environment(Scope::Block {}); let parameter_type = checking_data.types.register_type(Type::RootPolyType( crate::types::PolyNature::MappedGeneric { name: parameter.clone(), - eager_fixed: matching_type, + extends: matching_type, }, )); - environment.named_types.insert(parameter.clone(), parameter_type); + sub_environment.named_types.insert(parameter.clone(), parameter_type); let key = if let Some(as_type) = as_type { - synthesise_type_annotation(as_type, &mut environment, checking_data) + synthesise_type_annotation(as_type, &mut sub_environment, checking_data) } else { parameter_type }; + // crate::utilities::notify!("output_type {:?}", output_type); + let value = synthesise_type_annotation( output_type, - &mut environment, + &mut sub_environment, checking_data, ); + environment + .info + .current_properties + .extend(sub_environment.info.current_properties); + (key, value) }; + // wrg to `references_key_of`, it is TSC behavior for the conditionality and + // writable of the property to be based on the argument from keyof. + // if keyof is not present this argument is not set and so breaks things. + // This `keyof` could be collected during synthesising but doing here as easier + // + edge cases around alias and generics + + let always_defined = match optionality { + parser::types::interface::Optionality::Default => { + if references_key_of(key, &checking_data.types) { + IsDefined(TypeId::NON_OPTIONAL_KEY_ARGUMENT) + } else { + IsDefined::from_optionality(false) + } + } + parser::types::interface::Optionality::Optional => { + IsDefined::from_optionality(true) + } + parser::types::interface::Optionality::Required => { + IsDefined::from_optionality(false) + } + }; + + let writable = match is_readonly { + parser::types::interface::MappedReadonlyKind::Negated => { + Writable::from_readonly(false) + } + parser::types::interface::MappedReadonlyKind::Always => { + Writable::from_readonly(true) + } + parser::types::interface::MappedReadonlyKind::False => { + if references_key_of(key, &checking_data.types) { + crate::utilities::notify!("Here"); + Writable(TypeId::WRITABLE_KEY_ARGUMENT) + } else { + Writable::from_readonly(false) + } + } + }; + interface_register_behavior.register( - ParserPropertyKeyType::Type(key), - InterfaceValue::Value(value), + InterfaceKey::Type(key), + (InterfaceValue::Value(value), always_defined, writable), checking_data, environment, position.with_source(environment.get_source()), diff --git a/checker/src/synthesis/mod.rs b/checker/src/synthesis/mod.rs index c3254f74..a29560a5 100644 --- a/checker/src/synthesis/mod.rs +++ b/checker/src/synthesis/mod.rs @@ -24,9 +24,9 @@ use parser::{ use source_map::SourceId; use crate::{ - context::{Names, VariableRegisterArguments}, + context::{Environment, LocalInformation, Names, VariableRegisterArguments}, types::properties::PropertyKey, - CheckingData, Diagnostic, Environment, LocalInformation, RootContext, TypeId, VariableId, + CheckingData, Diagnostic, RootContext, TypeId, VariableId, }; use self::{ @@ -91,13 +91,13 @@ impl crate::ASTImplementation for EznoParser { synthesise_block(&module.items, module_environment, checking_data); } - fn synthesise_definition_file( - file: Self::DefinitionFile<'_>, + fn synthesise_definition_module( + module: &Self::DefinitionFile<'_>, source: SourceId, root: &RootContext, checking_data: &mut CheckingData, ) -> (Names, LocalInformation) { - definitions::type_definition_file(file, source, checking_data, root) + definitions::type_definition_file(module, source, checking_data, root) } fn synthesise_expression( @@ -117,6 +117,18 @@ impl crate::ASTImplementation for EznoParser { ¶meter.name } + fn synthesise_type_parameter_extends( + parameter: &Self::TypeParameter<'_>, + environment: &mut Environment, + checking_data: &mut crate::CheckingData, + ) -> TypeId { + if let Some(ref extends) = parameter.extends { + synthesise_type_annotation(extends, environment, checking_data) + } else { + TypeId::ANY_TYPE + } + } + fn type_annotation_position<'_a>( annotation: &'_a Self::TypeAnnotation<'_a>, ) -> source_map::Span { @@ -166,10 +178,27 @@ impl crate::ASTImplementation for EznoParser { parser::statements::ForLoopStatementInitialiser::VariableDeclaration(declaration) => { // TODO is this correct & the best hoist_variable_declaration(declaration, environment, checking_data); - synthesise_variable_declaration(declaration, environment, checking_data, false); + synthesise_variable_declaration( + declaration, + environment, + checking_data, + false, + // IMPORTANT! + checking_data.options.infer_sensible_constraints_in_for_loops, + ); + } + parser::statements::ForLoopStatementInitialiser::VarStatement(stmt) => { + checking_data.raise_unimplemented_error( + "var in for statement initiliser", + stmt.get_position().with_source(environment.get_source()), + ); + } + parser::statements::ForLoopStatementInitialiser::Expression(expr) => { + checking_data.raise_unimplemented_error( + "expression as for statement initiliser", + expr.get_position().with_source(environment.get_source()), + ); } - parser::statements::ForLoopStatementInitialiser::VarStatement(_) => todo!(), - parser::statements::ForLoopStatementInitialiser::Expression(_) => todo!(), } } @@ -209,21 +238,23 @@ pub(super) fn parser_property_key_to_checker_property_key< ParserPropertyKey::StringLiteral(value, ..) | ParserPropertyKey::Identifier(value, ..) => { PropertyKey::String(std::borrow::Cow::Owned(value.clone())) } - ParserPropertyKey::NumberLiteral(number, _) => { + ParserPropertyKey::NumberLiteral(number, pos) => { let result = f64::try_from(number.clone()); - match result { - Ok(v) => { - // TODO is there a better way - #[allow(clippy::float_cmp)] - if v.floor() == v { - PropertyKey::from_usize(v as usize) - } else { - // TODO - PropertyKey::String(std::borrow::Cow::Owned(v.to_string())) - } + if let Ok(v) = result { + // TODO is there a better way + #[allow(clippy::float_cmp)] + if v.floor() == v { + PropertyKey::from_usize(v as usize) + } else { + // TODO + PropertyKey::String(std::borrow::Cow::Owned(v.to_string())) } - // TODO - Err(()) => todo!(), + } else { + checking_data.raise_unimplemented_error( + "big int as property key", + pos.with_source(environment.get_source()), + ); + PropertyKey::Type(TypeId::ERROR_TYPE) } } ParserPropertyKey::Computed(expression, _) => { @@ -275,7 +306,7 @@ impl StatementOrExpressionVariable for ExpressionPosition { /// For the REPL in Ezno's CLI pub mod interactive { - use std::{collections::HashSet, mem, path::PathBuf}; + use std::{mem, path::PathBuf}; use source_map::{FileSystem, MapFileStore, SourceId, WithPathMap}; @@ -295,7 +326,7 @@ pub mod interactive { impl<'a, T: crate::ReadFromFS> State<'a, T> { pub fn new( resolver: &'a T, - type_definition_files: HashSet, + type_definition_files: Vec, ) -> Result)> { let mut root = RootContext::new_with_primitive_references(); let mut checking_data = diff --git a/checker/src/synthesis/statements.rs b/checker/src/synthesis/statements.rs index 05d0887a..cd291cd1 100644 --- a/checker/src/synthesis/statements.rs +++ b/checker/src/synthesis/statements.rs @@ -217,7 +217,7 @@ pub(super) fn synthesise_statement( TypeId::ANY_TYPE, ); let thrown_position = stmt.1.with_source(environment.get_source()); - environment.throw_value(thrown_value, thrown_position); + environment.throw_value(thrown_value, thrown_position, &mut checking_data.types); } Statement::Labelled { position: _, name, statement } => { // Labels on invalid statements is caught at parse time @@ -230,7 +230,13 @@ pub(super) fn synthesise_statement( } Statement::VarVariable(stmt) => { for declaration in &stmt.declarations { - synthesise_variable_declaration_item(declaration, environment, checking_data, None); + synthesise_variable_declaration_item( + declaration, + environment, + checking_data, + None, + false, + ); } } Statement::TryCatch(stmt) => new_try_context( diff --git a/checker/src/synthesis/type_annotations.rs b/checker/src/synthesis/type_annotations.rs index affb7079..bd7d34ce 100644 --- a/checker/src/synthesis/type_annotations.rs +++ b/checker/src/synthesis/type_annotations.rs @@ -1,47 +1,31 @@ //! Logic for getting [`TypeId`] from [`parser::TypeAnnotation`]s -//! -//! ### There are several behaviors for type references depending on their position: -//! #### Sources: -//! - Type reference of any source variable declarations is a [`crate::TypeConstraint`] -//! - Type references in parameters are [`crate::TypeConstraint`]s -//! - Type references in returns types are also [`crate::TypeConstraint`]s, because ezno uses the body to get the return -//! type -//! -//! #### Declarations -//! - Type reference in any declaration or return type is a internal type [`crate::Type::InternalObjectReference`] -//! - Return types need to know whether they return a unique object (todo don't know any examples) -//! or a new object. e.g. `Array.from` -//! - Parameters shouldn't do generic resolving -//! -//! ### Treatment of `any` -//! - Any has no properties because it is a union of all the types. It also means that it could be be `{}` -//! - To allow for compat it treats it as inferred generic **so it can get properties off of it**. Would be better -//! to allow this as a condition in the future use std::convert::TryInto; -use crate::{ - synthesis::assignments::synthesise_access_to_reference, types::generics::ExplicitTypeArguments, - Map, -}; -use parser::{ - type_annotations::{AnnotationWithBinder, CommonTypes, TupleElementKind, TupleLiteralElement}, - ASTNode, TypeAnnotation, +use super::{ + assignments::synthesise_access_to_reference, functions::synthesise_function_annotation, }; -use source_map::SpanWithSource; - use crate::{ + context::{Environment, LocalInformation, Scope}, diagnostics::{TypeCheckError, TypeCheckWarning, TypeStringRepresentation}, features::objects::ObjectBuilder, - synthesis::functions::synthesise_function_annotation, - types::properties::Publicity, types::{ generics::generic_type_arguments::GenericArguments, - properties::{PropertyKey, PropertyValue}, + properties::{PropertyKey, PropertyValue, Publicity}, Constant, Constructor, PartiallyAppliedGenerics, Type, TypeId, }, - CheckingData, Environment, Scope, + types::{ + generics::ExplicitTypeArguments, + intrinsics::{self, distribute_tsc_string_intrinsic}, + ArrayItem, Counter, + }, + CheckingData, Map, +}; +use parser::{ + type_annotations::{AnnotationWithBinder, CommonTypes, TupleElementKind, TupleLiteralElement}, + ASTNode, TypeAnnotation, }; +use source_map::SpanWithSource; /// Turns a [`parser::TypeAnnotation`] into [`TypeId`] /// @@ -52,7 +36,7 @@ use crate::{ /// Example errors: /// - Reference to generic without generic types /// - Reference to non generic with generic types -pub(super) fn synthesise_type_annotation( +pub fn synthesise_type_annotation( annotation: &TypeAnnotation, // TODO shouldn't be mutable. Currently required because of checking just generic specialisation environment: &mut Environment, @@ -80,7 +64,13 @@ pub(super) fn synthesise_type_annotation( // TODO differentiate? see #137 "any" | "unknown" => TypeId::ANY_TYPE, "never" => TypeId::NEVER_TYPE, - "this" => todo!(), // environment.get_value_of_this(&mut checking_data.types), + "this" => { + checking_data.raise_unimplemented_error( + "this annotation", + pos.with_source(environment.get_source()), + ); + TypeId::ERROR_TYPE + } "self" => TypeId::ANY_INFERRED_FREE_THIS, name => { if let Some(ty) = environment.get_type_from_name(name) { @@ -98,11 +88,20 @@ pub(super) fn synthesise_type_annotation( ty } } else { - checking_data.diagnostics_container.add_error(TypeCheckError::CannotFindType( - name, - environment.get_all_named_types(), - pos.with_source(environment.get_source()), - )); + let possibles = { + let mut possibles = + crate::get_closest(environment.get_all_named_types(), name) + .unwrap_or(vec![]); + possibles.sort_unstable(); + possibles + }; + checking_data.diagnostics_container.add_error( + TypeCheckError::CouldNotFindType( + name, + possibles, + pos.with_source(environment.get_source()), + ), + ); TypeId::ERROR_TYPE } } @@ -164,26 +163,11 @@ pub(super) fn synthesise_type_annotation( // _ => {} // } - let Some(inner_type_id) = environment.get_type_from_name(name) else { - checking_data.diagnostics_container.add_error(TypeCheckError::CouldNotFindType( - name, - environment.get_all_named_types(), - position.with_source(environment.get_source()), - )); - return TypeId::ERROR_TYPE; - }; - - let inner_type = checking_data.types.get_type_by_id(inner_type_id); - - // crate::utilities::notify!("{:?}", inner_type); - - if let Some(parameters) = inner_type.get_parameters() { - let is_flattenable_alias = if let Type::AliasTo { to, .. } = inner_type { - // Important that these wrappers are kept as there 'wrap' holds information - if matches!( - inner_type_id, - TypeId::LITERAL_RESTRICTION | TypeId::READONLY_RESTRICTION - ) { + if let Some(inner_type_id) = environment.get_type_from_name(name) { + let inner_type = checking_data.types.get_type_by_id(inner_type_id); + let inner_type_alias_id = if let Type::AliasTo { to, .. } = inner_type { + // Fix for recursion + if *to == TypeId::ANY_TO_INFER_TYPE { None } else { Some(*to) @@ -192,101 +176,148 @@ pub(super) fn synthesise_type_annotation( None }; - let mut type_arguments: crate::Map = - crate::Map::default(); + // crate::utilities::notify!("{:?}", inner_type); - for (parameter, argument_type_annotation) in - parameters.clone().into_iter().zip(arguments.iter()) - { - let argument = synthesise_type_annotation( - argument_type_annotation, - environment, - checking_data, - ); + if let Some(parameters) = inner_type.get_parameters() { + let mut type_arguments: crate::Map = + crate::Map::default(); + for (parameter, argument_type_annotation) in + parameters.clone().into_iter().zip(arguments.iter()) { - // TODO check restriction on parameter - // let mut basic_equality = BasicEquality { - // add_property_restrictions: true, - // position: argument_type_annotation - // .get_position() - // .with_source(environment.get_source()), - // // TODO not needed - // object_constraints: Default::default(), - // allow_errors: true, - // }; - - // let Type::RootPolyType(PolyNature::InterfaceGeneric { name: _ }) = - // checking_data.types.get_type_by_id(parameter) - // else { - // unreachable!() - // }; - - // // TODO it is a bit weird with the arguments, maybe should get their restriction directly here? - // // Definition files don't necessary need to check ... - // let result = type_is_subtype( - // *parameter_restriction, - // argument, - // &mut basic_equality, - // environment, - // &checking_data.types, - // ); - - // if let SubTypeResult::IsNotSubType(_matches) = result { - // let error = TypeCheckError::GenericArgumentDoesNotMeetRestriction { - // parameter_restriction: TypeStringRepresentation::from_type_id( - // *parameter_restriction, - // environment, - // &checking_data.types, - // checking_data.options.debug_types, - // ), - // argument: TypeStringRepresentation::from_type_id( - // argument, - // environment, - // &checking_data.types, - // checking_data.options.debug_types, - // ), - // position: argument_type_annotation - // .get_position() - // .with_source(environment.get_source()), - // }; - - // checking_data.diagnostics_container.add_error(error); - // } + let argument = synthesise_type_annotation( + argument_type_annotation, + environment, + checking_data, + ); + + { + // TODO check restriction on parameter + // let mut basic_equality = BasicEquality { + // add_property_restrictions: true, + // position: argument_type_annotation + // .get_position() + // .with_source(environment.get_source()), + // // TODO not needed + // object_constraints: Default::default(), + // allow_errors: true, + // }; + + // let Type::RootPolyType(PolyNature::InterfaceGeneric { name: _ }) = + // checking_data.types.get_type_by_id(parameter) + // else { + // unreachable!() + // }; + + // // TODO it is a bit weird with the arguments, maybe should get their restriction directly here? + // // Definition files don't necessary need to check ... + // let result = type_is_subtype( + // *parameter_restriction, + // argument, + // &mut basic_equality, + // environment, + // &checking_data.types, + // ); + + // if let SubTypeResult::IsNotSubType(_matches) = result { + // let error = TypeCheckError::GenericArgumentDoesNotMeetRestriction { + // parameter_restriction: TypeStringRepresentation::from_type_id( + // *parameter_restriction, + // environment, + // &checking_data.types, + // checking_data.options.debug_types, + // ), + // argument: TypeStringRepresentation::from_type_id( + // argument, + // environment, + // &checking_data.types, + // checking_data.options.debug_types, + // ), + // position: argument_type_annotation + // .get_position() + // .with_source(environment.get_source()), + // }; + + // checking_data.diagnostics_container.add_error(error); + // } + } + + let with_source = argument_type_annotation + .get_position() + .with_source(environment.get_source()); + + type_arguments.insert(parameter, (argument, with_source)); } - let with_source = argument_type_annotation - .get_position() - .with_source(environment.get_source()); + // Inline alias with arguments unless intrinsic + // crate::utilities::notify!( + // "{:?} and {:?}", + // inner_type_alias_id, + // inner_type_alias_id.is_some_and(intrinsics::tsc_string_intrinsic) + // ); + + if intrinsics::tsc_string_intrinsic(inner_type_id) { + distribute_tsc_string_intrinsic( + inner_type_id, + type_arguments.get(&TypeId::STRING_GENERIC).unwrap().0, + &mut checking_data.types, + ) + } else if let (Some(inner_type_alias_id), false) = + (inner_type_alias_id, intrinsics::is_intrinsic(inner_type_id)) + { + // Important that these wrappers are kept as there 'wrap' holds information + // { + // use crate::types::printing::print_type; + + // let ty = print_type( + // inner_type_id, + // &mut checking_data.types, + // environment, + // true, + // ); + // crate::utilities::notify!("Here substituting alias eagerly {}", ty); + // } - type_arguments.insert(parameter, (argument, with_source)); - } + let substitution_arguments = + ExplicitTypeArguments(type_arguments).into_substitution_arguments(); - // Eagerly specialise for type alias. TODO don't do for object types... - if let Some(on) = is_flattenable_alias { - crate::types::substitute( - on, - &ExplicitTypeArguments(type_arguments).into_substitution_arguments(), - environment, - &mut checking_data.types, - ) - } else { - let arguments = GenericArguments::ExplicitRestrictions(type_arguments); + crate::types::substitute( + inner_type_alias_id, + &substitution_arguments, + environment, + &mut checking_data.types, + ) + } else { + let arguments = GenericArguments::ExplicitRestrictions(type_arguments); - let ty = Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: inner_type_id, - arguments, - }); + let ty = Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: inner_type_id, + arguments, + }); - checking_data.types.register_type(ty) + checking_data.types.register_type(ty) + } + } else { + checking_data.diagnostics_container.add_error( + TypeCheckError::TypeHasNoGenericParameters( + name.clone(), + position.with_source(environment.get_source()), + ), + ); + TypeId::ERROR_TYPE } } else { - checking_data.diagnostics_container.add_error( - TypeCheckError::TypeHasNoGenericParameters( - name.clone(), - position.with_source(environment.get_source()), - ), - ); + let possibles = { + let mut possibles = crate::get_closest(environment.get_all_named_types(), name) + .unwrap_or(vec![]); + possibles.sort_unstable(); + possibles + }; + checking_data.diagnostics_container.add_error(TypeCheckError::CouldNotFindType( + name, + possibles, + position.with_source(environment.get_source()), + )); TypeId::ERROR_TYPE } } @@ -306,7 +337,7 @@ pub(super) fn synthesise_type_annotation( checking_data, &position, // TODO async - crate::features::functions::FunctionBehavior::ArrowFunction { is_async: false }, + crate::types::functions::FunctionBehavior::ArrowFunction { is_async: false }, ); // TODO bit messy checking_data.types.new_function_type_annotation( @@ -370,6 +401,50 @@ pub(super) fn synthesise_type_annotation( .0 } TypeAnnotation::TupleLiteral(members, position) => { + let mut items = Vec::<(SpanWithSource, ArrayItem)>::with_capacity(members.len()); + + for TupleLiteralElement(spread, member, pos) in members { + // TODO store binder name...? + let type_annotation = match member { + AnnotationWithBinder::Annotated { ty, .. } + | AnnotationWithBinder::NoAnnotation(ty) => ty, + }; + + let annotation_ty = + synthesise_type_annotation(type_annotation, environment, checking_data); + + let pos = pos.with_source(environment.get_source()); + + match spread { + TupleElementKind::Standard => { + items.push((pos, ArrayItem::Member(annotation_ty))); + } + TupleElementKind::Optional => { + items.push((pos, ArrayItem::Optional(annotation_ty))); + } + TupleElementKind::Spread => { + let slice = crate::types::as_slice( + annotation_ty, + &checking_data.types, + environment, + ); + + crate::utilities::notify!("slice = {:?}", slice); + + // Flattening + match slice { + Ok(new_items) => { + // TODO position here? + items.extend(new_items.into_iter().map(|value| (pos, value))); + } + Err(()) => items.push((pos, ArrayItem::Wildcard(annotation_ty))), + } + } + } + } + + let mut idx: Counter = 0.into(); + // TODO maybe should be special type let mut obj = ObjectBuilder::new( Some(TypeId::ARRAY_TYPE), @@ -378,48 +453,66 @@ pub(super) fn synthesise_type_annotation( &mut environment.info, ); - for (idx, TupleLiteralElement(spread, member, _)) in members.iter().enumerate() { - // TODO binder name under data...? - match spread { - TupleElementKind::Standard => { - let type_annotation = match member { - AnnotationWithBinder::Annotated { ty, .. } - | AnnotationWithBinder::NoAnnotation(ty) => ty, - }; + for (ty_position, item) in items { + 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) => { + crate::utilities::notify!("found wildcard"); + let after = idx.into_type(&mut checking_data.types); - let item_ty = - synthesise_type_annotation(type_annotation, environment, checking_data); + let key = checking_data.types.new_intrinsic( + &crate::types::intrinsics::Intrinsic::GreaterThan, + after, + ); - let ty_position = - type_annotation.get_position().with_source(environment.get_source()); + let item_type = checking_data.types.register_type(Type::Constructor( + Constructor::Property { + on, + under: PropertyKey::Type(TypeId::OPEN_NUMBER_TYPE), + result: TypeId::UNIMPLEMENTED_ERROR_TYPE, + mode: crate::types::properties::AccessMode::Regular, + }, + )); obj.append( - environment, Publicity::Public, - PropertyKey::from_usize(idx), - PropertyValue::Value(item_ty), + PropertyKey::Type(key), + PropertyValue::Value(item_type), ty_position, + &mut environment.info, ); + idx.increment(&mut checking_data.types); + + continue; } - TupleElementKind::Optional => { - todo!() - } - TupleElementKind::Spread => { - todo!(); - } + }; + { + obj.append( + Publicity::Public, + idx.into_property_key(), + value, + ty_position, + &mut environment.info, + ); + idx.increment(&mut checking_data.types); } } - let constant = Constant::Number((members.len() as f64).try_into().unwrap()); - let length_value = checking_data.types.new_constant_type(constant); + let length_value = idx.into_type(&mut checking_data.types); + let position = annotation.get_position().with_source(environment.get_source()); - // TODO: Does `constant` have a position? Or should it have one? obj.append( - environment, Publicity::Public, PropertyKey::String("length".into()), PropertyValue::Value(length_value), - annotation.get_position().with_source(environment.get_source()), + position, + &mut environment.info, ); // TODO this should be anonymous object type @@ -453,17 +546,26 @@ pub(super) fn synthesise_type_annotation( } TypeAnnotation::Conditional { condition, resolve_true, resolve_false, position: _ } => { let (condition, infer_types) = { - let mut environment = + let mut sub_environment = environment.new_lexical_environment(Scope::TypeAnnotationCondition { infer_parameters: Default::default(), }); let condition = - synthesise_type_annotation(condition, &mut environment, checking_data); + synthesise_type_annotation(condition, &mut sub_environment, checking_data); let Scope::TypeAnnotationCondition { infer_parameters } = - environment.context_type.scope + sub_environment.context_type.scope else { unreachable!() }; + + // TODO temp as object types use the same environment.properties representation + { + let LocalInformation { current_properties, prototypes, .. } = + sub_environment.info; + environment.info.current_properties.extend(current_properties); + environment.info.prototypes.extend(prototypes); + } + (condition, infer_parameters) }; @@ -471,23 +573,61 @@ pub(super) fn synthesise_type_annotation( if infer_types.is_empty() { synthesise_type_annotation(resolve_true, environment, checking_data) } else { - let mut environment = + let mut sub_environment = environment.new_lexical_environment(Scope::TypeAnnotationConditionResult); - environment.named_types.extend(infer_types); - synthesise_type_annotation(resolve_true, &mut environment, checking_data) + + sub_environment.named_types.extend(infer_types); + let ty = synthesise_type_annotation( + resolve_true, + &mut sub_environment, + checking_data, + ); + + // TODO temp as object types use the same environment.properties representation + { + let LocalInformation { current_properties, prototypes, .. } = + sub_environment.info; + environment.info.current_properties.extend(current_properties); + environment.info.prototypes.extend(prototypes); + } + + ty } }; let otherwise_result = synthesise_type_annotation(resolve_false, environment, checking_data); - // TODO might want to record whether infer_types is_empty here + // TODO WIP + if let Type::Constructor(Constructor::TypeRelationOperator( + crate::types::TypeRelationOperator::Extends { item, extends }, + )) = checking_data.types.get_type_by_id(condition) + { + if let Type::Constant(_) = checking_data.types.get_type_by_id(*item) { + use crate::types::generics::substitution::{ + compute_extends_rule, SubstitutionArguments, + }; + let temp_args = SubstitutionArguments::new_arguments_for_use_in_loop(); + crate::utilities::notify!("Here"); + return compute_extends_rule( + *extends, + *item, + environment, + &mut checking_data.types, + truthy_result, + &temp_args, + otherwise_result, + ); + } + } + // TODO might want to record whether infer_types is_empty here + let result_union = checking_data.types.new_or_type(truthy_result, otherwise_result); let ty = Type::Constructor(Constructor::ConditionalResult { condition, truthy_result, otherwise_result, - result_union: checking_data.types.new_or_type(truthy_result, otherwise_result), + result_union, }); checking_data.types.register_type(ty) @@ -529,14 +669,17 @@ pub(super) fn synthesise_type_annotation( acc } TypeAnnotation::Infer { name, extends, position: _ } => { - if extends.is_some() { - crate::utilities::notify!("TODO"); - } + let extends = if let Some(ref extends) = extends { + synthesise_type_annotation(extends, environment, checking_data) + } else { + TypeId::ANY_TYPE + }; + if let Scope::TypeAnnotationCondition { ref mut infer_parameters } = environment.context_type.scope { let infer_type = checking_data.types.register_type(Type::RootPolyType( - crate::types::PolyNature::InferGeneric { name: name.clone() }, + crate::types::PolyNature::InferGeneric { name: name.clone(), extends }, )); let existing = infer_parameters.insert(name.clone(), infer_type); @@ -570,12 +713,19 @@ pub(super) fn synthesise_type_annotation( // )); // checking_data.types.register_type(ty) } - TypeAnnotation::Symbol { position, .. } => { - checking_data.raise_unimplemented_error( - "symbol annotation", - position.with_source(environment.get_source()), - ); - TypeId::ERROR_TYPE + TypeAnnotation::Symbol { name, unique: _unique, position: _ } => { + // TODO what does unique do? + if let Some(name) = name { + // TODO existing names + if name == "iterator" { + TypeId::SYMBOL_ITERATOR + } else { + crate::utilities::notify!("New symbol:{}", name); + checking_data.types.new_constant_type(Constant::Symbol { key: name.clone() }) + } + } else { + TypeId::SYMBOL_TYPE + } } TypeAnnotation::Asserts(_, position) => { // TODO construct condition for never @@ -607,7 +757,7 @@ pub(super) fn synthesise_type_annotation( fn part_to_type( part: &parser::ast::TemplateLiteralPart, checking_data: &mut CheckingData, - environment: &mut crate::context::Context, + environment: &mut Environment, ) -> TypeId { match part { parser::ast::TemplateLiteralPart::Static(s) => { diff --git a/checker/src/synthesis/variables.rs b/checker/src/synthesis/variables.rs index 85ff2810..477b956c 100644 --- a/checker/src/synthesis/variables.rs +++ b/checker/src/synthesis/variables.rs @@ -1,19 +1,25 @@ use std::borrow::Cow; use parser::{ - declarations::VariableDeclarationItem, ASTNode, ArrayDestructuringField, + declarations::VariableDeclarationItem, ASTNode, ArrayDestructuringField, Expression, ObjectDestructuringField, SpreadDestructuringField, VariableField, VariableIdentifier, }; use super::expressions::synthesise_expression; use crate::{ context::{Context, ContextType, VariableRegisterArguments}, - diagnostics::{PropertyRepresentation, TypeCheckError, TypeStringRepresentation}, - features::variables::{get_new_register_argument_under, VariableMutability}, + diagnostics::{PropertyKeyRepresentation, TypeCheckError, TypeStringRepresentation}, + features::{ + self, + variables::{get_new_register_argument_under, VariableMutability, VariableOrImport}, + }, synthesis::parser_property_key_to_checker_property_key, types::{ - printing, - properties::{get_property_key_names_on_a_single_type, PropertyKey, Publicity}, + get_larger_type, printing, + properties::{ + get_properties_on_single_type, get_property_key_names_on_a_single_type, PropertyKey, + Publicity, + }, }, CheckingData, Environment, TypeId, }; @@ -26,20 +32,12 @@ pub(crate) fn register_variable_identifier ) { match name { parser::VariableIdentifier::Standard(name, pos) => { - if let Some(reassignment_constraint) = argument.space { - let id = crate::VariableId(environment.get_source(), pos.start); - checking_data - .local_type_mappings - .variables_to_constraints - .0 - .insert(id, reassignment_constraint); - } - environment.register_variable_handle_error( name, argument, pos.with_source(environment.get_source()), &mut checking_data.diagnostics_container, + &mut checking_data.local_type_mappings, checking_data.options.record_all_assignments_and_reads, ); } @@ -62,9 +60,12 @@ pub(crate) fn register_variable( parser::VariableField::Name(variable) => { register_variable_identifier(variable, environment, checking_data, argument); } - parser::VariableField::Array { members, spread, position: _ } => { + parser::VariableField::Array { members, spread, position } => { if let Some(_spread) = spread { - todo!() + checking_data.raise_unimplemented_error( + "spread variable field", + position.with_source(environment.get_source()), + ); } for (idx, field) in members.iter().enumerate() { match field.get_ast_ref() { @@ -100,6 +101,8 @@ pub(crate) fn register_variable( } } parser::VariableField::Object { members, spread, .. } => { + let mut taken_members = spread.is_some().then(Vec::>::new); + for field in members { match field.get_ast_ref() { ObjectDestructuringField::Name(variable, _type, ..) => { @@ -107,6 +110,9 @@ pub(crate) fn register_variable( VariableIdentifier::Standard(ref name, _) => name, VariableIdentifier::Marker(_, _) => "?", }; + if let Some(ref mut taken_members) = taken_members { + taken_members.push(Cow::Borrowed(name)); + } let key = PropertyKey::String(Cow::Borrowed(name)); let argument = get_new_register_argument_under( &argument, @@ -136,6 +142,14 @@ pub(crate) fn register_variable( checking_data, false, ); + if let Some(ref mut taken_members) = taken_members { + match key { + PropertyKey::String(ref s) => taken_members.push(s.clone()), + PropertyKey::Type(_) => { + crate::utilities::notify!("Cannot remove type"); + } + } + } let argument = get_new_register_argument_under( &argument, &key, @@ -147,17 +161,47 @@ pub(crate) fn register_variable( } } } - if let Some(SpreadDestructuringField(variable, _position)) = spread { + if let (Some(taken_members), Some(SpreadDestructuringField(variable, _position))) = + (taken_members, spread) + { + // TODO + let initial_value = argument.initial_value; + + let space = if let Some(space) = argument.space { + let rest = checking_data.types.new_anonymous_interface_type(); + for (publicity, key, property) in get_properties_on_single_type( + space, + &checking_data.types, + environment, + true, + TypeId::ANY_TYPE, + ) { + if let PropertyKey::String(ref s) = key { + if taken_members.contains(s) { + continue; + } + } + + environment.info.register_property_on_type(rest, publicity, key, property); + } + Some(rest) + } else { + None + }; + + let argument = VariableRegisterArguments { + constant: argument.constant, + space, + initial_value, + allow_reregistration: argument.allow_reregistration, + }; + register_variable( variable, environment, checking_data, // TODO - VariableRegisterArguments { - constant: argument.constant, - space: argument.space, - initial_value: argument.initial_value, - }, + argument, ); } } @@ -177,6 +221,7 @@ pub(super) fn synthesise_variable_declaration_item< environment: &mut Environment, checking_data: &mut CheckingData, exported: Option, + infer_constraint: bool, ) { // This is only added if there is an annotation, so can be None let get_position = variable_declaration.get_position(); @@ -186,30 +231,74 @@ pub(super) fn synthesise_variable_declaration_item< .get(&(environment.get_source(), get_position.start)) .map(|(ty, pos)| (*ty, *pos)); - let value_ty = if let Some(value) = + // let name = + // types.new_constant_type(crate::Constant::String(name_object.to_owned())); + + let value_ty = if let Some(expression) = U::as_option_expression_ref(&variable_declaration.expression) { - let expecting = var_ty_and_pos.as_ref().map_or(TypeId::ANY_TYPE, |(var_ty, _)| *var_ty); + let expected: TypeId = + var_ty_and_pos.as_ref().map_or(TypeId::ANY_TYPE, |(var_ty, _)| *var_ty); - let value_ty = - super::expressions::synthesise_expression(value, environment, checking_data, expecting); + // Crazy JavaScript behavior!!! + let expected: TypeId = if let ( + VariableField::Name(name), + Expression::ExpressionFunction(_) | Expression::ClassExpression(_), + ) = (variable_declaration.name.get_ast_ref(), expression) + { + let name = checking_data.types.new_constant_type(crate::Constant::String( + name.as_option_str().unwrap_or_default().to_owned(), + )); + features::functions::new_name_expected_object( + name, + expected, + &mut checking_data.types, + environment, + ) + } else { + expected + }; + + let value_ty = super::expressions::synthesise_expression( + expression, + environment, + checking_data, + expected, + ); if let Some((var_ty, ta_pos)) = var_ty_and_pos { let is_valid = crate::features::variables::check_variable_initialization( (var_ty, ta_pos), - (value_ty, value.get_position().with_source(environment.get_source())), + (value_ty, expression.get_position().with_source(environment.get_source())), environment, checking_data, ); - // crate::utilities::notify!("{:?} {:?}", is_valid, variable_declaration); - if !is_valid || value_ty == TypeId::ERROR_TYPE { // If error, then create a new type like the annotation checking_data.types.new_error_type(var_ty) } else { value_ty } + } else if infer_constraint { + let constraint = get_larger_type(value_ty, &checking_data.types); + + if let VariableField::Name(n) = variable_declaration.name.get_ast_ref() { + if let VariableOrImport::Variable { + mutability: VariableMutability::Mutable { reassignment_constraint }, + .. + } = environment.variables.get_mut(n.as_option_str().unwrap_or_default()).unwrap() + { + let _ = reassignment_constraint.insert(constraint); + } + } else { + crate::utilities::notify!( + "Infer constraint on {:?}", + variable_declaration.name.get_ast_ref() + ); + } + + value_ty } else { value_ty } @@ -230,8 +319,15 @@ fn assign_initial_to_fields( ) { match item { VariableField::Name(name) => { - let get_position = name.get_position(); - let id = crate::VariableId(environment.get_source(), get_position.start); + let position = name.get_position(); + let id = if let Some(aliases) = + checking_data.local_type_mappings.var_aliases.get(&position.start) + { + *aliases + } else { + crate::VariableId(environment.get_source(), position.start) + }; + environment.register_initial_variable_declaration_value(id, value); if let Some(mutability) = exported { @@ -242,7 +338,7 @@ fn assign_initial_to_fields( VariableIdentifier::Standard(ref name, _) => name.to_owned(), VariableIdentifier::Marker(_, _) => "?".to_owned(), }; - exported.named.push((name, (id, mutability))); + exported.named.insert(name, (id, mutability)); } else { checking_data.diagnostics_container.add_error( TypeCheckError::NonTopLevelExport( @@ -254,38 +350,15 @@ fn assign_initial_to_fields( } VariableField::Array { members: _, spread: _, position } => { checking_data.raise_unimplemented_error( - "destructuring array (needs iterator)", + "array spread", position.with_source(environment.get_source()), ); - // for (idx, item) in items.iter().enumerate() { - // match item.get_ast_ref() { - // ArrayDestructuringField::Spread(_, _) => todo!(), - // ArrayDestructuringField::Name(variable_field, _) => { - // let idx = PropertyKey::from_usize(idx); - - // let value = environment.get_property( - // value, - // Publicity::Public, - // idx, - // &mut checking_data.types, - // None, - // *variable_field.get_position(), - // &checking_data.options, - // ); + // let position = position.with_source(environment.get_source()); - // if let Some((_, value)) = value { - // assign_to_fields( - // variable_field, - // environment, - // checking_data, - // value, - // exported, - // ); - // } - // ArrayDestructuringField::Comment { .. } | ArrayDestructuringField::None => {} - // } + // if let Some(spread) = spread { + // } } - VariableField::Object { members, spread, .. } => { + VariableField::Object { members, spread, position } => { for member in members { match member.get_ast_ref() { ObjectDestructuringField::Name(name, _, default_value, _) => { @@ -301,19 +374,17 @@ fn assign_initial_to_fields( // TODO if LHS = undefined ...? conditional // TODO record information - let property = environment.get_property( + let property = environment.get_property_handle_errors( value, Publicity::Public, &key_ty, - &mut checking_data.types, - None, + checking_data, position, - &checking_data.options, - false, + crate::types::properties::AccessMode::DoNotBindThis, ); let value = match property { - Some((_, value)) => value, - None => { + Ok(instance) => instance.get_value(), + Err(()) => { if let Some(else_expression) = default_value { synthesise_expression( else_expression, @@ -326,10 +397,12 @@ fn assign_initial_to_fields( TypeCheckError::PropertyDoesNotExist { property: match key_ty { PropertyKey::String(s) => { - PropertyRepresentation::StringKey(s.to_string()) + PropertyKeyRepresentation::StringKey( + s.to_string(), + ) } PropertyKey::Type(t) => { - PropertyRepresentation::Type( + PropertyKeyRepresentation::Type( printing::print_type( t, &checking_data.types, @@ -345,10 +418,10 @@ fn assign_initial_to_fields( &checking_data.types, false, ), - site: position, + position, possibles: get_property_key_names_on_a_single_type( value, - &mut checking_data.types, + &checking_data.types, environment, ) .iter() @@ -356,8 +429,7 @@ fn assign_initial_to_fields( .collect::>(), }, ); - - TypeId::ERROR_TYPE + TypeId::ANY_TYPE } } }; @@ -379,22 +451,18 @@ fn assign_initial_to_fields( ); // TODO if LHS = undefined ...? conditional - // TODO record information - let property_value = environment.get_property( + let property_value = environment.get_property_handle_errors( value, Publicity::Public, - // TODO different above &key_ty, - &mut checking_data.types, - None, + checking_data, position.with_source(environment.get_source()), - &checking_data.options, - false, + crate::types::properties::AccessMode::DoNotBindThis, ); let value = match property_value { - Some((_, value)) => value, - None => { + Ok(instance) => instance.get_value(), + Err(()) => { if let Some(default_value) = default_value { synthesise_expression( default_value, @@ -407,10 +475,12 @@ fn assign_initial_to_fields( TypeCheckError::PropertyDoesNotExist { property: match key_ty { PropertyKey::String(s) => { - PropertyRepresentation::StringKey(s.to_string()) + PropertyKeyRepresentation::StringKey( + s.to_string(), + ) } PropertyKey::Type(t) => { - PropertyRepresentation::Type( + PropertyKeyRepresentation::Type( printing::print_type( t, &checking_data.types, @@ -426,10 +496,11 @@ fn assign_initial_to_fields( &checking_data.types, false, ), - site: position.with_source(environment.get_source()), + position: position + .with_source(environment.get_source()), possibles: get_property_key_names_on_a_single_type( value, - &mut checking_data.types, + &checking_data.types, environment, ) .iter() @@ -454,7 +525,10 @@ fn assign_initial_to_fields( } } if let Some(_spread) = spread { - todo!() + checking_data.raise_unimplemented_error( + "spread variable field", + position.with_source(environment.get_source()), + ); } } } diff --git a/checker/src/type_mappings.rs b/checker/src/type_mappings.rs index 063f753b..3e94406e 100644 --- a/checker/src/type_mappings.rs +++ b/checker/src/type_mappings.rs @@ -1,14 +1,10 @@ -use std::{collections::HashMap, path::PathBuf}; - -use source_map::{SourceId, SpanWithSource}; - -use super::range_map::RangeMap; - use crate::{ features::variables::VariableWithValue, types::{TypeId, TypeStore}, - GeneralContext, VariableId, + GeneralContext, RangeMap, VariableId, }; +use source_map::{SourceId, SpanWithSource}; +use std::{collections::HashMap, path::PathBuf}; /// **PER MODULE** /// [`TypeMappings`] is used to retaining information between passes, including the synthesise and checking passes @@ -28,6 +24,9 @@ pub struct TypeMappings { pub types_to_types: RangeMap, pub import_statements_to_pointing_path: RangeMap, + /// For places where there are two `var` statements in + pub var_aliases: HashMap, + /// Variable restriction. Cached after hoisting pass. TODO temp needs tidy pub variable_restrictions: HashMap<(SourceId, u32), (TypeId, SpanWithSource)>, /// Temp diff --git a/checker/src/types/calling.rs b/checker/src/types/calling.rs index fda0606c..b902e74c 100644 --- a/checker/src/types/calling.rs +++ b/checker/src/types/calling.rs @@ -1,45 +1,152 @@ use source_map::{BaseSpan, Nullable, SpanWithSource}; use crate::{ - context::{ - information::InformationChain, invocation::CheckThings, CallCheckingBehavior, Environment, - Logical, MissingOrToCalculate, PossibleLogical, + context::{invocation::CheckThings, CallCheckingBehavior, Environment, InformationChain}, + diagnostics::{ + InfoDiagnostic, TypeCheckError, TypeCheckWarning, TypeStringRepresentation, TDZ, + }, + events::{ + application::ApplicationInput, apply_events, ApplicationResult, Event, RootReference, }, - diagnostics::{TypeCheckError, TypeStringRepresentation, TDZ}, - events::{application::ErrorsAndInfo, apply_events, ApplicationResult, Event, RootReference}, features::{ constant_functions::{ call_constant_function, CallSiteTypeArguments, ConstantFunctionError, ConstantOutput, }, - functions::{FunctionBehavior, ThisValue}, - objects::{ObjectBuilder, SpecialObjects}, + objects::{ObjectBuilder, SpecialObject}, }, subtyping::{ type_is_subtype, type_is_subtype_with_generics, State, SubTypeResult, SubTypingMode, SubTypingOptions, }, types::{ - functions::SynthesisedArgument, generics::substitution::SubstitutionArguments, - get_structure_arguments_based_on_object_constraint, substitute, FunctionEffect, - FunctionType, GenericChainLink, ObjectNature, PartiallyAppliedGenerics, Type, + functions::{FunctionBehavior, FunctionEffect, FunctionType}, + generics::substitution::SubstitutionArguments, + get_structure_arguments_based_on_object_constraint, + logical::{Invalid, Logical, LogicalOrValid, NeedsCalculation, PossibleLogical}, + properties::AccessMode, + substitute, GenericChainLink, ObjectNature, PartiallyAppliedGenerics, Type, }, FunctionId, GenericTypeParameters, ReadFromFS, SpecialExpressions, TypeId, }; use super::{ generics::{contributions::Contributions, generic_type_arguments::GenericArguments}, - get_constraint, is_type_constant, + get_constraint, properties::PropertyKey, Constructor, GenericChain, PolyNature, TypeRestrictions, TypeStore, }; +/// Other information to do with calling +#[derive(Clone, Copy)] pub struct CallingInput { /// Also depicts what happens with `this` pub called_with_new: CalledWithNew, - pub call_site_type_arguments: Option>, pub call_site: SpanWithSource, + + /// An option to invocation + pub max_inline: u16, +} + +#[derive(Clone, Copy, Debug, Default, binary_serialize_derive::BinarySerializable)] +pub enum ThisValue { + Passed(TypeId), + /// Or pick from [`Constructor::Property`] + #[default] + UseParent, +} + +impl ThisValue { + pub(crate) fn get( + self, + environment: &mut Environment, + types: &TypeStore, + position: SpanWithSource, + ) -> TypeId { + match self { + ThisValue::Passed(value) => value, + ThisValue::UseParent => environment.get_value_of_this(types, position), + } + } + + pub(crate) fn get_passed(self) -> Option { + match self { + ThisValue::Passed(value) => Some(value), + ThisValue::UseParent => None, + } + } +} + +/// For diagnostics +#[derive(Copy, Clone, Default)] +pub enum CallingContext { + #[default] + Regular, + Getter, + Setter, + JSX, + TemplateLiteral, + Super, + Iteration, +} + +#[derive(Default)] +pub struct CallingDiagnostics { + pub errors: Vec, + pub warnings: Vec, + pub info: Vec, +} + +impl CallingDiagnostics { + pub(crate) fn append_to( + self, + context: CallingContext, + diagnostics_container: &mut crate::DiagnosticsContainer, + ) { + self.errors.into_iter().for_each(|error| { + let error = match context { + CallingContext::Regular => TypeCheckError::FunctionCallingError(error), + CallingContext::JSX => TypeCheckError::JSXCallingError(error), + CallingContext::TemplateLiteral => { + TypeCheckError::TemplateLiteralCallingError(error) + } + CallingContext::Getter => TypeCheckError::GetterCallingError(error), + CallingContext::Setter => TypeCheckError::SetterCallingError(error), + CallingContext::Super => TypeCheckError::SuperCallError(error), + // TODO maybe separate error (even thoguh this should not occur) + #[allow(clippy::match_same_arms)] + CallingContext::Iteration => TypeCheckError::FunctionCallingError(error), + }; + diagnostics_container.add_error(error); + }); + self.warnings.into_iter().for_each(|warning| diagnostics_container.add_warning(warning)); + self.info.into_iter().for_each(|crate::diagnostics::InfoDiagnostic(message, position)| { + diagnostics_container.add_info(crate::diagnostics::Diagnostic::Position { + reason: message, + position, + kind: crate::diagnostics::DiagnosticKind::Info, + }); + }); + } +} + +#[derive(Clone, Debug, binary_serialize_derive::BinarySerializable)] +pub struct SynthesisedArgument { + pub(crate) spread: bool, + pub(crate) value: TypeId, + pub(crate) position: SpanWithSource, } +impl SynthesisedArgument { + pub fn non_spread_type(&self) -> Result { + if self.spread { + Err(()) + } else { + Ok(self.value) + } + } +} + +/// Intermediate step for inference pub struct UnsynthesisedArgument<'a, A: crate::ASTImplementation> { pub spread: bool, pub expression: &'a A::Expression<'a>, @@ -48,8 +155,8 @@ pub struct UnsynthesisedArgument<'a, A: crate::ASTImplementation> { /// Intermediate type for calling a function /// /// Generic arguments handled with `Logical::Implies` -#[derive(Debug)] -struct FunctionLike { +#[derive(Debug, Clone, binary_serialize_derive::BinarySerializable)] +pub struct FunctionLike { pub(crate) function: FunctionId, /// For generic calls pub(crate) from: Option, @@ -57,13 +164,10 @@ struct FunctionLike { pub(crate) this_value: ThisValue, } -/// TODO *result* name bad pub struct CallingOutput { pub called: Option, /// TODO should this be pub result: Option, - /// TODO is [`InfoDiagnostic`] the best type here? - pub warnings: Vec, pub special: Option, // pub special: Option, pub result_was_const_computation: bool, @@ -72,12 +176,12 @@ pub struct CallingOutput { pub struct BadCallOutput { /// This might be from specialisation pub returned_type: TypeId, - pub errors: Vec, } /// Also synthesise arguments in terms of expected types pub fn call_type_handle_errors( ty: TypeId, + call_site_type_arguments: Option>, arguments: &[UnsynthesisedArgument], input: CallingInput, environment: &mut Environment, @@ -90,7 +194,7 @@ pub fn call_type_handle_errors { + Ok(LogicalOrValid::Logical(callable)) => { // crate::utilities::notify!("{:?}", callable); // Fix as `.map` doesn't get this passed down @@ -134,50 +238,32 @@ pub fn call_type_handle_errors { - for warning in warnings { - checking_data.diagnostics_container.add_info( - crate::diagnostics::Diagnostic::Position { - reason: warning.0, - position: call_site, - kind: crate::diagnostics::DiagnosticKind::Info, - }, - ); - } - - // if thrown_type != TypeId::NEVER_TYPE { - // todo!() - // // environment.context_type.state.append_termination(FinalEvent::Throw { - // // thrown: thrown_type, - // // position: call_site, - // // }); - // } - let returned_type = application_result_to_return_type( result, environment, @@ -186,24 +272,22 @@ pub fn call_type_handle_errors { - for error in error.errors { - checking_data - .diagnostics_container - .add_error(TypeCheckError::FunctionCallingError(error)); - } - (error.returned_type, None) - } + Err(error) => (error.returned_type, None), } } - Err(MissingOrToCalculate::Error) => (TypeId::ERROR_TYPE, None), - Err(MissingOrToCalculate::Infer { on: _ }) => { - todo!("function calling inference") - } - Err(MissingOrToCalculate::Proxy(..)) => { - todo!("calling proxy") - } - Err(MissingOrToCalculate::Missing) => { + Ok(LogicalOrValid::NeedsCalculation(l)) => match l { + NeedsCalculation::Infer { on } => { + if on == TypeId::ERROR_TYPE { + (TypeId::ERROR_TYPE, None) + } else { + todo!("function calling inference") + } + } + NeedsCalculation::Proxy(..) => { + todo!("calling proxy") + } + }, + Err(Invalid(ty)) => { checking_data.diagnostics_container.add_error(TypeCheckError::FunctionCallingError( FunctionCallingError::NotCallable { calling: TypeStringRepresentation::from_type_id( @@ -231,7 +315,7 @@ pub fn application_result_to_return_type( // TODO ApplicationResult::Return { returned, position: _ } => returned, ApplicationResult::Throw { thrown, position } => { - environment.throw_value(thrown, position); + environment.throw_value(thrown, position, types); TypeId::NEVER_TYPE } ApplicationResult::Yield {} => todo!("Create generator object"), @@ -251,18 +335,128 @@ pub fn application_result_to_return_type( } } -/// In events -pub(crate) fn call_type( - on: TypeId, - arguments: Vec, - input: &CallingInput, - top_environment: &mut Environment, - behavior: &mut E, - types: &mut TypeStore, -) -> Result { - // input.this_value, - let callable = get_logical_callable_from_type(on, None, None, types).ok(); - try_call_logical(callable, input, arguments, None, top_environment, types, behavior, on) +#[derive(Debug, Copy, Clone, binary_serialize_derive::BinarySerializable)] +pub enum Callable { + /// TODO `ThisValue` not always needed + Fixed(FunctionId, ThisValue), + Type(TypeId), +} + +impl Callable { + pub(crate) fn from_type(ty: TypeId, types: &TypeStore) -> Self { + if let Type::SpecialObject(SpecialObject::Function(func_id, _)) = types.get_type_by_id(ty) { + Callable::Fixed(*func_id, ThisValue::UseParent) + } else { + crate::utilities::notify!("Here!!"); + Callable::Type(ty) + } + } + + pub(crate) fn new_from_function(function: FunctionType, types: &mut TypeStore) -> Self { + let id = function.id; + types.functions.insert(id, function); + Callable::Fixed(id, ThisValue::UseParent) + } + + pub(crate) fn into_type(self, types: &mut TypeStore) -> TypeId { + match self { + Callable::Fixed(id, this_value) => { + types.register_type(Type::SpecialObject(SpecialObject::Function(id, this_value))) + } + Callable::Type(ty) => ty, + } + } + + pub(crate) fn get_return_type(self, types: &TypeStore) -> TypeId { + match self { + Callable::Fixed(id, _this_value) => types.get_function_from_id(id).return_type, + Callable::Type(ty) => { + if let Type::SpecialObject(SpecialObject::Function(id, _)) = + types.get_type_by_id(ty) + { + types.get_function_from_id(*id).return_type + } else { + crate::utilities::notify!("Cannot get return type"); + TypeId::ERROR_TYPE + } + } + } + } + + pub(crate) fn get_first_argument(self, types: &TypeStore) -> TypeId { + match self { + Callable::Fixed(id, _this_value) => types + .get_function_from_id(id) + .parameters + .get_parameter_type_at_index(0) + .map_or(TypeId::ERROR_TYPE, |(ty, _)| ty), + Callable::Type(ty) => { + if let Type::SpecialObject(SpecialObject::Function(id, _)) = + types.get_type_by_id(ty) + { + types + .get_function_from_id(*id) + .parameters + .get_parameter_type_at_index(0) + .map_or(TypeId::ERROR_TYPE, |(ty, _)| ty) + } else { + crate::utilities::notify!("Cannot get first arugment"); + TypeId::ERROR_TYPE + } + } + } + } + + pub(crate) fn call( + self, + arguments: Vec, + input: CallingInput, + top_environment: &mut Environment, + (behavior, diagnostics): (&mut B, &mut CallingDiagnostics), + types: &mut TypeStore, + ) -> Result { + match self { + Callable::Fixed(on, this_value) => { + let function_like = FunctionLike { function: on, from: None, this_value }; + + call_logical( + Logical::Pure(function_like), + (arguments, None, None), + input, + top_environment, + types, + (behavior, diagnostics), + ) + } + Callable::Type(on) => { + let callable = get_logical_callable_from_type(on, None, None, types); + if let Ok(LogicalOrValid::Logical(callable)) = callable { + call_logical( + callable, + (arguments, None, None), + input, + top_environment, + types, + (behavior, diagnostics), + ) + } else { + crate::utilities::notify!("callable={:?}", callable); + + diagnostics.errors.push(FunctionCallingError::NotCallable { + calling: crate::diagnostics::TypeStringRepresentation::from_type_id( + on, + top_environment, + types, + behavior.debug_types(), + ), + call_site: input.call_site, + }); + + Err(BadCallOutput { returned_type: TypeId::ERROR_TYPE }) + } + } + } + } } /// This could possibly be done in one step @@ -272,30 +466,30 @@ fn get_logical_callable_from_type( from: Option, types: &TypeStore, ) -> PossibleLogical { - if ty == TypeId::ERROR_TYPE { - return Err(MissingOrToCalculate::Error); - } + // if ty == TypeId::ERROR_TYPE { + // return Err(MissingOrToCalculate::Error); + // } if ty == TypeId::ANY_TYPE { - return Err(MissingOrToCalculate::Infer { on: from.unwrap() }); + crate::utilities::notify!("Calling ANY"); + return Ok(NeedsCalculation::Infer { on: from.unwrap() }.into()); } let le_ty = types.get_type_by_id(ty); - // crate::utilities::notify!("ty1={:?} ({:?})", le_ty, ty); - match le_ty { Type::Class { .. } | Type::Interface { .. } | Type::Constant(_) | Type::Object(_) => { - Err(MissingOrToCalculate::Missing) + Err(Invalid(ty)) } Type::And(_, _) => todo!(), Type::Or(left, right) => { - let left = get_logical_callable_from_type(*left, on, from, types); - let right = get_logical_callable_from_type(*right, on, from, types); + let left = get_logical_callable_from_type(*left, on, from, types)?; + let right = get_logical_callable_from_type(*right, on, from, types)?; Ok(Logical::Or { - condition: TypeId::BOOLEAN_TYPE, + condition: TypeId::OPEN_BOOLEAN_TYPE, left: Box::new(left), right: Box::new(right), - }) + } + .into()) } Type::AliasTo { to, name: _, parameters } => { if parameters.is_some() { @@ -310,34 +504,35 @@ fn get_logical_callable_from_type( from, this_value: on.unwrap_or(ThisValue::UseParent), }; - Ok(Logical::Pure(function)) + Ok(Logical::Pure(function).into()) } - Type::SpecialObject(SpecialObjects::Function(f, t)) => { + Type::SpecialObject(SpecialObject::Function(f, t)) => { let this_value = on.unwrap_or(*t); let from = Some(from.unwrap_or(ty)); - Ok(Logical::Pure(FunctionLike { from, function: *f, this_value })) - } - Type::SpecialObject(SpecialObjects::ClassConstructor { constructor, .. }) => { - Ok(Logical::Pure(FunctionLike { - from, - function: *constructor, - this_value: ThisValue::UseParent, - })) + Ok(Logical::Pure(FunctionLike { from, function: *f, this_value }).into()) } Type::SpecialObject(so) => match so { - crate::features::objects::SpecialObjects::Proxy { .. } => todo!(), - _ => Err(MissingOrToCalculate::Missing), + crate::features::objects::SpecialObject::Proxy { .. } => todo!(), + _ => Err(Invalid(ty)), }, Type::PartiallyAppliedGenerics(generic) => { - get_logical_callable_from_type(generic.on, on, from, types).map(|res| { - crate::utilities::notify!("Calling found {:?}", generic.arguments); - Logical::Implies { on: Box::new(res), antecedent: generic.arguments.clone() } - }) + let result = get_logical_callable_from_type(generic.on, on, from, types)?; + if let LogicalOrValid::Logical(result) = result { + Ok(Logical::Implies { on: Box::new(result), antecedent: generic.arguments.clone() } + .into()) + } else { + crate::utilities::notify!("Should not be here"); + Ok(result) + } } - Type::Constructor(Constructor::Property { on, under: _, result, bind_this }) => { + Type::Constructor(Constructor::Property { on, under: _, result, mode }) => { crate::utilities::notify!("Passing {:?}", on); - let this_value = if *bind_this { ThisValue::Passed(*on) } else { ThisValue::UseParent }; + let this_value = if let AccessMode::DoNotBindThis = mode { + ThisValue::UseParent + } else { + ThisValue::Passed(*on) + }; let result = get_logical_callable_from_type(*result, Some(this_value), Some(ty), types)?; @@ -350,86 +545,228 @@ fn get_logical_callable_from_type( }); if let Some(antecedent) = and_then { - Ok(Logical::Implies { on: Box::new(result), antecedent }) + if let LogicalOrValid::Logical(result) = result { + Ok(Logical::Implies { on: Box::new(result), antecedent }.into()) + } else { + crate::utilities::notify!("TODO not antecedent {:?}", antecedent); + Ok(result) + } } else { Ok(result) } } Type::RootPolyType(_) | Type::Constructor(_) => { let constraint = get_constraint(ty, types).unwrap(); - crate::utilities::notify!("Calling constructor / root poly type! {:?}", constraint); + crate::utilities::notify!( + "Getting callable constructor / root poly type! {:?}", + constraint + ); get_logical_callable_from_type(constraint, on, Some(ty), types) } } } #[allow(clippy::too_many_arguments)] -fn try_call_logical( - callable: Option>, - input: &CallingInput, - arguments: Vec, - type_argument_restrictions: Option, - top_environment: &mut Environment, - types: &mut TypeStore, - behavior: &mut E, - on: TypeId, -) -> Result { - if let Some(logical) = callable { - call_logical( - logical, - input.called_with_new, - input.call_site, - type_argument_restrictions, - None, - arguments, - top_environment, - types, - behavior, - ) - } else { - Err(BadCallOutput { - returned_type: TypeId::ERROR_TYPE, - errors: vec![FunctionCallingError::NotCallable { - calling: crate::diagnostics::TypeStringRepresentation::from_type_id( - on, - top_environment, - types, - behavior.debug_types(), - ), - call_site: input.call_site, - }], - }) - } -} - -#[allow(clippy::too_many_arguments)] -fn call_logical( +fn call_logical( logical: Logical, - called_with_new: CalledWithNew, - call_site: SpanWithSource, - explicit_type_arguments: Option, - structure_generics: Option, - arguments: Vec, + (arguments, explicit_type_arguments, structure_generics): ( + Vec, + Option, + Option, + ), + input: CallingInput, top_environment: &mut Environment, types: &mut TypeStore, - behavior: &mut E, + (behavior, diagnostics): (&mut B, &mut CallingDiagnostics), ) -> Result { match logical { Logical::Pure(function) => { if let Some(function_type) = types.functions.get(&function.function) { + // TODO clone let function_type = function_type.clone(); + if let FunctionEffect::Constant { identifier: ref const_fn_ident, .. } = + &function_type.effect + { + let has_dependent_argument = + arguments.iter().any(|arg| types.get_type_by_id(arg.value).is_dependent()); + + // || matches!(this_value, ThisValue::Passed(ty) if types.get_type_by_id(ty).is_dependent()); + + crate::utilities::notify!( + "has_dependent_argument={:?}, const_fn_ident={:?}", + has_dependent_argument, + const_fn_ident + ); + + // TODO just for debugging. These have their constant things called every time AND queue an event + let is_independent_function = const_fn_ident.ends_with("independent"); + + let call_anyway = const_fn_ident.starts_with("debug") + || const_fn_ident.starts_with("print") + || is_independent_function + || matches!( + const_fn_ident.as_str(), + "satisfies" | "is_dependent" | "bind" | "proxy:constructor" + ); + + // { + // let arguments = arguments + // .iter() + // .map(|a| types.get_type_by_id(a.value)) + // .collect::>(); + + // crate::utilities::notify!( + // "E::CHECK_PARAMETERS={:?}, args={:?}, ident={:?}", + // E::CHECK_PARAMETERS, + // arguments, + // const_fn_ident + // ); + // } + + // For debugging only + if is_independent_function && B::CHECK_PARAMETERS { + let function_id = function.function; + let value = Event::CallsType { + on: Callable::Fixed(function.function, function.this_value), + with: arguments.clone().into_boxed_slice(), + timing: crate::events::CallingTiming::Synchronous, + called_with_new: input.called_with_new, + reflects_dependency: None, + call_site: input.call_site, + possibly_thrown: None, + }; + behavior.get_latest_info(top_environment).events.push(value); + return Ok(CallingOutput { + called: Some(function_id), + result: None, + special: Some(SpecialExpressions::Marker), + result_was_const_computation: true, + }); + } + + // Most of the time don't call using constant function if an argument is dependent. + // But sometimes do for the cases above + if call_anyway || !has_dependent_argument { + // TODO event + let result = call_constant_function( + const_fn_ident, + // TODO + function.this_value, + explicit_type_arguments.as_ref(), + &arguments, + types, + top_environment, + input.call_site, + diagnostics, + ); + + match result { + Ok(ConstantOutput::Value(value)) => { + // Very special + let special = if const_fn_ident == "compile_type_to_object" { + Some(SpecialExpressions::CompileOut) + } else { + None + }; + return Ok(CallingOutput { + result: Some(ApplicationResult::Return { + returned: value, + position: input.call_site, + }), + called: None, + result_was_const_computation: !is_independent_function, + special, + }); + } + Ok(ConstantOutput::Diagnostic(diagnostic)) => { + diagnostics.info.push(InfoDiagnostic(diagnostic, input.call_site)); + return Ok(CallingOutput { + result: None, + called: None, + result_was_const_computation: !is_independent_function, + // WIP!! + special: Some(SpecialExpressions::Marker), + }); + } + Err(ConstantFunctionError::NoLogicForIdentifier(name)) => { + let err = FunctionCallingError::NoLogicForIdentifier( + name, + input.call_site, + ); + diagnostics.errors.push(err); + return Err(BadCallOutput { + returned_type: types.new_error_type(function_type.return_type), + }); + } + Err(ConstantFunctionError::FunctionCallingError(err)) => { + diagnostics.errors.push(err); + return Err(BadCallOutput { + returned_type: types.new_error_type(function_type.return_type), + }); + } + Err(ConstantFunctionError::BadCall) => { + crate::utilities::notify!( + "Constant function calling failed, non constant params" + ); + } + } + } else if !has_dependent_argument { + // TODO with cloned!! + let call = function_type.call( + ( + function.this_value, + &arguments, + explicit_type_arguments, + structure_generics, + ), + input, + top_environment, + (behavior, diagnostics), + types, + ); + + return match call { + Ok(call) => Ok(CallingOutput { + result: call.result, + called: None, + special: None, + result_was_const_computation: false, + // TODO + }), + Err(err) => { + crate::utilities::notify!( + "Calling function with dependent argument failed" + ); + Err(err) + } + }; + } + } + + // Temp fix for structure generics, doesn't work in access + let structure_generics = if let (false, Some(on)) = + (structure_generics.is_some(), function.this_value.get_passed()) + { + if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: _, + arguments, + }) = types.get_type_by_id(get_constraint(on, types).unwrap_or(on)) + { + Some(arguments.clone()) + } else { + None + } + } else { + structure_generics + }; + let mut result = function_type.call( - called_with_new, - function.this_value, - call_site, - &arguments, - explicit_type_arguments, - structure_generics, + (function.this_value, &arguments, explicit_type_arguments, structure_generics), + input, top_environment, - behavior, + (behavior, diagnostics), types, - true, )?; // crate::utilities::notify!("result {:?} {:?}", result.called, result.returned_type); @@ -441,16 +778,15 @@ fn call_logical( ) || (matches!(function_type.effect, FunctionEffect::Constant { .. } if !result.result_was_const_computation)); if is_poly_or_failed_const_call && function.from.is_some() { - let on = function.from.unwrap(); - // if function_type.effect // This should be okay, constant or IO functions don't mutate their arguments...? - if !matches!( - &function_type.effect, - FunctionEffect::Constant { .. } | FunctionEffect::InputOutput { .. } - ) { - find_possible_mutations(&arguments, types, top_environment); - } + // if !matches!( + // &function_type.effect, + // FunctionEffect::Constant { .. } | FunctionEffect::InputOutput { .. } + // ) { + // for a + // mark_possible_mutation(&arguments, types, top_environment); + // } // match function_type.effect { // FunctionEffect::SideEffects { .. } => unreachable!(), @@ -478,11 +814,13 @@ fn call_logical( top_environment, types, ); - let reflects_dependency = if is_type_constant(result_as_type, types) { + let reflects_dependency = if types.get_type_by_id(result_as_type).is_constant() + { None } else { let id = types.register_type(Type::Constructor(Constructor::Image { - on, + // TODO + on: function.from.expect("function `on`"), // TODO... with: with.clone(), result: result_as_type, @@ -493,25 +831,29 @@ fn call_logical( result.result = Some(ApplicationResult::Return { returned: reflects_dependency.unwrap_or(result_as_type), - position: call_site, + position: input.call_site, }); let possibly_thrown = if let FunctionEffect::InputOutput { may_throw, .. } | FunctionEffect::Constant { may_throw, .. } = &function_type.effect { - crate::utilities::notify!("Here"); + // crate::utilities::notify!("Constant / input output: may_throw"); *may_throw } else { None }; + let on = match function.from { + Some(ty) => Callable::Type(ty), + None => Callable::Fixed(function.function, function.this_value), + }; behavior.get_latest_info(top_environment).events.push(Event::CallsType { on, with, timing: crate::events::CallingTiming::Synchronous, - called_with_new, + called_with_new: input.called_with_new, reflects_dependency, - position: call_site, + call_site: input.call_site, possibly_thrown, }); } @@ -521,111 +863,106 @@ fn call_logical( panic!() } } - Logical::Or { condition: based_on, left, right } => { - if let (Ok(_left), Ok(_right)) = (*left, *right) { - // let (truthy_result, otherwise_result) = behavior.evaluate_conditionally( - // top_environment, - // types, - // based_on, - // (left, right), - // (explicit_type_arguments, structure_generics, arguments), - // |top_environment, types, target, func, data| { - // let (explicit_type_arguments, structure_generics, arguments) = data; - // let result = call_logical( - // func, - // called_with_new, - // call_site, - // explicit_type_arguments.clone(), - // structure_generics.clone(), - // arguments.clone(), - // top_environment, - // types, - // target, - // ); - - // result.map(|result| { - // application_result_to_return_type( - // result.result, - // top_environment, - // types, - // ) - // }) - // }, - // ); - // let truthy_result = truthy_result?; - // let otherwise_result = otherwise_result?; - // // truthy_result.warnings.append(&mut otherwise_result.warnings); - // let _result = - // types.new_conditional_type(based_on, truthy_result, otherwise_result); - - todo!(); + Logical::Or { condition, left, right } => { + todo!("{:?}", (condition, left, right)); + // if let (Ok(_left), Ok(_right)) = (*left, *right) { + // let (truthy_result, otherwise_result) = behavior.evaluate_conditionally( + // top_environment, + // types, + // based_on, + // (left, right), + // (explicit_type_arguments, structure_generics, arguments), + // |top_environment, types, target, func, data| { + // let (explicit_type_arguments, structure_generics, arguments) = data; + // let result = call_logical( + // func, + // called_with_new, + // call_site, + // explicit_type_arguments.clone(), + // structure_generics.clone(), + // arguments.clone(), + // top_environment, + // types, + // target, + // ); + + // result.map(|result| { + // application_result_to_return_type( + // result.result, + // top_environment, + // types, + // ) + // }) + // }, + // ); + // let truthy_result = truthy_result?; + // let otherwise_result = otherwise_result?; + // // truthy_result.warnings.append(&mut otherwise_result.warnings); + // let _result = + // types.new_conditional_type(based_on, truthy_result, otherwise_result); + // TODO combine information (merge) // Ok(CallingOutput { called: None, result: Soe, warnings: (), special: (), result_was_const_computation: () }) - } else { - // TODO inference and errors - let err = FunctionCallingError::NotCallable { - calling: TypeStringRepresentation::from_type_id( - based_on, - top_environment, - types, - false, - ), - call_site, - }; - Err(BadCallOutput { returned_type: TypeId::ERROR_TYPE, errors: vec![err] }) - } + // } else { + // // TODO inference and errors + // let err = FunctionCallingError::NotCallable { + // calling: TypeStringRepresentation::from_type_id( + // based_on, + // top_environment, + // types, + // false, + // ), + // call_site: input.call_site, + // }; + // Err(BadCallOutput { returned_type: TypeId::ERROR_TYPE, errors: vec![err] }) + // } } - Logical::Implies { on, antecedent } => call_logical( - *on, - called_with_new, - call_site, - explicit_type_arguments, - Some(antecedent), - // this_value, - arguments, - top_environment, - types, - behavior, - ), + Logical::Implies { on, antecedent } => { + // crate::utilities::notify!("antecedent={:?}", antecedent); + call_logical( + *on, + (arguments, explicit_type_arguments, Some(antecedent)), + input, + top_environment, + types, + (behavior, diagnostics), + ) + } + Logical::BasedOnKey { .. } => todo!(), } } -fn find_possible_mutations( - arguments: &Vec, +fn mark_possible_mutation( + argument: &SynthesisedArgument, + parameter_type: TypeId, types: &mut TypeStore, top_environment: &mut Environment, ) { - for argument in arguments { - // TODO need to do in a function - // All properties - // Functions free variables etc - // TODO if spread - match types.get_type_by_id(argument.value) { - Type::Interface { .. } - | Type::Class { .. } - | Type::AliasTo { .. } - | Type::And(_, _) - | Type::Object(ObjectNature::AnonymousTypeAnnotation) - | Type::FunctionReference(_) - | Type::PartiallyAppliedGenerics(_) - | Type::Or(_, _) => { - crate::utilities::notify!("Unreachable"); - } - Type::Constant(_) => {} - Type::RootPolyType(_) | Type::Constructor(_) => { - // All dependent anyway - crate::utilities::notify!("TODO if any properties set etc"); - } - Type::SpecialObject(SpecialObjects::Function(_, _)) => { - crate::utilities::notify!("TODO record that function could be called"); - } - Type::Object(ObjectNature::RealDeal) => { - top_environment.possibly_mutated_objects.insert(argument.value); - crate::utilities::notify!("TODO record methods could be called here as well"); - } - Type::SpecialObject(_) => { - crate::utilities::notify!("TODO record stuff if mutable"); - } + match types.get_type_by_id(argument.value) { + Type::Interface { .. } + | Type::Class { .. } + | Type::AliasTo { .. } + | Type::And(_, _) + | Type::Object(ObjectNature::AnonymousTypeAnnotation) + | Type::FunctionReference(_) + | Type::PartiallyAppliedGenerics(_) + | Type::Or(_, _) => { + crate::utilities::notify!("Unreachable"); + } + Type::Constant(_) => {} + Type::RootPolyType(_) | Type::Constructor(_) => { + // All dependent anyway + crate::utilities::notify!("TODO if any properties set etc"); + } + Type::SpecialObject(SpecialObject::Function(_, _)) => { + crate::utilities::notify!("TODO record that function could be called"); + } + Type::Object(ObjectNature::RealDeal) => { + top_environment.possibly_mutated_objects.insert(argument.value, parameter_type); + crate::utilities::notify!("TODO record methods could be called here as well"); + } + Type::SpecialObject(_) => { + crate::utilities::notify!("TODO record stuff if mutable"); } } } @@ -667,20 +1004,27 @@ pub enum FunctionCallingError { NeedsToBeCalledWithNewKeyword(SpanWithSource), TDZ { error: TDZ, - /// Should be set + /// Should be set by parent call_site: SpanWithSource, }, + /// For #18 SetPropertyConstraint { property_type: TypeStringRepresentation, value_type: TypeStringRepresentation, assignment_position: SpanWithSource, - /// Should be set + /// Should be set by parent call_site: SpanWithSource, }, - /// TODO WIP - UnconditionalThrow { - value: TypeStringRepresentation, - /// Should be set + /// For #18 + DeleteConstraint { + constraint: TypeStringRepresentation, + delete_position: SpanWithSource, + /// Should be set by parent + call_site: SpanWithSource, + }, + NotConfiguarable { + property: crate::diagnostics::PropertyKeyRepresentation, + /// Should be set by parent call_site: SpanWithSource, }, MismatchedThis { @@ -695,8 +1039,6 @@ pub enum FunctionCallingError { }, } -pub struct InfoDiagnostic(pub String); - #[derive(Debug, Default, Clone, Copy, binary_serialize_derive::BinarySerializable)] pub enum CalledWithNew { /// With `new X(...)`; @@ -704,28 +1046,31 @@ pub enum CalledWithNew { // [See new.target](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new.target), which contains a reference to what called new. This does not == this_type on: TypeId, }, + GetterOrSetter { + this_type: TypeId, + }, /// With `super(...)` - Super { this_type: TypeId }, + Super { + this_type: TypeId, + }, #[default] None, } impl FunctionType { /// Calls the function and returns warnings and errors - #[allow(clippy::too_many_arguments)] - pub(crate) fn call( + pub(crate) fn call( &self, - called_with_new: CalledWithNew, - this_value: ThisValue, - call_site: SpanWithSource, - arguments: &[SynthesisedArgument], - call_site_type_arguments: Option, - parent_arguments: Option, + (this_value, arguments, call_site_type_arguments, parent_arguments): ( + ThisValue, + &[SynthesisedArgument], + Option, + Option, + ), + input: CallingInput, environment: &mut Environment, - behavior: &mut E, + (behavior, diagnostics): (&mut B, &mut CallingDiagnostics), types: &mut crate::TypeStore, - // This fixes recursive case of call_function - call_constant: bool, ) -> Result { // TODO check that parameters vary if behavior.in_recursive_cycle(self.id) { @@ -734,8 +1079,7 @@ impl FunctionType { let returned = types.new_error_type(self.return_type); return Ok(CallingOutput { called: Some(self.id), - result: Some(ApplicationResult::Return { returned, position: call_site }), - warnings: Vec::new(), + result: Some(ApplicationResult::Return { returned, position: input.call_site }), // TODO ? special: None, result_was_const_computation: false, @@ -748,119 +1092,7 @@ impl FunctionType { types.called_functions.insert(self.id); } - if let (FunctionEffect::Constant { identifier: ref const_fn_ident, .. }, true) = - (&self.effect, call_constant) - { - let has_dependent_argument = - arguments.iter().any(|arg| types.get_type_by_id(arg.value).is_dependent()); - - // || matches!(this_value, ThisValue::Passed(ty) if types.get_type_by_id(ty).is_dependent()); - - let call_anyway = matches!( - const_fn_ident.as_str(), - "debug_type" - | "debug_type_rust" | "print_type" - | "print_and_debug_type" - | "debug_effects" | "debug_effects_rust" - | "satisfies" | "is_dependent" - | "bind" | "create_proxy" - ); - - // TODO just for debugging. These have their constant things called every time AND queue an event - let is_independent = const_fn_ident.ends_with("independent"); - - // Most of the time don't call using constant function if an argument is dependent. - // But sometimes do for the cases above - if call_anyway || is_independent || !has_dependent_argument { - // TODO event - let result = call_constant_function( - const_fn_ident, - // TODO - this_value, - call_site_type_arguments.as_ref(), - arguments, - types, - environment, - call_site, - ); - - match result { - Ok(ConstantOutput::Value(value)) => { - // Very special - let special = if const_fn_ident == "compile_type_to_object" { - Some(SpecialExpressions::CompileOut) - } else { - None - }; - return Ok(CallingOutput { - result: Some(ApplicationResult::Return { - returned: value, - position: call_site, - }), - warnings: Default::default(), - called: None, - result_was_const_computation: !is_independent, - special, - }); - } - Ok(ConstantOutput::Diagnostic(diagnostic)) => { - // crate::utilities::notify!("Here, constant output"); - return Ok(CallingOutput { - result: None, - warnings: vec![InfoDiagnostic(diagnostic)], - called: None, - result_was_const_computation: !is_independent, - // TODO!! - special: Some(SpecialExpressions::Marker), - }); - } - Err(ConstantFunctionError::NoLogicForIdentifier(name)) => { - let item = FunctionCallingError::NoLogicForIdentifier(name, call_site); - return Err(BadCallOutput { - returned_type: types.new_error_type(self.return_type), - errors: vec![item], - }); - } - Err(ConstantFunctionError::BadCall) => { - crate::utilities::notify!( - "Constant function calling failed, non constant params" - ); - } - } - } else if has_dependent_argument { - // TODO with cloned!! - let call = self.call( - called_with_new, - this_value, - call_site, - arguments, - call_site_type_arguments, - parent_arguments, - environment, - behavior, - types, - // Very important! - false, - ); - - if let Err(ref _err) = call { - crate::utilities::notify!("Calling function with dependent argument failed"); - } - - let result = call?.result; - - return Ok(CallingOutput { - result, - warnings: Default::default(), - called: None, - special: None, - result_was_const_computation: false, - // TODO - }); - } - } - - let mut errors = ErrorsAndInfo::default(); + let errors = CallingDiagnostics::default(); let mut type_arguments = SubstitutionArguments { parent: None, @@ -873,36 +1105,30 @@ impl FunctionType { }; self.set_this_for_behavior( - called_with_new, + input.called_with_new, this_value, &mut type_arguments.arguments, environment, types, - &mut errors, - call_site, - behavior, + input.call_site, + (behavior, diagnostics), ); - self.assign_arguments_to_parameters::( + self.assign_arguments_to_parameters::( arguments, &mut type_arguments, - call_site_type_arguments.clone(), - parent_arguments.as_ref(), + (call_site_type_arguments.clone(), parent_arguments.as_ref()), environment, types, - &mut errors, - call_site, - behavior, + input.call_site, + matches!(self.effect, FunctionEffect::SideEffects { .. }), + (behavior, diagnostics), ); - if E::CHECK_PARAMETERS { - // TODO check free variables from inference - } - - if !errors.errors.is_empty() { + if !diagnostics.errors.is_empty() { return Err(BadCallOutput { + // TODO what about `new` returned_type: types.new_error_type(self.return_type), - errors: errors.errors, }); } @@ -916,7 +1142,6 @@ impl FunctionType { // crate::utilities::notify!("events: {:?}", events); { let substitution: &mut SubstitutionArguments = &mut type_arguments; - let errors: &mut ErrorsAndInfo = &mut errors; if !closed_over_variables.0.is_empty() { let closure_id = types.new_closure_id(); substitution.closures.push(closure_id); @@ -941,33 +1166,42 @@ impl FunctionType { } } - let current_errors = errors.errors.len(); + let current_errors = diagnostics.errors.len(); + let current_warnings = diagnostics.warnings.len(); // Apply events here let result = apply_events( events, - this_value, + &ApplicationInput { + this_value, + call_site: input.call_site, + max_inline: input.max_inline, + }, substitution, environment, target, types, - errors, - call_site, + diagnostics, ); // Adjust call sites. (because they aren't currently passed down) - for d in &mut errors.errors[current_errors..] { + for d in &mut diagnostics.errors[current_errors..] { if let FunctionCallingError::TDZ { call_site: ref mut c, .. } | FunctionCallingError::SetPropertyConstraint { call_site: ref mut c, .. + } = d + { + *c = input.call_site; } - | FunctionCallingError::UnconditionalThrow { + } + for d in &mut diagnostics.warnings[current_warnings..] { + if let TypeCheckWarning::ConditionalExceptionInvoked { call_site: ref mut c, .. } = d { - *c = call_site; + *c = input.call_site; } } @@ -984,58 +1218,56 @@ impl FunctionType { } } - crate::utilities::notify!( - "Substituting return type (no return) {:?}", - &type_arguments.arguments - ); + // crate::utilities::notify!( + // "Substituting return type (no return) type_arguments={:?}", + // type_arguments + // .arguments + // .iter() + // .map(|(k, v)| (types.get_type_by_id(*k), types.get_type_by_id(*v))) + // .collect::>() + // ); let returned = substitute(self.return_type, &type_arguments, environment, types); - Some(ApplicationResult::Return { returned, position: call_site }) + Some(ApplicationResult::Return { returned, position: input.call_site }) }; - if !errors.errors.is_empty() { + if !diagnostics.errors.is_empty() { crate::utilities::notify!("Got {} application errors", errors.errors.len()); return Err(BadCallOutput { + // TODO what about `new` returned_type: types.new_error_type(self.return_type), - errors: errors.errors, }); } // TODO what does super return? - if let CalledWithNew::New { .. } = called_with_new { + let result = if let CalledWithNew::New { .. } = input.called_with_new { // TODO ridiculous early return primitive rule - match self.behavior { - FunctionBehavior::ArrowFunction { is_async: _ } => todo!(), - FunctionBehavior::Method { .. } => todo!(), - FunctionBehavior::Constructor { prototype: _, this_object_type } - | FunctionBehavior::Function { - is_async: _, - is_generator: _, - free_this_id: this_object_type, - .. - } => { - let new_instance_type = - type_arguments.arguments.get(&this_object_type).expect("no this argument?"); - - // TODO if return type is primitive (or replace primitives with new_instance_type) - return Ok(CallingOutput { - result: Some(ApplicationResult::Return { - returned: *new_instance_type, - position: call_site, - }), - warnings: errors.warnings, - called: Some(self.id), - special: None, - result_was_const_computation: false, - }); + let returned = match self.behavior { + FunctionBehavior::ArrowFunction { .. } | FunctionBehavior::Method { .. } => { + TypeId::ERROR_TYPE } - } - } + FunctionBehavior::Constructor { prototype: _, this_object_type, name: _ } => { + *type_arguments.arguments.get(&this_object_type).expect("no this argument?") + } + FunctionBehavior::Function { is_async: _, is_generator: _, this_id, .. } => { + if let Type::Constructor(Constructor::ConditionalResult { + truthy_result, .. + }) = types.get_type_by_id(this_id) + { + *type_arguments.arguments.get(truthy_result).expect("no this argument?") + } else { + unreachable!() + } + } + }; + // TODO if return type is primitive (or replace primitives with new_instance_type) + Some(ApplicationResult::Return { returned, position: input.call_site }) + } else { + result + }; Ok(CallingOutput { result, - warnings: errors.warnings, - // unconditional_exception, called: Some(self.id), special: None, result_was_const_computation: false, @@ -1044,29 +1276,31 @@ impl FunctionType { /// TODO set not using `type_arguments` #[allow(clippy::too_many_arguments)] - fn set_this_for_behavior( + fn set_this_for_behavior( &self, called_with_new: CalledWithNew, this_value: ThisValue, type_arguments: &mut crate::Map, environment: &mut Environment, types: &mut TypeStore, - errors: &mut ErrorsAndInfo, call_site: SpanWithSource, - behavior: &E, + (behavior, diagnostics): (&mut B, &mut CallingDiagnostics), ) { match self.behavior { FunctionBehavior::ArrowFunction { .. } => {} FunctionBehavior::Method { free_this_id, .. } => { // TODO - let value_of_this = if let Some(value) = this_value.get_passed() { - value - } else { - crate::utilities::notify!( - "method has no 'this' passed :?. Passing `undefined` here" - ); - TypeId::UNDEFINED_TYPE - }; + let value_of_this = + if let CalledWithNew::GetterOrSetter { this_type } = called_with_new { + this_type + } else if let Some(value) = this_value.get_passed() { + value + } else { + crate::utilities::notify!( + "method has no 'this' passed :?. Passing `undefined` here" + ); + TypeId::UNDEFINED_TYPE + }; let base_type = get_constraint(free_this_id, types).unwrap_or(free_this_id); @@ -1082,7 +1316,7 @@ impl FunctionType { type_is_subtype(base_type, value_of_this, &mut state, environment, types); if let SubTypeResult::IsNotSubType(_reason) = type_is_subtype { - errors.errors.push(FunctionCallingError::MismatchedThis { + diagnostics.errors.push(FunctionCallingError::MismatchedThis { expected: TypeStringRepresentation::from_type_id( free_this_id, environment, @@ -1110,80 +1344,128 @@ impl FunctionType { FunctionBehavior::Function { is_async: _, is_generator: _, - free_this_id, + this_id, prototype, + name: _, } => { match called_with_new { CalledWithNew::New { on: _ } => { - crate::utilities::notify!("TODO set prototype"); - // if let Some(prototype) = non_super_prototype { - // let this_ty = environment.create_this(prototype, types); - - let this_constraint = get_constraint(free_this_id, types); - // Check `this` has all prototype information - if let Some(this_constraint) = this_constraint { - let mut state = State { - already_checked: Default::default(), - mode: SubTypingMode::default(), - contributions: None, - object_constraints: None, - others: SubTypingOptions::default(), + // This condition is by creation + // TODO THIS IS FRAGILE AND UNCLEAR + let (this_id, constraint) = + if let Type::Constructor(Constructor::ConditionalResult { + truthy_result: this_id, + otherwise_result: constraint, + .. + }) = types.get_type_by_id(this_id) + { + (*this_id, *constraint) + } else { + unreachable!() }; - let result = type_is_subtype( - this_constraint, - prototype, - &mut state, - environment, - types, - ); + // { + // let debug = true; + // crate::utilities::notify!( + // "Checking against this constraint {}", + // crate::types::printing::print_type( + // this_id, + // types, + // environment, + // debug + // ) + // ); + // crate::utilities::notify!( + // "Other {}", + // crate::types::printing::print_type( + // constraint, + // types, + // environment, + // debug + // ) + // ); + // } + + // Check `this` constraint + { + let this_constraint = get_constraint(constraint, types); + + // Check `this` has all prototype information + if let Some(this_constraint) = this_constraint { + let mut state = State { + already_checked: Default::default(), + mode: SubTypingMode::default(), + contributions: None, + object_constraints: None, + others: SubTypingOptions::default(), + }; + + let result = type_is_subtype( + this_constraint, + prototype, + &mut state, + environment, + types, + ); - if let SubTypeResult::IsNotSubType(_reason) = result { - errors.errors.push(FunctionCallingError::MismatchedThis { - expected: TypeStringRepresentation::from_type_id( - this_constraint, - environment, - types, - behavior.debug_types(), - ), - found: TypeStringRepresentation::from_type_id( - prototype, - environment, - types, - behavior.debug_types(), - ), - call_site, - }); + // crate::utilities::notify!("result={:?}", result); + + if let SubTypeResult::IsNotSubType(_reason) = result { + diagnostics.errors.push(FunctionCallingError::MismatchedThis { + expected: TypeStringRepresentation::from_type_id( + this_constraint, + environment, + types, + behavior.debug_types(), + ), + found: TypeStringRepresentation::from_type_id( + prototype, + environment, + types, + behavior.debug_types(), + ), + call_site, + }); + } } } - // TODO think so - let is_under_dyn = true; - let value_of_this = environment.info.new_object( - Some(prototype), - types, - call_site, - is_under_dyn, - ); + // Set argument + { + // TODO think so + let is_under_dyn = true; + let value_of_this = environment.info.new_object( + Some(prototype), + types, + call_site, + is_under_dyn, + ); + + type_arguments.insert(this_id, value_of_this); - type_arguments.insert(free_this_id, value_of_this); + // crate::utilities::notify!("Calling new on regular function"); + // crate::utilities::notify!("Set {:?} top {:?}", free_this_id, value_of_this); + } } - CalledWithNew::Super { this_type } => { - type_arguments.insert(free_this_id, this_type); + CalledWithNew::GetterOrSetter { this_type } + | CalledWithNew::Super { this_type } => { + crate::utilities::notify!("Super / getter / setter on regular function?"); + + type_arguments.insert(this_id, this_type); } CalledWithNew::None => { // TODO let value_of_this = this_value.get(environment, types, call_site); - type_arguments.insert(free_this_id, value_of_this); + type_arguments.insert(this_id, value_of_this); } } } - FunctionBehavior::Constructor { prototype, this_object_type } => { + FunctionBehavior::Constructor { prototype, this_object_type, name: _ } => { crate::utilities::notify!("Here {:?}", called_with_new); match called_with_new { - CalledWithNew::None => { - errors + CalledWithNew::GetterOrSetter { .. } | CalledWithNew::None => { + diagnostics .errors .push(FunctionCallingError::NeedsToBeCalledWithNewKeyword(call_site)); } @@ -1233,7 +1515,9 @@ impl FunctionType { // ty } // In spec, not `new` -> `new.target === undefined` - CalledWithNew::None => TypeId::UNDEFINED_TYPE, + CalledWithNew::GetterOrSetter { .. } | CalledWithNew::None => { + TypeId::UNDEFINED_TYPE + } }; type_arguments.insert(TypeId::NEW_TARGET_ARG, new_target_value); @@ -1241,17 +1525,19 @@ impl FunctionType { } #[allow(clippy::too_many_arguments)] - fn assign_arguments_to_parameters( + fn assign_arguments_to_parameters( &self, arguments: &[SynthesisedArgument], type_arguments: &mut SubstitutionArguments<'static>, - call_site_type_arguments: Option, - parent: Option<&GenericArguments>, + (call_site_type_arguments, parent_arguments): ( + Option, + Option<&GenericArguments>, + ), environment: &mut Environment, types: &mut TypeStore, - errors: &mut ErrorsAndInfo, call_site: SpanWithSource, - behavior: &E, + evaluating_effects: bool, + (behavior, diagnostics): (&mut B, &mut CallingDiagnostics), ) { for (parameter_idx, parameter) in self.parameters.parameters.iter().enumerate() { // TODO temp @@ -1259,17 +1545,17 @@ impl FunctionType { // This handles if the argument is missing but allowing elided arguments let argument = arguments.get(parameter_idx); - if let Some(SynthesisedArgument { spread, value, position }) = argument { - if *spread { + if let Some(argument) = argument { + if argument.spread { crate::utilities::notify!("TODO spread arguments"); } - if E::CHECK_PARAMETERS { + if B::CHECK_PARAMETERS { let result = check_parameter_type( parameter.ty, call_site_type_arguments.as_ref(), - parent, - *value, + parent_arguments, + argument, type_arguments, environment, types, @@ -1277,14 +1563,14 @@ impl FunctionType { if let SubTypeResult::IsNotSubType(_reasons) = result { let type_arguments = Some(GenericChainLink::FunctionRoot { - parent_link: parent, + parent_arguments, call_site_type_arguments: call_site_type_arguments.as_ref(), type_arguments: &type_arguments.arguments, }); - // crate::utilities::notify!("Type arguments are {:?}", type_arguments); + crate::utilities::notify!("Type arguments are {:?}", type_arguments); - errors.errors.push(FunctionCallingError::InvalidArgumentType { + diagnostics.errors.push(FunctionCallingError::InvalidArgumentType { parameter_type: TypeStringRepresentation::from_type_id_with_generics( parameter.ty, type_arguments, @@ -1293,25 +1579,25 @@ impl FunctionType { behavior.debug_types(), ), argument_type: TypeStringRepresentation::from_type_id_with_generics( - *value, + argument.value, type_arguments, environment, types, behavior.debug_types(), ), parameter_position: parameter.position, - argument_position: *position, + argument_position: argument.position, restriction: None, }); } } else { // Already checked so can set - type_arguments.arguments.insert(parameter.ty, *value); + type_arguments.arguments.insert(parameter.ty, argument.value); } } else if parameter.is_optional { type_arguments.arguments.insert(parameter.ty, TypeId::UNDEFINED_TYPE); } else { - errors.errors.push(FunctionCallingError::MissingArgument { + diagnostics.errors.push(FunctionCallingError::MissingArgument { parameter_position: parameter.position, call_site, }); @@ -1322,22 +1608,24 @@ impl FunctionType { if self.parameters.parameters.len() < arguments.len() { if let Some(ref rest_parameter) = self.parameters.rest_parameter { // TODO reuse synthesise_array literal logic (especially for spread items) - let mut basis = ObjectBuilder::new( - Some(TypeId::ARRAY_TYPE), - types, - rest_parameter.position, - &mut environment.info, - ); + let mut basis = evaluating_effects.then(|| { + ObjectBuilder::new( + Some(TypeId::ARRAY_TYPE), + types, + rest_parameter.position, + &mut environment.info, + ) + }); let mut count = 0; for argument in arguments.iter().skip(self.parameters.parameters.len()) { - if E::CHECK_PARAMETERS { + if B::CHECK_PARAMETERS { let result = check_parameter_type( rest_parameter.item_type, call_site_type_arguments.as_ref(), - parent, - argument.value, + parent_arguments, + argument, type_arguments, environment, types, @@ -1346,12 +1634,12 @@ impl FunctionType { // TODO different diagnostic? if let SubTypeResult::IsNotSubType(_reasons) = result { let type_arguments = Some(GenericChainLink::FunctionRoot { - parent_link: parent, + parent_arguments, call_site_type_arguments: call_site_type_arguments.as_ref(), type_arguments: &type_arguments.arguments, }); - errors.errors.push(FunctionCallingError::InvalidArgumentType { + diagnostics.errors.push(FunctionCallingError::InvalidArgumentType { parameter_type: TypeStringRepresentation::from_type_id_with_generics( rest_parameter.ty, @@ -1374,37 +1662,38 @@ impl FunctionType { } } - { + // Add to spread array + if let Some(basis) = basis.as_mut() { let key = PropertyKey::from_usize(count); - basis.append( - environment, crate::types::properties::Publicity::Public, key, crate::types::properties::PropertyValue::Value(argument.value), argument.position, + &mut environment.info, ); } count += 1; } - { + // Set length of spread array + if let Some(mut basis) = basis { let length = types.new_constant_type(crate::Constant::Number( (count as f64).try_into().unwrap(), )); basis.append( - environment, crate::types::properties::Publicity::Public, PropertyKey::String("length".into()), crate::types::properties::PropertyValue::Value(length), rest_parameter.position, + &mut environment.info, ); - } - let rest_parameter_array_type = basis.build_object(); - type_arguments.arguments.insert(rest_parameter.ty, rest_parameter_array_type); + let rest_parameter_array_type = basis.build_object(); + type_arguments.arguments.insert(rest_parameter.ty, rest_parameter_array_type); + } } else { // TODO types.options.allow_extra_arguments let mut left_over = arguments.iter().skip(self.parameters.parameters.len()); @@ -1426,8 +1715,10 @@ impl FunctionType { first.position }; - if E::CHECK_PARAMETERS { - errors.errors.push(FunctionCallingError::ExcessArguments { count, position }); + if B::CHECK_PARAMETERS { + diagnostics + .errors + .push(FunctionCallingError::ExcessArguments { count, position }); } } } @@ -1445,10 +1736,10 @@ impl FunctionType { } fn check_parameter_type( - parameter_ty: TypeId, + parameter_type: TypeId, call_site_type_arguments: Option<&CallSiteTypeArguments>, parent: Option<&GenericArguments>, - argument_ty: TypeId, + argument: &SynthesisedArgument, type_arguments: &mut SubstitutionArguments, environment: &mut Environment, types: &mut TypeStore, @@ -1473,8 +1764,8 @@ fn check_parameter_type( }; let result = type_is_subtype_with_generics( - (parameter_ty, GenericChain::None), - (argument_ty, GenericChain::None), + (parameter_type, GenericChain::None), + (argument.value, GenericChain::None), &mut state, environment, types, @@ -1517,6 +1808,12 @@ fn check_parameter_type( type_arguments.arguments.insert(*on, value); } } + + // TODO + let no_events = false; + if no_events { + mark_possible_mutation(argument, parameter_type, types, environment); + } } // environment.context_type.requests.append(&mut parameter_constraint_request); @@ -1548,7 +1845,7 @@ fn synthesise_argument_expressions_wrt_parameters todo!(), } } diff --git a/checker/src/types/casts.rs b/checker/src/types/casts.rs index 8f96c224..13b8ba5e 100644 --- a/checker/src/types/casts.rs +++ b/checker/src/types/casts.rs @@ -17,8 +17,7 @@ pub(crate) fn cast_as_number(cst: &Constant, strict_casts: bool) -> Result Ok(if *val { 1f64 } else { 0f64 }), - Constant::NaN | Constant::Undefined => Ok(f64::NAN), - Constant::Null => Ok(0f64), + Constant::NaN => Ok(f64::NAN), Constant::Symbol { key: _ } => todo!(), } } @@ -42,7 +41,7 @@ pub(crate) fn cast_as_boolean(cst: &Constant, strict_casts: bool) -> Result number.into_inner() != 0., Constant::String(value) => !value.is_empty(), Constant::Boolean(value) => *value, - Constant::NaN | Constant::Undefined | Constant::Null => false, + Constant::NaN => false, Constant::Symbol { key: _ } => todo!(), }) } diff --git a/checker/src/types/classes.rs b/checker/src/types/classes.rs index 6cff4798..d181caa5 100644 --- a/checker/src/types/classes.rs +++ b/checker/src/types/classes.rs @@ -102,6 +102,6 @@ pub(crate) fn register_properties_into_environment< } else { PropertyValue::Value(TypeId::UNDEFINED_TYPE) }; - environment.info.register_property(on, publicity, key, value, true, position); + environment.info.register_property(on, publicity, key, value, position); } } diff --git a/checker/src/types/disjoint.rs b/checker/src/types/disjoint.rs new file mode 100644 index 00000000..db70f788 --- /dev/null +++ b/checker/src/types/disjoint.rs @@ -0,0 +1,93 @@ +use super::PartiallyAppliedGenerics; +use crate::{context::InformationChain, types::TypeStore, Type, TypeId}; + +/// For equality + [`crate::intrinsics::Intrinsics::Not`] +/// +/// TODO slices +/// TODO properties +/// TODO (which references subtyping) and (via commutativity of this operation) +/// +/// Could shrink some logic here but is more readable verbose +pub fn types_are_disjoint( + left: TypeId, + right: TypeId, + already_checked: &mut Vec<(TypeId, TypeId)>, + information: &impl InformationChain, + types: &TypeStore, +) -> bool { + if left == right { + false + } else if already_checked.iter().any(|pair| *pair == (left, right)) { + // TODO explain why `true` + true + } else { + let left_ty = types.get_type_by_id(left); + let right_ty = types.get_type_by_id(right); + + // if let Type::Constructor(Constructor::KeyOf(_)) = left_ty { + // todo!("get property != ") + // } else + if let Type::Constant(left_cst) = left_ty { + if let Type::Constant(right_cst) = right_ty { + left_cst != right_cst + } else { + left_cst.get_backing_type_id() != right + } + } else if let Type::Constant(right_cst) = right_ty { + right_cst.get_backing_type_id() != left + } else if let Type::Or(left_left, left_right) = left_ty { + types_are_disjoint(*left_left, right, already_checked, information, types) + && types_are_disjoint(*left_right, right, already_checked, information, types) + } else if let Type::And(left_left, left_right) = left_ty { + types_are_disjoint(*left_left, right, already_checked, information, types) + || types_are_disjoint(*left_right, right, already_checked, information, types) + } else if let Type::Or(right_left, right_right) = right_ty { + types_are_disjoint(left, *right_left, already_checked, information, types) + && types_are_disjoint(left, *right_right, already_checked, information, types) + } else if let Type::And(right_left, right_right) = right_ty { + types_are_disjoint(left, *right_left, already_checked, information, types) + || types_are_disjoint(left, *right_right, already_checked, information, types) + } else if let Type::AliasTo { to, parameters: None, name: _ } = left_ty { + // TODO temp fix, need infer ANY + if matches!(*to, TypeId::ANY_TYPE) { + true + } else { + types_are_disjoint(*to, right, already_checked, information, types) + } + } else if let Type::AliasTo { to, parameters: None, name: _ } = right_ty { + if matches!(*to, TypeId::ANY_TYPE) { + true + } else { + types_are_disjoint(left, *to, already_checked, information, types) + } + } else if let ( + Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::ARRAY_TYPE, + arguments: _arguments, + }), + Type::Object(super::ObjectNature::RealDeal), + ) = (left_ty, right_ty) + { + let rhs_prototype = information + .get_chain_of_info() + .find_map(|info| info.prototypes.get(&right).copied()); + // { + // if let Some(lhs_prototype) = info.prototypes.get(&lhs).copied() { + // let rhs_prototype = information.get_prototype_of(right); + + // TODO leaving arguments out of picture for now + rhs_prototype != Some(TypeId::ARRAY_TYPE) + } else if let (Type::Object(super::ObjectNature::RealDeal), _) + | (_, Type::Object(super::ObjectNature::RealDeal)) = (left_ty, right_ty) + { + true + } else { + crate::utilities::notify!( + "{:?} cap {:?} == empty ? cases. Might be missing, calling disjoint", + left_ty, + right_ty + ); + true + } + } +} diff --git a/checker/src/types/functions.rs b/checker/src/types/functions.rs index 1b072312..d8f21bcf 100644 --- a/checker/src/types/functions.rs +++ b/checker/src/types/functions.rs @@ -3,12 +3,11 @@ use std::collections::HashMap; use source_map::{BaseSpan, Nullable, SpanWithSource}; +use super::calling::{Callable, CallingContext, CallingInput}; use crate::{ - call_type_handle_errors, - context::environment::FunctionScope, + context::{environment::FunctionScope, invocation::CheckThings}, events::{Event, RootReference}, - features::functions::{ClassPropertiesToRegister, ClosedOverVariables, FunctionBehavior}, - types::calling::CallingInput, + features::functions::{ClassPropertiesToRegister, ClosedOverVariables}, CheckingData, Environment, FunctionId, GenericTypeParameters, Scope, TypeId, }; @@ -30,6 +29,7 @@ pub struct FunctionType { #[derive(Clone, Debug, binary_serialize_derive::BinarySerializable)] pub enum FunctionEffect { + /// Has synthesised events SideEffects { /// Note that a function can still be considered to be 'pure' and have a non-empty vector of events events: Vec, @@ -55,6 +55,7 @@ pub enum FunctionEffect { identifier: String, may_throw: Option, }, + /// Such as a callback Unknown, } @@ -78,10 +79,12 @@ impl From for FunctionEffect { } impl FunctionType { + #[allow(clippy::too_many_arguments)] pub(crate) fn new_auto_constructor( function_id: FunctionId, class_prototype: TypeId, extends: Option, + name: TypeId, properties: ClassPropertiesToRegister, environment: &mut Environment, checking_data: &mut CheckingData, @@ -114,16 +117,26 @@ impl FunctionType { let input = CallingInput { call_site: BaseSpan::NULL, called_with_new, - call_site_type_arguments: None, + max_inline: checking_data.options.max_inline_count, }; - let _ = call_type_handle_errors( - extends, - &[], + let mut diagnostics = Default::default(); + let result = Callable::Type(extends).call( + Vec::new(), input, environment, - checking_data, - TypeId::ANY_TYPE, + ( + &mut CheckThings { debug_types: checking_data.options.debug_types }, + &mut diagnostics, + ), + &mut checking_data.types, ); + + diagnostics + .append_to(CallingContext::Super, &mut checking_data.diagnostics_container); + match result { + Ok(_) => {} + Err(_error) => {} + } } crate::types::classes::register_properties_into_environment( @@ -138,8 +151,11 @@ impl FunctionType { }, ); - let behavior = - FunctionBehavior::Constructor { prototype: class_prototype, this_object_type: on }; + let behavior = FunctionBehavior::Constructor { + prototype: class_prototype, + this_object_type: on, + name, + }; let (info, _free_variables) = env_data.unwrap(); Self { @@ -157,11 +173,58 @@ impl FunctionType { } } -/// TODO temp +/// TODO different place +/// TODO maybe generic #[derive(Clone, Copy, Debug, binary_serialize_derive::BinarySerializable)] -pub enum GetSet { - Get, - Set, +pub enum FunctionBehavior { + /// For arrow functions, cannot have `this` bound + ArrowFunction { + is_async: bool, + }, + Method { + free_this_id: TypeId, + is_async: bool, + is_generator: bool, + name: TypeId, + }, + /// Functions defined `function`. Extends above by allowing `new` + Function { + /// This points the general `this` object. + /// When calling with: + /// - `new`: an arguments should set with (`free_this_id`, *new object*) + /// - regularly: bound argument, else parent `this` (I think) + this_id: TypeId, + /// The function type. [See](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new) + prototype: TypeId, + is_async: bool, + /// Cannot be called with `new` if true + is_generator: bool, + /// This is to implement + name: TypeId, + }, + /// Constructors, require new + Constructor { + /// The prototype of the base object + prototype: TypeId, + /// The id of the generic that needs to be pulled out + this_object_type: TypeId, + name: TypeId, + }, +} + +impl FunctionBehavior { + pub(crate) fn can_be_bound(self) -> bool { + matches!(self, Self::Method { .. } | Self::Function { .. }) + } + + pub(crate) fn get_name(self) -> TypeId { + match self { + Self::ArrowFunction { .. } => TypeId::EMPTY_STRING, + Self::Method { name, .. } + | Self::Function { name, .. } + | Self::Constructor { name, .. } => name, + } + } } /// Optionality is indicated by what vector it is in [`SynthesisedParameters`] @@ -209,20 +272,3 @@ impl SynthesisedParameters { } } } - -#[derive(Clone, Debug, binary_serialize_derive::BinarySerializable)] -pub struct SynthesisedArgument { - pub(crate) spread: bool, - pub(crate) value: TypeId, - pub(crate) position: SpanWithSource, -} - -impl SynthesisedArgument { - pub fn non_spread_type(&self) -> Result { - if self.spread { - Err(()) - } else { - Ok(self.value) - } - } -} diff --git a/checker/src/types/generics/chain.rs b/checker/src/types/generics/chain.rs new file mode 100644 index 00000000..30450aa5 --- /dev/null +++ b/checker/src/types/generics/chain.rs @@ -0,0 +1,314 @@ +use crate::types::{ + CovariantContribution, GenericArguments, SliceArguments, SubstitutionArguments, TypeId, + TypeRestrictions, TypeStore, +}; + +pub type GenericChain<'a> = Option>; +pub type GenericChainParent<'a> = Option<&'a GenericChainLink<'a>>; + +/// - Used for printing and subtyping. Handles nested restrictions +/// - Uses lifetimes because lifetimes +#[derive(Clone, Copy, Debug)] +pub enum GenericChainLink<'a> { + FunctionRoot { + call_site_type_arguments: Option<&'a TypeRestrictions>, + type_arguments: &'a crate::Map, + /// From whatever the function was on + parent_arguments: Option<&'a GenericArguments>, + }, + // For walking through [`Type::PartiallyAppliedGenerics`] + PartiallyAppliedGenericArgumentsLink { + from: TypeId, + value: &'a GenericArguments, + parent_link: GenericChainParent<'a>, + }, + /// WIP. For the specialised arguments during property key lookup + MappedPropertyLink { value: &'a SliceArguments, parent_link: GenericChainParent<'a> }, + SpecialGenericChainLink { + special: SpecialGenericChainLink, + parent_link: GenericChainParent<'a>, + }, +} + +#[derive(Clone, Copy, Debug)] +pub enum SpecialGenericChainLink { + Exclusive, + CaseTransform { transform: TypeId }, + CaseInsensitive, + // Don't think this is necesary? + // Readonly, +} + +impl<'a> GenericChainLink<'a> { + pub fn get_value(self) -> Option<&'a GenericArguments> { + if let Self::PartiallyAppliedGenericArgumentsLink { value, .. } = self { + Some(value) + } else { + None + } + } + + pub fn get_origin(&self) -> Option { + match self { + Self::PartiallyAppliedGenericArgumentsLink { from, .. } => Some(*from), + Self::FunctionRoot { .. } => None, + Self::SpecialGenericChainLink { parent_link, .. } + | Self::MappedPropertyLink { parent_link, .. } => parent_link.and_then(Self::get_origin), + } + } + + // TODO lifetimes here + // TODO make iterator + // fn get_parent(&self) -> GenericChainParent { + // if let Self::PartiallyAppliedGenericArgumentsLink { parent_link, .. } + // | Self::MappedPropertyLink { parent_link, .. } + // | Self::CaseTransform { parent_link, .. } + // | Self::Exclusive { parent_link, .. } = self + // { + // parent_link + // } else { + // None + // } + // } + + pub(crate) fn get_string_transform(&self) -> Option { + match self { + GenericChainLink::FunctionRoot { .. } => None, + GenericChainLink::SpecialGenericChainLink { + parent_link: _, + special: SpecialGenericChainLink::CaseTransform { transform }, + } => Some(*transform), + GenericChainLink::SpecialGenericChainLink { parent_link, special: _ } + | GenericChainLink::PartiallyAppliedGenericArgumentsLink { parent_link, .. } + | GenericChainLink::MappedPropertyLink { parent_link, .. } => { + if let Some(parent_link) = parent_link { + parent_link.get_string_transform() + } else { + None + } + } + } + } + + pub(crate) fn is_case_insensitive(&self) -> bool { + match self { + GenericChainLink::FunctionRoot { .. } => false, + GenericChainLink::SpecialGenericChainLink { + parent_link: _, + special: SpecialGenericChainLink::CaseInsensitive, + } => true, + GenericChainLink::SpecialGenericChainLink { parent_link, special: _ } + | GenericChainLink::PartiallyAppliedGenericArgumentsLink { parent_link, .. } + | GenericChainLink::MappedPropertyLink { parent_link, .. } => { + if let Some(parent_link) = parent_link { + parent_link.is_case_insensitive() + } else { + false + } + } + } + } + + pub(crate) fn exclusive_mode(&self) -> bool { + match self { + GenericChainLink::FunctionRoot { .. } => false, + GenericChainLink::SpecialGenericChainLink { + parent_link: _, + special: SpecialGenericChainLink::Exclusive, + } => true, + GenericChainLink::SpecialGenericChainLink { parent_link, special: _ } + | GenericChainLink::PartiallyAppliedGenericArgumentsLink { parent_link, .. } + | GenericChainLink::MappedPropertyLink { parent_link, .. } => { + if let Some(parent_link) = parent_link { + parent_link.exclusive_mode() + } else { + false + } + } + } + } + + /// WIP I want to make this the main one + pub(crate) fn get_argument_covariant(&self, on: TypeId) -> Option { + match self { + GenericChainLink::PartiallyAppliedGenericArgumentsLink { + parent_link, + value, + from: _, + } => value + .get_structure_restriction(on) + .map(CovariantContribution::TypeId) + .or_else(|| parent_link.and_then(|f| f.get_argument_covariant(on))), + GenericChainLink::FunctionRoot { + parent_arguments, + call_site_type_arguments, + type_arguments, + } => parent_arguments + .and_then(|parent| { + parent.get_structure_restriction(on).map(CovariantContribution::TypeId) + }) + .or_else(|| { + call_site_type_arguments + .and_then(|ta1| ta1.get(&on).map(|(arg, _)| *arg)) + .map(CovariantContribution::TypeId) + }) + .or_else(|| type_arguments.get(&on).copied().map(CovariantContribution::TypeId)), + GenericChainLink::MappedPropertyLink { parent_link, value } => value + .get(&on) + .map(|(arg, _)| arg.clone()) + .or_else(|| parent_link.and_then(|f| f.get_argument_covariant(on))), + GenericChainLink::SpecialGenericChainLink { parent_link, special: _ } => { + parent_link.and_then(|f| f.get_argument_covariant(on)) + } + } + } + + /// TODO WIP + /// + /// between this and `extend_arguments` there needs to be something better + pub(crate) fn build_substitutable( + &self, + types: &mut TypeStore, + ) -> SubstitutionArguments<'static> { + match self { + GenericChainLink::PartiallyAppliedGenericArgumentsLink { + from: _, + parent_link, + value, + } => { + let mut args = value.build_substitutable(); + if let Some(parent_link) = parent_link { + parent_link.extend_arguments(&mut args); + } + args + } + GenericChainLink::MappedPropertyLink { parent_link, value } => { + crate::utilities::notify!("parent_link={:?}", parent_link); + let arguments = value + .iter() + .map(|(k, (contribution, _))| (*k, contribution.clone().into_type(types))) + .collect(); + let mut args = + SubstitutionArguments { parent: None, arguments, closures: Default::default() }; + if let Some(parent_link) = parent_link { + parent_link.extend_arguments(&mut args); + } + args + } + GenericChainLink::FunctionRoot { .. } => { + todo!() + } + GenericChainLink::SpecialGenericChainLink { parent_link, special: _ } => { + todo!("{:?}", parent_link.is_some()) + } + } + } + + pub(crate) fn append_to_link( + from: TypeId, + parent: GenericChainParent<'a>, + value: &'a GenericArguments, + ) -> GenericChainLink<'a> { + GenericChainLink::PartiallyAppliedGenericArgumentsLink { parent_link: parent, value, from } + } + + #[allow(clippy::unnecessary_wraps)] + pub(crate) fn append( + from: TypeId, + parent: GenericChainParent<'a>, + value: &'a GenericArguments, + ) -> GenericChain<'a> { + Some(GenericChainLink::append_to_link(from, parent, value)) + } + + /// Does not do 'lookup generics'. Which may be fine + /// + /// - (swaps `get_argument_as_list` with `get_structure_restriction`) + pub(crate) fn get_single_argument(&self, on: TypeId) -> Option { + match self { + GenericChainLink::PartiallyAppliedGenericArgumentsLink { + parent_link, + value, + from: _, + } => value + .get_structure_restriction(on) + .or_else(|| parent_link.and_then(|parent| parent.get_single_argument(on))), + GenericChainLink::FunctionRoot { + parent_arguments, + call_site_type_arguments, + type_arguments, + } => parent_arguments + .and_then(|parent| parent.get_structure_restriction(on)) + .or_else(|| { + call_site_type_arguments.and_then(|ta1| ta1.get(&on).map(|(arg, _)| *arg)) + }) + .or_else(|| type_arguments.get(&on).copied()), + GenericChainLink::MappedPropertyLink { parent_link, value } => value + .get(&on) + .cloned() + .and_then(|(c, _)| { + if let CovariantContribution::TypeId(t) = c { + Some(t) + } else { + crate::utilities::notify!( + "WARNING SKIPPING AS Contribution is {:?} (NOT TYPEID)", + c + ); + None + } + }) + .or_else(|| parent_link.and_then(|parent| parent.get_single_argument(on))), + GenericChainLink::SpecialGenericChainLink { parent_link, special: _ } => { + parent_link.and_then(|parent| parent.get_single_argument(on)) + } + } + } + + /// For building up [`SubstitutionArguments`] from a generic chain + pub(crate) fn extend_arguments(&self, arguments: &mut SubstitutionArguments<'static>) { + match self { + GenericChainLink::PartiallyAppliedGenericArgumentsLink { + from: _, + parent_link, + value, + } => { + match value { + GenericArguments::ExplicitRestrictions(n) => { + arguments.arguments.extend(n.iter().map(|(k, v)| { + assert!(*k != v.0); + (*k, v.0) + })); + } + GenericArguments::Closure(n) => arguments.closures.extend(n.iter().copied()), + GenericArguments::LookUp { .. } => todo!(), + } + if let Some(parent_link) = parent_link { + parent_link.extend_arguments(arguments); + } + } + GenericChainLink::FunctionRoot { + parent_arguments, + call_site_type_arguments: _, + type_arguments, + } => { + arguments.arguments.extend(type_arguments.iter().map(|(k, v)| (*k, *v))); + if let Some(value) = parent_arguments { + match value { + GenericArguments::ExplicitRestrictions(n) => { + arguments.arguments.extend(n.iter().map(|(k, v)| (*k, v.0))); + } + GenericArguments::Closure(n) => { + arguments.closures.extend(n.iter().copied()); + } + GenericArguments::LookUp { .. } => todo!(), + } + } + } + + GenericChainLink::MappedPropertyLink { .. } => todo!("need &mut TypeStore"), + GenericChainLink::SpecialGenericChainLink { parent_link, special: _ } => { + todo!("parent_link={:?}", parent_link) + } + } + } +} diff --git a/checker/src/types/generics/contributions.rs b/checker/src/types/generics/contributions.rs index af73176a..776ab745 100644 --- a/checker/src/types/generics/contributions.rs +++ b/checker/src/types/generics/contributions.rs @@ -1,37 +1,86 @@ -use source_map::SpanWithSource; +use source_map::{Nullable, SpanWithSource}; use crate::{ subtyping::{type_is_subtype_with_generics, State, SubTypeResult}, - types::{properties::PropertyKey, GenericChain, TypeRestrictions, TypeStore}, + types::{GenericChain, PartiallyAppliedGenerics, PropertyKey, TypeRestrictions, TypeStore}, Environment, TypeId, }; use super::generic_type_arguments::GenericArguments; -pub type DoubleMap = crate::Map; pub type TriMap = crate::Map; /// How deep through the contribution is () -pub type Depth = u8; +pub type ContributionDepth = u8; /// This is something that is generated inferred during subtyping #[derive(Debug, Clone)] pub enum CovariantContribution { + /// Note this can reference array TypeId(TypeId), - // SliceOf(Box, (u32, u32)), - PropertyKey(PropertyKey<'static>), + /// This can be set by property keys in mapped types. Also `[x]` on a constant string + String(String), + SliceOf(Box, (u32, u32)), + CaseInsensitive(Box), + /// This can be from `.length` on a constant string + Number(f64), } impl CovariantContribution { pub(crate) fn into_type(self, types: &mut TypeStore) -> TypeId { match self { CovariantContribution::TypeId(ty) => ty, - // CovariantContribution::SliceOf(inner, _) => { - // let inner = inner.into_type(types); - // crate::utilities::notify!("TODO as slice"); - // inner - // } - CovariantContribution::PropertyKey(p) => p.into_type(types), + CovariantContribution::SliceOf(inner, (start, end)) => { + let inner = inner.into_type(types); + if let crate::Type::Constant(crate::types::Constant::String(s)) = + types.get_type_by_id(inner) + { + let slice: String = s.chars().skip(start as usize).take(end as usize).collect(); + types.new_constant_type(crate::Constant::String(slice)) + } else { + todo!("slice type") + } + } + CovariantContribution::String(slice) => { + types.new_constant_type(crate::Constant::String(slice)) + } + CovariantContribution::Number(number) => { + if let Ok(number) = number.try_into() { + types.new_constant_type(crate::Constant::Number(number)) + } else { + TypeId::NAN + } + } + CovariantContribution::CaseInsensitive(on) => { + let inner = on.into_type(types); + types.register_type(crate::Type::PartiallyAppliedGenerics( + PartiallyAppliedGenerics { + on: TypeId::CASE_INSENSITIVE, + arguments: GenericArguments::ExplicitRestrictions(crate::Map::from_iter([ + (TypeId::STRING_GENERIC, (inner, SpanWithSource::NULL)), + ])), + }, + )) + } + } + } + + // TODO maybe return modifier for generic chain + pub(crate) fn into_property_key(self) -> PropertyKey<'static> { + match self { + CovariantContribution::TypeId(ty) => PropertyKey::Type(ty), + CovariantContribution::SliceOf(inner, (start, end)) => { + todo!("{:?}", (inner, (start, end))); + } + CovariantContribution::String(slice) => { + PropertyKey::String(std::borrow::Cow::Owned(slice.clone())) + } + CovariantContribution::Number(number) => { + PropertyKey::String(std::borrow::Cow::Owned(number.to_string())) + } + CovariantContribution::CaseInsensitive(on) => { + todo!("{:?}", on) + } } } } @@ -42,6 +91,15 @@ impl From for CovariantContribution { } } +impl From> for CovariantContribution { + fn from(value: PropertyKey<'static>) -> Self { + match value { + PropertyKey::String(s) => CovariantContribution::String(s.to_string()), + PropertyKey::Type(value) => CovariantContribution::TypeId(value), + } + } +} + /// `staging_*` is to get around the fact that intersections cannot be during subtyping (as types /// is immutable at this stage, rather than mutable) /// @@ -62,7 +120,7 @@ pub struct Contributions<'a> { // pub existing_covariant: &'a mut X, /// Only for explicit generic parameters pub staging_covariant: TriMap, - pub staging_contravariant: TriMap, + pub staging_contravariant: TriMap, } impl<'a> Contributions<'a> { diff --git a/checker/src/types/generics/generic_type_arguments.rs b/checker/src/types/generics/generic_type_arguments.rs index cdbff191..565fa8f7 100644 --- a/checker/src/types/generics/generic_type_arguments.rs +++ b/checker/src/types/generics/generic_type_arguments.rs @@ -2,7 +2,7 @@ //! TODO Some of these are a bit overkill and don't need wrapping objects **AND THEY BREAK FINALIZE THINGS REQUIRE CLONING** use crate::{ - context::information::InformationChain, + context::InformationChain, features::functions::ClosureId, types::{SubstitutionArguments, TypeRestrictions, TypeStore}, TypeId, @@ -11,6 +11,8 @@ use crate::{ use std::fmt::Debug; /// These are curried between structures +/// +/// It is also part of [`GenericChainLink`] #[derive(Clone, Debug, binary_serialize_derive::BinarySerializable)] pub enum GenericArguments { /// This is from a specialised result @@ -56,14 +58,14 @@ impl GenericArguments { .and_then(|lookup| lookup.get(&under))?; crate::utilities::notify!("Hopefully here {:?}", prototype); - let res = lookup.calculate_lookup(info, *on); + let res = lookup.calculate_lookup(info, types, *on); Some(res) } } } #[must_use] - pub fn into_substitutable(&self) -> SubstitutionArguments<'static> { + pub fn build_substitutable(&self) -> SubstitutionArguments<'static> { match self { GenericArguments::ExplicitRestrictions(res) => SubstitutionArguments { parent: None, diff --git a/checker/src/types/generics/generic_type_parameters.rs b/checker/src/types/generics/generic_type_parameters.rs index d6717bb0..acef15a1 100644 --- a/checker/src/types/generics/generic_type_parameters.rs +++ b/checker/src/types/generics/generic_type_parameters.rs @@ -6,15 +6,6 @@ use crate::TypeId; #[derive(Default, Debug, Clone, binary_serialize_derive::BinarySerializable)] pub struct GenericTypeParameters(pub Vec); -impl GenericTypeParameters { - #[must_use] - pub fn as_option(&self) -> Option<()> { - todo!() - // let borrow = self.0.borrow(); - // (!borrow.is_empty()).then(|| borrow) - } -} - impl FromIterator for GenericTypeParameters { fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect()) diff --git a/checker/src/types/generics/mod.rs b/checker/src/types/generics/mod.rs index 973abf39..69c5452e 100644 --- a/checker/src/types/generics/mod.rs +++ b/checker/src/types/generics/mod.rs @@ -2,6 +2,7 @@ //! //! Here dependent means the type depends on another type or condition (sometimes called type constructors) +pub mod chain; pub mod contributions; pub mod generic_type_arguments; pub mod generic_type_parameters; diff --git a/checker/src/types/generics/substitution.rs b/checker/src/types/generics/substitution.rs index 25b1ea15..9807ecf6 100644 --- a/checker/src/types/generics/substitution.rs +++ b/checker/src/types/generics/substitution.rs @@ -3,9 +3,10 @@ use source_map::{Nullable, SpanWithSource}; use crate::{ + context::LocalInformation, features::{ - functions::{ClosureChain, ClosureId, ThisValue}, - objects::SpecialObjects, + functions::{ClosureChain, ClosureId}, + objects::{Proxy, SpecialObject}, operations::{ evaluate_equality_inequality_operation, evaluate_mathematical_operation, evaluate_pure_unary_operator, @@ -13,12 +14,14 @@ use crate::{ }, subtyping::{State, SubTypingOptions}, types::{ + calling::ThisValue, generics::contributions::Contributions, - get_constraint, + intrinsics::{self, distribute_tsc_string_intrinsic}, + logical::{BasedOnKey, Logical, LogicalOrValid}, properties::{get_property_unbound, Publicity}, Constructor, ObjectNature, PartiallyAppliedGenerics, PolyNature, Type, TypeStore, }, - Decidable, Environment, Logical, PropertyValue, TypeId, + Decidable, Environment, PropertyValue, TypeId, }; use super::generic_type_arguments::GenericArguments; @@ -31,7 +34,7 @@ pub struct SubstitutionArguments<'a> { } impl<'a> ClosureChain for SubstitutionArguments<'a> { - fn get_fact_from_closure(&self, _fact: &crate::LocalInformation, cb: T) -> Option + fn get_fact_from_closure(&self, _fact: &LocalInformation, cb: T) -> Option where T: Fn(ClosureId) -> Option, { @@ -82,8 +85,8 @@ pub(crate) fn substitute( id } // Closures for objects - Type::SpecialObject(SpecialObjects::ClassConstructor { .. }) - | Type::Object(ObjectNature::RealDeal) => { + // Type::SpecialObject(SpecialObject::ClassConstructor { .. }) + Type::Object(ObjectNature::RealDeal) => { // Apply curring if arguments.closures.is_empty() { id @@ -97,29 +100,31 @@ pub(crate) fn substitute( // Specialisation for object type annotation (todo could do per property in future) // Can return functions from functions somehow as well Type::FunctionReference(..) | Type::Object(ObjectNature::AnonymousTypeAnnotation) => { + // crate::utilities::notify!("{:?}", arguments.arguments); // Apply curring if arguments.arguments.is_empty() { id } else { + let arguments = GenericArguments::ExplicitRestrictions( + arguments + .arguments + .iter() + .map(|(k, v)| (*k, (*v, SpanWithSource::NULL))) + .collect(), + ); types.register_type(Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on: id, // TODO argument positions - arguments: GenericArguments::ExplicitRestrictions( - arguments - .arguments - .iter() - .map(|(k, v)| (*k, (*v, SpanWithSource::NULL))) - .collect(), - ), + arguments, })) } } - Type::SpecialObject(SpecialObjects::Function(f, t)) => { + Type::SpecialObject(SpecialObject::Function(f, t)) => { // Substitute the this type let id = if let ThisValue::Passed(p) = t { let function_id = *f; let passed = ThisValue::Passed(substitute(*p, arguments, environment, types)); - types.register_type(Type::SpecialObject(SpecialObjects::Function( + types.register_type(Type::SpecialObject(SpecialObject::Function( function_id, passed, ))) @@ -141,7 +146,17 @@ pub(crate) fn substitute( arguments: structure_arguments, }) => { let on = *on; - let new_structure_arguments = match structure_arguments.clone() { + let generic_arguments = structure_arguments.clone(); + + // Fold intrinsic type + if intrinsics::tsc_string_intrinsic(on) { + let arg = + structure_arguments.get_structure_restriction(TypeId::STRING_GENERIC).unwrap(); + let value = substitute(arg, arguments, environment, types); + return distribute_tsc_string_intrinsic(value, on, types); + } + + let new_structure_arguments = match generic_arguments { GenericArguments::ExplicitRestrictions(restrictions) => { let restrictions = restrictions .into_iter() @@ -163,12 +178,21 @@ pub(crate) fn substitute( })) } Type::SpecialObject(special_object) => match special_object { - SpecialObjects::Promise { .. } => todo!(), - SpecialObjects::Generator { .. } => todo!(), - SpecialObjects::Proxy { .. } => todo!(), - SpecialObjects::RegularExpression(_) => todo!(), - SpecialObjects::Import(_) => todo!(), - _ => unreachable!(), + SpecialObject::Promise { .. } => todo!(), + SpecialObject::Generator { .. } => todo!(), + SpecialObject::Proxy(Proxy { over, handler }) => { + let (prev_over, prev_handler) = (*over, *handler); + let over = substitute(prev_over, arguments, environment, types); + let handler = substitute(prev_handler, arguments, environment, types); + crate::utilities::notify!("Here {:?}", (prev_over, prev_handler, over, handler)); + types.register_type(Type::SpecialObject(SpecialObject::Proxy(Proxy { + over, + handler, + }))) + } + SpecialObject::RegularExpression { .. } => todo!(), + SpecialObject::Import(_) | SpecialObject::Null => id, + SpecialObject::Function(..) => unreachable!("done above"), }, Type::And(lhs, rhs) => { let rhs = *rhs; @@ -185,10 +209,19 @@ pub(crate) fn substitute( Type::RootPolyType(nature) => { if let PolyNature::Open(_) | PolyNature::Error(_) = nature { id - } else if let PolyNature::FunctionGeneric { .. } | PolyNature::StructureGeneric { .. } = - nature + } else if let PolyNature::FunctionGeneric { .. } + | PolyNature::StructureGeneric { .. } + | PolyNature::InferGeneric { .. } = nature { - crate::utilities::notify!("Could not find argument for explicit generic {:?}", id); + // Infer generic is fine for `type Index = T extends Array ? I : never`; + crate::utilities::notify!( + "Could not find argument for explicit generic {:?} (nature={:?})", + id, + nature + ); + id + } else if let PolyNature::FreeVariable { .. } = nature { + crate::utilities::notify!("Could not find free variable"); id } else { // Other root poly types cases handled by the early return @@ -208,24 +241,20 @@ pub(crate) fn substitute( Ok(result) => result, Err(()) => { unreachable!( - "Cannot {lhs:?} {operator:?} {rhs:?} (restriction or something failed)" + "Cannot {:?} {operator:?} {:?} (restriction or something failed)", + crate::types::printing::print_type(lhs, types, environment, true), + crate::types::printing::print_type(rhs, types, environment, true) ); } } } Constructor::UnaryOperator { operand, operator, .. } => { - match evaluate_pure_unary_operator( + let operand = substitute(operand, arguments, environment, types); + evaluate_pure_unary_operator( operator, operand, types, // Restrictions should have been made ahead of time false, - ) { - Ok(result) => result, - Err(()) => { - unreachable!( - "Cannot {operand:?} {operator:?} (restriction or something failed)" - ); - } - } + ) } Constructor::ConditionalResult { condition, @@ -233,16 +262,16 @@ pub(crate) fn substitute( otherwise_result, result_union: _, } => { - // TSC behavior + // TSC behavior, use `compute_extends_rule` which does distribution if let Type::Constructor(Constructor::TypeRelationOperator( crate::types::TypeRelationOperator::Extends { item, extends }, )) = types.get_type_by_id(condition) { + // crate::utilities::notify!("Here!"); + let (item, extends) = (*item, *extends); let item = substitute(item, arguments, environment, types); - - // TODO pass generics down via other - // let extends = substitute(extends, arguments, environment, types); + let extends = substitute(extends, arguments, environment, types); return compute_extends_rule( extends, @@ -303,10 +332,12 @@ pub(crate) fn substitute( types.register_type(Type::Constructor(ty)) } } - Constructor::Property { on, under, result, bind_this, .. } => { - let on = get_constraint(on, types).unwrap_or(on); + Constructor::Property { on, under, result, mode, .. } => { + let under = under.substitute(arguments, environment, types); + let on = substitute(on, arguments, environment, types); let on_type = types.get_type_by_id(on); + if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on: TypeId::ARRAY_TYPE, arguments, @@ -318,7 +349,7 @@ pub(crate) fn substitute( let value = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap(); types.new_or_type(value, TypeId::UNDEFINED_TYPE) } else { - let substitutable = arguments.into_substitutable(); + let substitutable = arguments.build_substitutable(); let new_result = substitute( result, @@ -337,30 +368,55 @@ pub(crate) fn substitute( on, under, result: new_result, - bind_this, + mode, })) } } else if let Type::Interface { .. } - | Type::Object(ObjectNature::AnonymousTypeAnnotation) = on_type + | Type::Object(ObjectNature::AnonymousTypeAnnotation) + | Type::AliasTo { .. } = on_type { - let under = match under { - crate::types::properties::PropertyKey::Type(under) => { - let ty = substitute(under, arguments, environment, types); - crate::types::properties::PropertyKey::from_type(ty, types) - } - under @ crate::types::properties::PropertyKey::String(_) => under.clone(), - }; - // TODO union ors etc - match get_property_unbound( + let get_property = get_property_unbound( (on, None), (Publicity::Public, &under, None), + false, environment, types, - ) { - Ok(Logical::Pure(PropertyValue::Value(v))) => v, - result => { - crate::utilities::notify!("Here!! {:?}", result); + ); + + match get_property { + Ok(LogicalOrValid::Logical(value)) => { + fn resolve_logical_during_substitution( + value: Logical, + environment: &Environment, + types: &mut TypeStore, + ) -> TypeId { + match value { + Logical::Pure(v) => { + // substitute(id, arguments, environment, types) + v.as_get_type(types) + } + Logical::Or { .. } => todo!("{:?}", value), + Logical::Implies { .. } => todo!("{:?}", value), + Logical::BasedOnKey(BasedOnKey::Left { .. }) => { + todo!("{:?}", value) + } + Logical::BasedOnKey(BasedOnKey::Right(property_on)) => { + property_on + .get_on(None, environment, types) + .unwrap_or(TypeId::NEVER_TYPE) + } + } + } + + resolve_logical_during_substitution(value, environment, types) + } + Ok(value) => { + crate::utilities::notify!("TODO {:?}", value); + TypeId::ERROR_TYPE + } + Err(err) => { + crate::utilities::notify!("{:?}", err); TypeId::ERROR_TYPE } } @@ -404,18 +460,6 @@ pub(crate) fn substitute( // .get_property(on, property, checking_data, None) // .expect("Inferred constraints and checking failed for a property") } - - // Constructor::PrototypeOf(prototype) => { - // let prototype = substitute(prototype, arguments, environment, types); - // if let Type::AliasTo { to, .. } = types.get_type_by_id(prototype) { - // crate::utilities::notify!( - // "TODO temp might have to do more here when specialising a prototype" - // ); - // *to - // } else { - // todo!() - // } - // } Constructor::CanonicalRelationOperator { lhs, operator, rhs } => { let operator = match operator { crate::features::operations::CanonicalEqualityAndInequality::StrictEqual => { @@ -428,37 +472,49 @@ pub(crate) fn substitute( let lhs = substitute(lhs, arguments, environment, types); let rhs = substitute(rhs, arguments, environment, types); - match evaluate_equality_inequality_operation(lhs, &operator, rhs, types, false) { - Ok(result) => result, - Err(()) => { - unreachable!( - "Cannot {lhs:?} {operator:?} {rhs:?} (restriction or something failed)" - ); - } - } + evaluate_equality_inequality_operation(lhs, &operator, rhs, types, false) } Constructor::TypeOperator(op) => match op { - crate::types::TypeOperator::PrototypeOf(_) => todo!(), crate::types::TypeOperator::TypeOf(ty) => { let ty = substitute(ty, arguments, environment, types); crate::features::type_of_operator(ty, types) } + crate::types::TypeOperator::PrototypeOf(_) => { + unreachable!("'PrototypeOf' should be specialised by events") + } + crate::types::TypeOperator::HasProperty(_, _) => { + unreachable!("'HasProperty' should be specialised by events") + } }, Constructor::TypeRelationOperator(op) => match op { crate::types::TypeRelationOperator::Extends { item, extends } => { + let before = item; let item = substitute(item, arguments, environment, types); let extends = substitute(extends, arguments, environment, types); + { + use crate::types::printing::print_type; + + crate::utilities::notify!( + "Subtyping {} (prev {}) :>= {} ({:?})", + print_type(item, types, environment, true), + print_type(before, types, environment, true), + print_type(extends, types, environment, true), + &arguments.arguments + ); + } + + let mut state = crate::subtyping::State { + already_checked: Default::default(), + mode: Default::default(), + contributions: None, + object_constraints: None, + others: Default::default(), + }; let result = crate::subtyping::type_is_subtype( extends, item, - &mut crate::subtyping::State { - already_checked: Default::default(), - mode: Default::default(), - contributions: None, - object_constraints: None, - others: Default::default(), - }, + &mut state, environment, types, ); @@ -506,7 +562,7 @@ pub(crate) fn substitute( } } -fn compute_extends_rule( +pub(crate) fn compute_extends_rule( extends: TypeId, item: TypeId, environment: &Environment, diff --git a/checker/src/types/intrinsics.rs b/checker/src/types/intrinsics.rs new file mode 100644 index 00000000..27d7e7c1 --- /dev/null +++ b/checker/src/types/intrinsics.rs @@ -0,0 +1,188 @@ +use source_map::SpanWithSource; + +use crate::{ + types::{ + generics::generic_type_arguments::GenericArguments, get_constraint, + PartiallyAppliedGenerics, TypeStore, + }, + TypeId, +}; + +use super::Type; + +/// These are special marker types (using [`Type::Alias`]) +/// +/// Some are from TSC, others are added by me! +pub enum Intrinsic { + /// (TSC) + Uppercase, + /// (TSC) + Lowercase, + /// (TSC) + Capitalize, + /// (TSC) + Uncapitalize, + /// Doesn't add to `Contributions` (TSC) + NoInfer, + /// (EZNO) + CaseInsensitive, + /// Has to be a known constant + Literal, + /// Cannot contain excess properties + Exclusive, + /// This value `< arg`. Also implies [`Intrinsic::NotNotANumber`] + LessThan, + /// This value `> arg`. Also implies [`Intrinsic::NotNotANumber`] + GreaterThan, + /// This value `% arg === 0`. Also implies [`Intrinsic::NotNotANumber`] + MultipleOf, + /// `number \ T ` + Not, +} + +pub(crate) fn distribute_tsc_string_intrinsic( + on: TypeId, + value: TypeId, + types: &mut TypeStore, +) -> TypeId { + match types.get_type_by_id(value) { + Type::Constant(crate::Constant::String(s)) => { + if s.is_empty() { + return value; + } + let transformed = apply_string_intrinsic(on, s); + types.new_constant_type(crate::Constant::String(transformed)) + } + Type::AliasTo { to, .. } => distribute_tsc_string_intrinsic(on, *to, types), + Type::Or(lhs, rhs) => { + let (lhs, rhs) = (*lhs, *rhs); + let lhs = distribute_tsc_string_intrinsic(on, lhs, types); + let rhs = distribute_tsc_string_intrinsic(on, rhs, types); + types.new_or_type(lhs, rhs) + } + _ => types.register_type(Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on, + arguments: GenericArguments::ExplicitRestrictions(crate::Map::from_iter([( + TypeId::STRING_GENERIC, + (value, ::NULL), + )])), + })), + } +} + +pub(crate) fn apply_string_intrinsic(on: TypeId, s: &str) -> String { + match on { + TypeId::STRING_UPPERCASE => s.to_uppercase(), + TypeId::STRING_LOWERCASE => s.to_lowercase(), + TypeId::STRING_CAPITALIZE => { + let mut chars = s.chars(); + let mut s = chars.next().unwrap().to_uppercase().collect::(); + s.extend(chars); + s + } + TypeId::STRING_UNCAPITALIZE => { + let mut chars = s.chars(); + let mut s = chars.next().unwrap().to_lowercase().collect::(); + s.extend(chars); + s + } + _ => unreachable!(), + } +} + +pub fn tsc_string_intrinsic(id: TypeId) -> bool { + matches!( + id, + TypeId::STRING_UPPERCASE + | TypeId::STRING_LOWERCASE + | TypeId::STRING_CAPITALIZE + | TypeId::STRING_UNCAPITALIZE + ) +} + +pub fn is_intrinsic(id: TypeId) -> bool { + tsc_string_intrinsic(id) + || ezno_number_intrinsic(id) + || matches!( + id, + TypeId::LITERAL_RESTRICTION + | TypeId::READONLY_RESTRICTION + | TypeId::NO_INFER + | TypeId::EXCLUSIVE_RESTRICTION + | TypeId::NOT_RESTRICTION + | TypeId::CASE_INSENSITIVE + ) +} + +pub fn ezno_number_intrinsic(id: TypeId) -> bool { + matches!(id, TypeId::LESS_THAN | TypeId::GREATER_THAN | TypeId::MULTIPLE_OF) +} + +pub fn get_greater_than(on: TypeId, types: &TypeStore) -> Option { + let on = get_constraint(on, types).unwrap_or(on); + let ty = types.get_type_by_id(on); + if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::GREATER_THAN, + arguments, + }) = ty + { + arguments.get_structure_restriction(TypeId::NUMBER_GENERIC) + } else if let Type::And(lhs, rhs) = ty { + get_greater_than(*lhs, types).or_else(|| get_greater_than(*rhs, types)) + } else { + None + } +} + +pub fn get_less_than(on: TypeId, types: &TypeStore) -> Option { + let on = get_constraint(on, types).unwrap_or(on); + let ty = types.get_type_by_id(on); + if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::LESS_THAN, + arguments, + }) = ty + { + arguments.get_structure_restriction(TypeId::NUMBER_GENERIC) + } else if let Type::And(lhs, rhs) = ty { + get_less_than(*lhs, types).or_else(|| get_less_than(*rhs, types)) + } else { + None + } +} + +pub fn get_multiple(on: TypeId, types: &TypeStore) -> Option { + let on = get_constraint(on, types).unwrap_or(on); + let ty = types.get_type_by_id(on); + if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::MULTIPLE_OF, + arguments, + }) = ty + { + 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 { + None + } +} + +#[allow(clippy::match_like_matches_macro)] +pub fn can_be_not_a_number(on: TypeId, types: &TypeStore) -> bool { + let on = get_constraint(on, types).unwrap_or(on); + if on == TypeId::NOT_NOT_A_NUMBER { + true + } else { + let ty = types.get_type_by_id(on); + if let Type::And(lhs, rhs) = ty { + can_be_not_a_number(*lhs, types) || can_be_not_a_number(*rhs, types) + } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::MULTIPLE_OF | TypeId::LESS_THAN | TypeId::GREATER_THAN, + arguments: _, + }) = ty + { + true + } else { + false + } + } +} diff --git a/checker/src/types/logical.rs b/checker/src/types/logical.rs new file mode 100644 index 00000000..24d377d9 --- /dev/null +++ b/checker/src/types/logical.rs @@ -0,0 +1,132 @@ +use super::{GenericArguments, SliceArguments, TypeId}; +use crate::features::objects::Proxy; + +/// Wraps logic +#[derive(Debug, Clone)] +pub enum Logical { + Pure(T), + /// Note this uses [`PossibleLogical`] rather than [`Logical`]. + Or { + /// This can be [`TypeId::BOOLEAN_TYPE`] for unknown left-right-ness + condition: TypeId, + left: Box>, + right: Box>, + }, + /// Passes down [`GenericArguments`] found trying to get to source + Implies { + on: Box, + antecedent: GenericArguments, + }, + /// WIP mainly for mapped type properties + BasedOnKey(BasedOnKey), +} + +#[derive(Debug, Clone)] +pub enum BasedOnKey { + Left { value: Box>, key_arguments: SliceArguments }, + Right(PropertyOn), +} + +/// TODO where does Error type fit in here? +#[derive(Debug, Clone)] +pub enum LogicalOrValid { + Logical(Logical), + NeedsCalculation(NeedsCalculation), +} + +impl From> for LogicalOrValid { + fn from(l: Logical) -> LogicalOrValid { + LogicalOrValid::Logical(l) + } +} + +impl From for LogicalOrValid { + fn from(l: NeedsCalculation) -> LogicalOrValid { + LogicalOrValid::NeedsCalculation(l) + } +} + +// /// TODO split up +// #[derive(Debug, Clone)] +// pub enum MissingOrToCalculate { +// /// Doesn't contain request +// Missing, +// /// From [`TypeId::ERROR_TYPE`] +// Error, +// /// From [`TypeId::ANY_TYPE`] +// Infer { on: TypeId }, +// /// Proxies require extra work in some cases +// Proxy(Proxy), +// } + +#[derive(Debug, Clone)] +pub enum NeedsCalculation { + /// From [`TypeId::ANY_TYPE`] + Infer { on: TypeId }, + /// Proxies require extra work in some cases. `TypeId` points to proxy + Proxy(Proxy, TypeId), +} + +#[derive(Debug, Clone, Copy)] +pub struct Invalid(pub TypeId); + +impl Invalid { + fn _is_null(self) -> bool { + self.0 == TypeId::NULL_TYPE + } +} + +/// TODO explain +pub type PossibleLogical = Result, Invalid>; +// pub type PossibleLogical2 = Result, ()>; + +#[derive(Debug, Clone)] +pub struct PropertyOn { + pub on: TypeId, + pub key: TypeId, +} + +impl PropertyOn { + pub fn get_on( + self, + generics: crate::types::GenericChain, + info: &impl crate::context::InformationChain, + types: &mut crate::TypeStore, + ) -> Option { + use crate::types::{get_constraint, properties, PartiallyAppliedGenerics, Type}; + let filter = get_constraint(self.key, types).unwrap_or(self.key); + + crate::utilities::notify!("filter={:?}", types.get_type_by_id(filter)); + + let entries = properties::list::get_properties_on_single_type2( + (self.on, generics), + types, + info, + filter, + ); + + let mut iter = entries.into_iter(); + if let Some((_, first_value, _)) = iter.next() { + // TODO should properly evaluate value + let mut value = first_value.as_get_type(types); + for (_, other, _) in iter { + value = types.new_or_type(value, other.as_get_type(types)); + } + + let complete = !matches!( + types.get_type_by_id(filter), + Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::CASE_INSENSITIVE, + arguments: _, + }) + ); + if complete { + value = types.new_or_type(value, TypeId::UNDEFINED_TYPE); + } + + Some(value) + } else { + None + } + } +} diff --git a/checker/src/types/mod.rs b/checker/src/types/mod.rs index 650b7de6..6b8c131f 100644 --- a/checker/src/types/mod.rs +++ b/checker/src/types/mod.rs @@ -1,9 +1,11 @@ pub mod calling; pub mod casts; pub mod classes; +pub mod disjoint; pub mod functions; pub mod generics; -pub mod others; +pub mod intrinsics; +pub mod logical; pub mod printing; pub mod properties; pub mod store; @@ -12,20 +14,25 @@ mod terms; use derive_debug_extras::DebugExtras; -pub(crate) use generics::substitution::*; +use derive_enum_from_into::EnumFrom; +pub(crate) use generics::{ + chain::{GenericChain, GenericChainLink}, + substitution::*, +}; +pub use crate::features::objects::SpecialObject; pub(crate) use casts::*; +pub use logical::*; use source_map::SpanWithSource; pub use store::TypeStore; pub use terms::Constant; use crate::{ - context::information::InformationChain, + context::InformationChain, events::RootReference, - features::{ - objects::SpecialObjects, - operations::{CanonicalEqualityAndInequality, MathematicalAndBitwise, PureUnary}, - }, + features::operations::{CanonicalEqualityAndInequality, MathematicalAndBitwise, PureUnary}, + subtyping::SliceArguments, + types::{generics::contributions::CovariantContribution, properties::AccessMode}, Decidable, FunctionId, }; @@ -47,15 +54,17 @@ pub type LookUpGenericMap = crate::Map; #[derive(PartialEq, Eq, Clone, Copy, DebugExtras, Hash)] pub struct TypeId(pub(crate) u16); -// TODO ids as macro as to not do in [crate::RootEnvironment] +/// TODO ids as macro as to not do in [`crate::context::RootContext`] impl TypeId { /// Not to be confused with [`TypeId::NEVER_TYPE`] pub const ERROR_TYPE: Self = Self(0); - pub const UNIMPLEMENTED_ERROR_TYPE: TypeId = TypeId::ERROR_TYPE; + pub const UNIMPLEMENTED_ERROR_TYPE: Self = Self::ERROR_TYPE; pub const NEVER_TYPE: Self = Self(1); pub const ANY_TYPE: Self = Self(2); + // TODO + pub const ANY_TO_INFER_TYPE: Self = Self::ANY_TYPE; pub const BOOLEAN_TYPE: Self = Self(3); pub const NUMBER_TYPE: Self = Self(4); @@ -74,7 +83,9 @@ impl TypeId { pub const OBJECT_TYPE: Self = Self(12); pub const FUNCTION_TYPE: Self = Self(13); + /// This points to the `RegExp` prototype pub const REGEXP_TYPE: Self = Self(14); + /// This points to the ??? pub const SYMBOL_TYPE: Self = Self(15); /// For more direct stuff and the rules @@ -82,23 +93,67 @@ impl TypeId { pub const FALSE: Self = Self(17); pub const ZERO: Self = Self(18); pub const ONE: Self = Self(19); - pub const NAN_TYPE: Self = Self(20); + pub const NAN: Self = Self(20); + pub const EMPTY_STRING: Self = Self(21); /// Shortcut for inferred this /// TODO remove - pub const ANY_INFERRED_FREE_THIS: Self = Self(21); + pub const ANY_INFERRED_FREE_THIS: Self = Self(22); /// - pub const NEW_TARGET_ARG: Self = Self(22); + pub const NEW_TARGET_ARG: Self = Self(23); + + pub const IMPORT_META: Self = Self(24); + + // known symbols + /// + pub const SYMBOL_ITERATOR: Self = Self(25); + /// + pub const SYMBOL_ASYNC_ITERATOR: Self = Self(26); + /// + pub const SYMBOL_HAS_INSTANCE: Self = Self(27); + /// + pub const SYMBOL_TO_PRIMITIVE: Self = Self(28); + + // TSC intrinsics + pub const STRING_GENERIC: Self = Self(29); + pub const STRING_UPPERCASE: Self = Self(30); + pub const STRING_LOWERCASE: Self = Self(31); + pub const STRING_CAPITALIZE: Self = Self(32); + pub const STRING_UNCAPITALIZE: Self = Self(33); + pub const NO_INFER: Self = Self(34); + + /// Might be a special type in TSC + pub const READONLY_RESTRICTION: Self = Self(35); + + /// For mapped types + pub const NON_OPTIONAL_KEY_ARGUMENT: Self = Self(36); + /// For mapped types + pub const WRITABLE_KEY_ARGUMENT: Self = Self(37); - pub const SYMBOL_TO_PRIMITIVE: Self = Self(23); + // Ezno intrinsics - pub const LITERAL_RESTRICTION: Self = Self(24); - pub const READONLY_RESTRICTION: Self = Self(25); + /// Used in [`Self::LESS_THAN`], [`Self::LESS_THAN`] and [`Self::MULTIPLE_OF`] + pub const NUMBER_GENERIC: Self = Self(38); + pub const LESS_THAN: Self = Self(39); + pub const GREATER_THAN: Self = Self(40); + pub const MULTIPLE_OF: Self = Self(41); + pub const NOT_NOT_A_NUMBER: Self = Self(42); + pub const NUMBER_BUT_NOT_NOT_A_NUMBER: Self = Self(43); - pub const IMPORT_META: Self = Self(26); + pub const LITERAL_RESTRICTION: Self = Self(44); + pub const EXCLUSIVE_RESTRICTION: Self = Self(45); + pub const NOT_RESTRICTION: Self = Self(46); - pub(crate) const INTERNAL_TYPE_COUNT: usize = 27; + /// This is needed for the TSC string intrinsics + pub const CASE_INSENSITIVE: Self = Self(47); + + /// WIP + pub const OPEN_BOOLEAN_TYPE: Self = Self::BOOLEAN_TYPE; + pub const OPEN_NUMBER_TYPE: Self = Self::NUMBER_TYPE; + + /// Above add one (because [`TypeId`] starts at zero). Used to assert that the above is all correct + pub(crate) const INTERNAL_TYPE_COUNT: usize = 48; } #[derive(Debug, binary_serialize_derive::BinarySerializable)] @@ -126,14 +181,13 @@ pub enum Type { /// Although they all alias Object Interface { name: String, - // Whether only values under this type can be matched - nominal: bool, parameters: Option>, + extends: Option, }, /// Pretty much same as [`Type::Interface`] Class { name: String, - parameters: Option>, + type_parameters: Option>, }, /// *Dependent equality types* Constant(crate::Constant), @@ -143,7 +197,7 @@ pub enum Type { /// Technically could be just a function but... Object(ObjectNature), - SpecialObject(SpecialObjects), + SpecialObject(SpecialObject), } /// TODO difference between untyped and typed parameters and what about parameter based for any @@ -158,13 +212,13 @@ pub enum PolyNature { /// - `fixed_to` can point to [`PolyNature::FunctionGeneric`] Parameter { fixed_to: TypeId }, /// This is on a structure (`class`, `interface` and `type` alias) - StructureGeneric { name: String, constrained: bool }, - /// From `infer U` - InferGeneric { name: String }, + StructureGeneric { name: String, extends: TypeId }, + /// From `infer U`. + InferGeneric { name: String, extends: TypeId }, /// For explicit generics (or on external definitions) - FunctionGeneric { name: String, eager_fixed: TypeId }, + FunctionGeneric { name: String, extends: TypeId }, /// For mapped types - MappedGeneric { name: String, eager_fixed: TypeId }, + MappedGeneric { name: String, extends: TypeId }, /// An error occurred and it looks like Error(TypeId), /// This is generic types. Examples such as a fetch @@ -190,10 +244,11 @@ impl PolyNature { matches!( self, Self::Parameter { .. } - | Self::StructureGeneric { .. } | Self::FunctionGeneric { .. } | Self::InferGeneric { .. } + | Self::MappedGeneric { .. } ) + // | Self::StructureGeneric { .. } } /// The constraint can be adjusted @@ -207,42 +262,24 @@ impl PolyNature { ) } + // TODO remove Option #[must_use] - pub fn try_get_constraint(&self) -> Option { + pub fn get_constraint(&self) -> TypeId { match self { PolyNature::Parameter { fixed_to: to } - | PolyNature::FunctionGeneric { eager_fixed: to, .. } - | PolyNature::MappedGeneric { eager_fixed: to, .. } + | PolyNature::FunctionGeneric { extends: to, .. } + | PolyNature::MappedGeneric { extends: to, .. } | PolyNature::FreeVariable { based_on: to, .. } | PolyNature::RecursiveFunction(_, to) + | PolyNature::StructureGeneric { extends: to, .. } + | PolyNature::InferGeneric { extends: to, .. } | PolyNature::CatchVariable(to) | PolyNature::Open(to) - | PolyNature::Error(to) => Some(*to), - PolyNature::StructureGeneric { .. } | PolyNature::InferGeneric { .. } => None, + | PolyNature::Error(to) => *to, } } } -// TODO -#[must_use] -pub fn is_primitive(ty: TypeId, _types: &TypeStore) -> bool { - if matches!(ty, TypeId::BOOLEAN_TYPE | TypeId::NUMBER_TYPE | TypeId::STRING_TYPE) { - return true; - } - false -} - -/// not full constant but -#[must_use] -pub fn is_type_constant(ty: TypeId, types: &TypeStore) -> bool { - // TODO `Type::SpecialObject(..)` might cause issues - matches!(ty, TypeId::UNDEFINED_TYPE | TypeId::NULL_TYPE) - || matches!( - types.get_type_by_id(ty), - Type::Constant(..) | Type::Object(ObjectNature::RealDeal) | Type::SpecialObject(..) - ) -} - /// TODO split #[derive(Copy, Clone, Debug, binary_serialize_derive::BinarySerializable)] pub enum ObjectNature { @@ -253,8 +290,9 @@ pub enum ObjectNature { } impl Type { + /// These can be referenced by `<...>` pub(crate) fn get_parameters(&self) -> Option> { - if let Type::Class { parameters, .. } + if let Type::Class { type_parameters: parameters, .. } | Type::Interface { parameters, .. } | Type::AliasTo { parameters, .. } = self { @@ -265,7 +303,8 @@ impl Type { } /// TODO return is poly - pub(crate) fn is_dependent(&self) -> bool { + #[must_use] + pub fn is_dependent(&self) -> bool { #[allow(clippy::match_same_arms)] match self { // TODO @@ -284,16 +323,30 @@ impl Type { } } - pub(crate) fn is_operator(&self) -> bool { - matches!(self, Self::And(..) | Self::Or(..)) + #[must_use] + pub fn is_operator(&self) -> bool { + matches!( + self, + Self::And(..) | Self::Or(..) | Self::Constructor(Constructor::ConditionalResult { .. }) + ) + } + + #[must_use] + pub fn is_nominal(&self) -> bool { + matches!(self, Self::Class { .. }) } - fn is_nominal(&self) -> bool { - matches!(self, Self::Class { .. } | Self::Interface { nominal: true, .. }) + #[must_use] + pub fn is_constant(&self) -> bool { + matches!( + self, + Self::Constant(..) | Self::Object(ObjectNature::RealDeal) | Self::SpecialObject(..) + ) } } -/// - Note that no || etc. This is handled using [`Constructor::ConditionalResult`] +/// - Some of these can be specialised, others are only created via event specialisation +/// - Note that no || and && etc. This is handled using [`Constructor::ConditionalResult`] #[derive(Clone, Debug, binary_serialize_derive::BinarySerializable)] pub enum Constructor { // TODO separate add? @@ -325,7 +378,7 @@ pub enum Constructor { Image { on: TypeId, // TODO I don't think this is necessary, maybe for debugging. In such case should be an Rc to share with events - with: Box<[SynthesisedArgument]>, + with: Box<[calling::SynthesisedArgument]>, result: TypeId, }, /// Access @@ -333,7 +386,7 @@ pub enum Constructor { on: TypeId, under: PropertyKey<'static>, result: TypeId, - bind_this: bool, + mode: AccessMode, }, /// For await a poly type Awaited { @@ -375,6 +428,7 @@ pub enum TypeOperator { PrototypeOf(TypeId), /// The `typeof` unary operator TypeOf(TypeId), + HasProperty(TypeId, properties::PropertyKey<'static>), } /// TODO instance of? @@ -424,98 +478,6 @@ pub enum ArgumentOrLookup { LookUpGeneric(LookUpGeneric), } -pub type GenericChain<'a> = Option>; -pub type GenericChainParent<'a> = Option<&'a GenericChainLink<'a>>; - -/// - Used for printing and subtyping. Handles nested restrictions -/// - Uses lifetimes because lifetimes -#[derive(Clone, Copy, Debug)] -pub enum GenericChainLink<'a> { - Link { - from: TypeId, - parent_link: GenericChainParent<'a>, - value: &'a GenericArguments, - }, - FunctionRoot { - parent_link: Option<&'a GenericArguments>, - call_site_type_arguments: Option<&'a crate::Map>, - type_arguments: &'a crate::Map, - }, -} - -impl<'a> GenericChainLink<'a> { - fn get_value(self) -> Option<&'a GenericArguments> { - if let Self::Link { value, .. } = self { - Some(value) - } else { - None - } - } - - /// TODO wip - #[allow(unused)] - pub(crate) fn get_argument( - &self, - on: TypeId, - info: &impl InformationChain, - types: &TypeStore, - ) -> Option> { - match self { - GenericChainLink::Link { parent_link: parent, value, from: _ } => value - .get_argument_as_list(on, info, types) - .or_else(|| parent.and_then(|parent| parent.get_argument(on, info, types))), - GenericChainLink::FunctionRoot { - parent_link: parent, - call_site_type_arguments, - type_arguments, - } => parent - .and_then(|parent| parent.get_argument_as_list(on, info, types)) - .or_else(|| { - call_site_type_arguments.and_then(|ta1| ta1.get(&on).map(|(arg, _)| vec![*arg])) - }) - .or_else(|| type_arguments.get(&on).map(|a| vec![*a])), - } - } - - pub(crate) fn append_to_link( - from: TypeId, - parent: GenericChainParent<'a>, - value: &'a GenericArguments, - ) -> GenericChainLink<'a> { - GenericChainLink::Link { parent_link: parent, value, from } - } - - #[allow(clippy::unnecessary_wraps)] - pub(crate) fn append( - from: TypeId, - parent: GenericChainParent<'a>, - value: &'a GenericArguments, - ) -> GenericChain<'a> { - Some(GenericChainLink::append_to_link(from, parent, value)) - } - - /// Does not do 'lookup generics'. Which may be fine - /// - /// - (swaps `get_argument_as_list` with `get_structure_restriction`) - pub(crate) fn get_single_argument(&self, on: TypeId) -> Option { - match self { - GenericChainLink::Link { parent_link: parent, value, from: _ } => value - .get_structure_restriction(on) - .or_else(|| parent.and_then(|parent| parent.get_single_argument(on))), - GenericChainLink::FunctionRoot { - parent_link: parent, - call_site_type_arguments, - type_arguments, - } => parent - .and_then(|parent| parent.get_structure_restriction(on)) - .or_else(|| { - call_site_type_arguments.and_then(|ta1| ta1.get(&on).map(|(arg, _)| *arg)) - }) - .or_else(|| type_arguments.get(&on).copied()), - } - } -} - // TODO maybe positions and extra information here // SomeLiteralMismatch // GenericParameterCollision @@ -529,6 +491,7 @@ pub enum NonEqualityReason { /// TODO more information MissingParameter, GenericParameterMismatch, + Excess, } #[derive(Debug)] @@ -539,9 +502,12 @@ pub enum PropertyError { /// TODO temp fix for printing pub(crate) fn is_explicit_generic(on: TypeId, types: &TypeStore) -> bool { - if let Type::RootPolyType(PolyNature::FunctionGeneric { .. }) = types.get_type_by_id(on) { + if let Type::RootPolyType( + PolyNature::FunctionGeneric { .. } | PolyNature::StructureGeneric { .. }, + ) = types.get_type_by_id(on) + { true - } else if let Type::Constructor(Constructor::Property { on, under, result: _, bind_this: _ }) = + } else if let Type::Constructor(Constructor::Property { on, under, result: _, mode: _ }) = types.get_type_by_id(on) { is_explicit_generic(*on, types) @@ -556,25 +522,7 @@ pub(crate) fn is_explicit_generic(on: TypeId, types: &TypeStore) -> bool { /// **Also looks at possibly mutated things pub(crate) fn get_constraint(on: TypeId, types: &TypeStore) -> Option { match types.get_type_by_id(on) { - Type::RootPolyType(nature) => Some( - *(match nature { - PolyNature::Parameter { fixed_to } - | PolyNature::MappedGeneric { name: _, eager_fixed: fixed_to } - | PolyNature::FunctionGeneric { name: _, eager_fixed: fixed_to } => fixed_to, - PolyNature::Error(ty) | PolyNature::Open(ty) => ty, - PolyNature::FreeVariable { reference: _, based_on } => based_on, - PolyNature::RecursiveFunction(_, return_ty) => return_ty, - PolyNature::StructureGeneric { constrained, .. } => { - return if *constrained { - todo!("get from TypeStore or ???") - } else { - Some(TypeId::ANY_TYPE) - } - } - PolyNature::CatchVariable(constraint) => constraint, - PolyNature::InferGeneric { .. } => return Some(TypeId::ANY_TYPE), - }), - ), + Type::RootPolyType(nature) => Some(nature.get_constraint()), Type::Constructor(constructor) => match constructor.clone() { Constructor::BinaryOperator { lhs, operator, rhs } => { if let MathematicalAndBitwise::Add = operator { @@ -594,8 +542,11 @@ pub(crate) fn get_constraint(on: TypeId, types: &TypeStore) -> Option { Some(TypeId::NUMBER_TYPE) } } - Constructor::UnaryOperator { operand: _, operator: _ } => { - todo!() + Constructor::UnaryOperator { operand: _, operator } => { + Some(match operator { + PureUnary::LogicalNot => TypeId::BOOLEAN_TYPE, + PureUnary::Negation | PureUnary::BitwiseNot => TypeId::NUMBER_TYPE, + }) // if *constraint == TypeId::ANY_TYPE && mutable_context { // let (operand, operator) = (operand.clone(), operator.clone()); // let constraint = to(self, data); @@ -613,8 +564,8 @@ pub(crate) fn get_constraint(on: TypeId, types: &TypeStore) -> Option { } Constructor::Awaited { on: _, result } | Constructor::Image { on: _, with: _, result } => Some(result), - Constructor::Property { on: _, under: _, result, bind_this: _ } => { - crate::utilities::notify!("Here, result of a property get"); + Constructor::Property { on: _, under: _, result, mode: _ } => { + // crate::utilities::notify!("Here, result of a property get"); Some(result) } Constructor::ConditionalResult { result_union, .. } => { @@ -638,7 +589,9 @@ pub(crate) fn get_constraint(on: TypeId, types: &TypeStore) -> Option { } } -fn get_larger_type(on: TypeId, types: &TypeStore) -> 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) { @@ -656,7 +609,12 @@ pub enum LookUpGeneric { impl LookUpGeneric { #[allow(unreachable_patterns)] - pub(crate) fn calculate_lookup(&self, info: &impl InformationChain, on: TypeId) -> Vec { + pub(crate) fn calculate_lookup( + &self, + info: &impl InformationChain, + types: &TypeStore, + on: TypeId, + ) -> Vec { match self { LookUpGeneric::NumberPropertyOfSelf => { info.get_chain_of_info() @@ -667,7 +625,7 @@ impl LookUpGeneric { if matches!(key, PropertyKey::String(s) if s == "length") { None } else { - Some(value.as_get_type()) + Some(value.as_get_type(types)) } }) .collect() @@ -742,3 +700,262 @@ pub(crate) fn get_structure_arguments_based_on_object_constraint<'a, C: Informat 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) = +} + +/// For getting `length` and stuff +fn get_simple_value( + ctx: &impl InformationChain, + on: TypeId, + property: &PropertyKey, + types: &TypeStore, +) -> Option { + fn get_logical(v: Logical) -> Option { + match v { + Logical::Pure(crate::PropertyValue::Value(t)) => Some(t), + Logical::Implies { on, antecedent: _ } => get_logical(*on), + _ => None, + } + } + + let value = properties::get_property_unbound( + (on, None), + (properties::Publicity::Public, property, None), + false, + ctx, + types, + ) + .ok()?; + + if let LogicalOrValid::Logical(value) = value { + get_logical(value) + } else { + None + } +} + +fn get_array_length( + ctx: &impl InformationChain, + on: TypeId, + types: &TypeStore, +) -> Result, Option> { + let length_property = PropertyKey::String(std::borrow::Cow::Borrowed("length")); + let 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, ()> { + 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, + })); + } + } + } + + 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, + })); + *self = Counter::AddTo(new); + } + + pub(crate) fn into_property_key(self) -> PropertyKey<'static> { + match self { + Counter::On(value) => PropertyKey::from_usize(value), + Counter::AddTo(ty) => 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 +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: _ } = c { + references_key_of(*lhs, types) || references_key_of(*rhs, types) + } else { + // TODO might have missed something here + 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::FunctionReference(_) + | Type::Object(_) + | Type::SpecialObject(_) => false, + } +} + +#[allow(clippy::match_like_matches_macro)] +pub fn type_is_error(ty: TypeId, types: &TypeStore) -> bool { + if ty == TypeId::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 +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 +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 + } + } +} + +pub fn is_inferrable_type(ty: TypeId) -> bool { + matches!(ty, TypeId::ANY_TO_INFER_TYPE | TypeId::OBJECT_TYPE) +} diff --git a/checker/src/types/others.rs b/checker/src/types/others.rs deleted file mode 100644 index 776c0d65..00000000 --- a/checker/src/types/others.rs +++ /dev/null @@ -1,147 +0,0 @@ -// Types to runtime behavior - -use source_map::SpanWithSource; - -use crate::{ - features::objects::{ObjectBuilder, SpecialObjects}, - types::properties::{get_properties_on_single_type, Publicity}, - Constant, Environment, Type, TypeId, -}; - -use super::{properties::PropertyKey, TypeStore}; - -#[allow(unused)] -pub(crate) fn create_object_for_type( - ty: TypeId, - environment: &mut Environment, - // &mut to create new objects - types: &mut TypeStore, - call_site: SpanWithSource, -) -> TypeId { - let mut obj = ObjectBuilder::new(None, types, call_site, &mut environment.info); - match types.get_type_by_id(ty) { - Type::AliasTo { to: _, name: _, parameters: _ } => todo!(), - ty @ (Type::And(left, right) | Type::Or(left, right)) => { - let kind = if matches!(ty, Type::And(..)) { "and" } else { "or" }; - let (left, right) = (*left, *right); - - // TODO: Do we need positions for the following appends? - obj.append( - environment, - Publicity::Public, - PropertyKey::String("kind".into()), - crate::PropertyValue::Value(types.new_constant_type(Constant::String(kind.into()))), - call_site, - ); - let left = create_object_for_type(left, environment, types, call_site); - let right = create_object_for_type(right, environment, types, call_site); - obj.append( - environment, - Publicity::Public, - PropertyKey::String("left".into()), - crate::PropertyValue::Value(left), - call_site, - ); - obj.append( - environment, - Publicity::Public, - PropertyKey::String("right".into()), - crate::PropertyValue::Value(right), - call_site, - ); - } - Type::RootPolyType(_) => todo!(), - Type::Constructor(_) => todo!(), - Type::Interface { name, parameters: _, nominal: _ } => { - let name = name.clone(); - - // TODO: Do we need positions for the following appends? - obj.append( - environment, - Publicity::Public, - PropertyKey::String("name".into()), - crate::PropertyValue::Value(types.new_constant_type(Constant::String(name))), - call_site, - ); - - if !matches!(ty, TypeId::BOOLEAN_TYPE | TypeId::STRING_TYPE | TypeId::NUMBER_TYPE) { - // TODO array - let mut inner_object = - ObjectBuilder::new(None, types, call_site, &mut environment.info); - - // let properties = env.create_array(); - for (_, key, property) in get_properties_on_single_type(ty, types, environment) { - todo!() - // let value = create_object_for_type(property, environment, types); - // inner_object.append( - // environment, - // Publicity::Public, - // key, - // crate::PropertyValue::Value(value), - // None, - // ); - } - - obj.append( - environment, - Publicity::Public, - PropertyKey::String("properties".into()), - crate::PropertyValue::Value(inner_object.build_object()), - call_site, - ); - } - } - Type::Constant(_) => { - obj.append( - environment, - Publicity::Public, - PropertyKey::String("constant".into()), - crate::PropertyValue::Value(ty), - call_site, - ); - } - Type::SpecialObject(SpecialObjects::Function(..)) => todo!(), - Type::FunctionReference(..) => todo!(), - Type::Object(_) => { - let value = crate::PropertyValue::Value( - types.new_constant_type(Constant::String("anonymous object".into())), - ); - obj.append( - environment, - Publicity::Public, - PropertyKey::String("kind".into()), - value, - call_site, - ); - - // TODO array - let mut inner_object = - ObjectBuilder::new(None, types, call_site, &mut environment.info); - - // let properties = env.create_array(); - for (_, key, property) in get_properties_on_single_type(ty, types, environment) { - todo!() - // let value = create_object_for_type(property, environment, types); - // inner_object.append( - // environment, - // Publicity::Public, - // key, - // crate::PropertyValue::Value(value), - // None, - // ); - } - - obj.append( - environment, - Publicity::Public, - PropertyKey::String("properties".into()), - crate::PropertyValue::Value(inner_object.build_object()), - call_site, - ); - } - Type::SpecialObject(_) => todo!(), - Type::Class { name, parameters } => todo!(), - Type::PartiallyAppliedGenerics(..) => todo!(), - } - obj.build_object() -} diff --git a/checker/src/types/printing.rs b/checker/src/types/printing.rs index 6c27c746..695491d1 100644 --- a/checker/src/types/printing.rs +++ b/checker/src/types/printing.rs @@ -1,26 +1,20 @@ use iterator_endiate::EndiateIteratorExt; -use source_map::{Nullable, SpanWithSource}; use std::collections::HashSet; -use super::{properties::PropertyKey, GenericChain, PolyNature, Type, TypeId, TypeStore}; +use super::{GenericChain, PolyNature, Type, TypeId, TypeStore}; use crate::{ - context::{ - information::{get_value_of_constant_import_variable, InformationChain}, - Logical, - }, - events::{Event, FinalEvent}, - features::{ - functions::ThisValue, - objects::{Proxy, SpecialObjects}, - }, + context::information::{get_value_of_constant_import_variable, InformationChain}, + features::objects::{Proxy, SpecialObject}, types::{ + calling::ThisValue, + functions::{FunctionBehavior, FunctionEffect}, generics::generic_type_arguments::GenericArguments, - get_constraint, - properties::{get_properties_on_single_type, get_property_unbound, Publicity}, - Constructor, FunctionEffect, GenericChainLink, ObjectNature, PartiallyAppliedGenerics, + get_array_length, get_constraint, get_simple_value, + properties::{get_properties_on_single_type, AccessMode, PropertyKey, Publicity}, + Constructor, GenericChainLink, ObjectNature, PartiallyAppliedGenerics, TypeRelationOperator, }, - Constant, PropertyValue, + PropertyValue, }; #[must_use] @@ -65,7 +59,7 @@ pub fn print_type_with_type_arguments( } /// Recursion safe + reuses buffer -fn print_type_into_buf( +pub fn print_type_into_buf( ty: TypeId, buf: &mut String, cycles: &mut HashSet, @@ -95,54 +89,64 @@ fn print_type_into_buf( print_type_into_buf(*b, buf, cycles, args, types, info, debug); } Type::RootPolyType(nature) => match nature { - PolyNature::FunctionGeneric { name, .. } - | PolyNature::MappedGeneric { name, .. } - | PolyNature::StructureGeneric { name, constrained: _ } => { - if let Some(structure_args) = - args.and_then(|args| args.get_argument(ty, info, types)) + PolyNature::MappedGeneric { name, extends } => { + if debug { + write!(buf, "[mg {} {}] ", ty.0, name).unwrap(); + } + crate::utilities::notify!("args={:?}", args); + if let Some(crate::types::CovariantContribution::String(property)) = + args.and_then(|arg| arg.get_argument_covariant(ty)) { + write!(buf, "{property}").unwrap(); + } else { + print_type_into_buf(*extends, buf, cycles, args, types, info, debug); + } + } + PolyNature::FunctionGeneric { name, .. } + | PolyNature::StructureGeneric { name, extends: _ } => { + if debug { + if let PolyNature::FunctionGeneric { .. } = nature { + 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 { - if let PolyNature::FunctionGeneric { .. } = nature { - write!(buf, "[fg {} {}, =]", ty.0, name).unwrap(); - } + buf.push_str(" (specialised with) "); } - for (more, arg) in structure_args.iter().nendiate() { - print_type_into_buf(*arg, buf, cycles, args, types, info, debug); - if more { - buf.push_str(" | "); + match arg { + CovariantContribution::TypeId(id) => { + print_type_into_buf(id, buf, cycles, args, types, info, debug); + } + arg => { + crate::utilities::notify!("TODO print {:?}", arg); } } + + // for (more, arg) in structure_args.iter().nendiate() { + // print_type_into_buf(*arg, buf, cycles, args, types, info, debug); + // if more { + // buf.push_str(" | "); + // } + // } } else { if debug { - if let PolyNature::FunctionGeneric { eager_fixed, .. } - | PolyNature::MappedGeneric { eager_fixed, .. } = nature - { - let key = match nature { - PolyNature::FunctionGeneric { .. } => "fg", - PolyNature::MappedGeneric { .. } => "mg", - _ => "??", - }; - write!(buf, "[{key} {}, @ ", ty.0).unwrap(); - print_type_into_buf( - *eager_fixed, - buf, - cycles, - args, - types, - info, - debug, - ); - buf.push_str("] "); + 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); } } - PolyNature::InferGeneric { name } => { + PolyNature::InferGeneric { name, extends } => { buf.push_str("infer "); buf.push_str(name); + if *extends != TypeId::ANY_TYPE { + buf.push_str(" extends "); + print_type_into_buf(*extends, buf, cycles, args, types, info, debug); + } } PolyNature::FreeVariable { based_on: to, .. } => { if debug { @@ -181,7 +185,7 @@ fn print_type_into_buf( } PolyNature::CatchVariable(constraint) => { if debug { - write!(buf, "[catch variable {}] ", ty.0).unwrap(); + write!(buf, "[catch variable {ty:?}] ").unwrap(); } print_type_into_buf(*constraint, buf, cycles, args, types, info, debug); } @@ -215,6 +219,10 @@ fn print_type_into_buf( } else if let Type::Class { .. } | Type::Interface { .. } | Type::AliasTo { .. } = types.get_type_by_id(*on) { + if debug { + write!(buf, "SG over ({:?})", types.get_type_by_id(*on)).unwrap(); + } + // on can be sometimes be generic print_type_into_buf(*on, buf, cycles, args, types, info, debug); match arguments { @@ -232,6 +240,10 @@ fn print_type_into_buf( GenericArguments::Closure(..) | GenericArguments::LookUp { .. } => {} } } else { + if debug { + write!(buf, "SG over ({:?})", types.get_type_by_id(*on)).unwrap(); + } + let new_arguments = arguments.clone(); let args = GenericChainLink::append(ty, args.as_ref(), &new_arguments); print_type_into_buf(*on, buf, cycles, args, types, info, debug); @@ -245,13 +257,26 @@ fn print_type_into_buf( otherwise_result, result_union: _, } => { - if debug { - write!(buf, "?#{} ", ty.0).unwrap(); + // TODO nested on constructor + let is_standard_generic = matches!( + types.get_type_by_id(*condition), + Type::RootPolyType( + PolyNature::InferGeneric { .. } + | PolyNature::MappedGeneric { .. } + | PolyNature::FunctionGeneric { .. } + | PolyNature::StructureGeneric { .. } + ) + ); + + if debug || is_standard_generic { + if debug { + write!(buf, "?#{} ", ty.0).unwrap(); + } print_type_into_buf(*condition, buf, cycles, args, types, info, debug); - buf.push_str("? "); + buf.push_str(" ? "); } print_type_into_buf(*truthy_result, buf, cycles, args, types, info, debug); - buf.push_str(if debug { " : " } else { " | " }); + buf.push_str(if debug || is_standard_generic { " : " } else { " | " }); print_type_into_buf(*otherwise_result, buf, cycles, args, types, info, debug); } Constructor::KeyOf(on) => { @@ -260,7 +285,7 @@ fn print_type_into_buf( buf.push_str("keyof "); print_type_into_buf(*on, buf, cycles, args, types, info, debug); // } else { - // let properties = get_properties_on_type(*on, types, info); + // let properties = get_properties_on_type(*on, types); // if properties.is_empty() { // buf.push_str("never"); // } else { @@ -278,9 +303,22 @@ fn print_type_into_buf( // } // } } - Constructor::Property { on, under, result, bind_this: _ } => { - if crate::types::is_explicit_generic(*on, types) { - print_type_into_buf(*on, buf, cycles, args, types, info, debug); + Constructor::Property { on, under, result, mode: _ } => { + // crate::utilities::notify!("before {:?}", types.get_type_by_id(*on)); + let on = if let Some(crate::types::CovariantContribution::TypeId(value)) = + args.and_then(|arg| arg.get_argument_covariant(*on)) + { + value + } else { + *on + }; + + // crate::utilities::notify!("after {:?}", types.get_type_by_id(on)); + + if crate::types::is_explicit_generic(on, types) + || matches!(types.get_type_by_id(on), Type::Interface { .. } | Type::Object(_)) + { + print_type_into_buf(on, buf, cycles, args, types, info, debug); buf.push('['); match under { PropertyKey::String(s) => { @@ -294,12 +332,29 @@ fn print_type_into_buf( }; buf.push(']'); } else if let Some(Type::PartiallyAppliedGenerics(sgs)) = - get_constraint(*on, types).map(|ty| types.get_type_by_id(ty)) + get_constraint(on, types).map(|ty| types.get_type_by_id(ty)) { let new_arguments = sgs.arguments.clone(); let args = GenericChainLink::append(ty, args.as_ref(), &new_arguments); print_type_into_buf(*result, buf, cycles, args, types, info, debug); } else { + if debug { + buf.push_str("(property on "); + print_type_into_buf(on, buf, cycles, args, types, info, debug); + buf.push_str(" under "); + match under { + PropertyKey::String(s) => { + buf.push('"'); + buf.push_str(s); + buf.push('"'); + } + PropertyKey::Type(t) => { + print_type_into_buf(*t, buf, cycles, args, types, info, debug); + } + } + write!(buf, "{ty:?}").unwrap(); + buf.push_str(") "); + } print_type_into_buf(*result, buf, cycles, args, types, info, debug); } } @@ -326,7 +381,7 @@ fn print_type_into_buf( print_type_into_buf(*operand, buf, cycles, args, types, info, debug); } Constructor::TypeOperator(to) => { - write!(buf, "TypeOperator = {to:?}").unwrap(); + write!(buf, "TypeOperator.{to:?}").unwrap(); } Constructor::TypeRelationOperator(TypeRelationOperator::Extends { item, @@ -337,18 +392,18 @@ fn print_type_into_buf( print_type_into_buf(*extends, buf, cycles, args, types, info, debug); } Constructor::Image { on: _, with: _, result } => { - write!(buf, "[func result {}] (*args here*)", ty.0).unwrap(); // TODO arguments + write!(buf, "[func result {}] (*args*)", ty.0).unwrap(); buf.push_str(" -> "); print_type_into_buf(*result, buf, cycles, args, types, info, debug); } - Constructor::Property { on, under, result, bind_this } => { + Constructor::Property { on, under, result, mode } => { buf.push('('); print_type_into_buf(*on, buf, cycles, args, types, info, debug); buf.push_str(")["); print_property_key_into_buf(under, buf, cycles, args, types, info, debug); buf.push(']'); - if !bind_this { + if let AccessMode::DoNotBindThis = mode { buf.push_str(" no bind"); }; buf.push_str(" = "); @@ -374,19 +429,28 @@ fn print_type_into_buf( print_type_into_buf(base, buf, cycles, args, types, info, debug); } }, - t @ (Type::Class { name, parameters: _, .. } + t @ (Type::Class { name, type_parameters: _, .. } | Type::Interface { name, parameters: _, .. } | Type::AliasTo { to: _, name, parameters: _ }) => { if debug { write!(buf, "{name}#{}", ty.0).unwrap(); + if let Type::AliasTo { to, .. } = t { + buf.push_str(" = "); + print_type_into_buf(*to, buf, cycles, args, types, info, debug); + } else if let Type::Class { .. } = t { + buf.push_str(" (class)"); + } } else { buf.push_str(name); } - if let (true, Type::AliasTo { to, .. }) = (debug, t) { - buf.push_str(" to "); - print_type_into_buf(*to, buf, cycles, args, types, info, debug); - } - // TODO + + // TODO under option + // if let Type::AliasTo { to, .. } = t { + // buf.push_str(" = "); + // print_type_into_buf(*to, buf, cycles, args, types, info, debug); + // } + + // TODO only if not partially applied generic // if let (true, Some(parameters)) = (debug, parameters) { // buf.push('{'); // for param in parameters { @@ -404,8 +468,22 @@ fn print_type_into_buf( } } Type::FunctionReference(func_id) - | Type::SpecialObject(SpecialObjects::Function(func_id, _)) => { + | Type::SpecialObject(SpecialObject::Function(func_id, _)) => { let func = types.functions.get(func_id).unwrap(); + + if let FunctionBehavior::Constructor { ref name, prototype, .. } = func.behavior { + if let Type::Constant(crate::Constant::String(name)) = types.get_type_by_id(*name) { + if debug { + write!(buf, "constructor(for#{})@{name}#{}", prototype.0, ty.0).unwrap(); + } else { + buf.push_str(name); + return; + } + } else { + buf.push_str("*class*"); + } + } + if debug { let kind = if matches!(r#type, Type::FunctionReference(_)) { "ref" } else { "" }; write!(buf, "[func{kind} #{}, kind {:?}, effect ", ty.0, func.behavior).unwrap(); @@ -415,12 +493,12 @@ fn print_type_into_buf( closed_over_variables, } = &func.effect { - write!(buf, "*side effects* {free_variables:?} {closed_over_variables:?} ") + write!(buf, "*side effects* {free_variables:?} {closed_over_variables:?}") .unwrap(); } else { - write!(buf, "{:?} ", func.effect).unwrap(); + write!(buf, "{:?}", func.effect).unwrap(); } - if let Type::SpecialObject(SpecialObjects::Function(_, ThisValue::Passed(p))) = + if let Type::SpecialObject(SpecialObject::Function(_, ThisValue::Passed(p))) = r#type { buf.push_str(", this "); @@ -480,34 +558,54 @@ fn print_type_into_buf( 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, "[aol {}] ", ty.0).unwrap(); } } let prototype = info.get_chain_of_info().find_map(|info| info.prototypes.get(&ty).copied()); if let Some(TypeId::ARRAY_TYPE) = prototype { - if let Some(n) = get_array_length(info, ty, types) { - buf.push('['); - for i in 0..(n.into_inner() as usize) { - if i != 0 { - buf.push_str(", "); - } - let value = get_simple_value(info, ty, &PropertyKey::from_usize(i), types); + match get_array_length(info, ty, types) { + Ok(n) => { + buf.push('['); + for i in 0..(n.into_inner() as usize) { + if i != 0 { + buf.push_str(", "); + } + let value = + get_simple_value(info, ty, &PropertyKey::from_usize(i), types); - if let Some(value) = value { - print_type_into_buf(value, buf, cycles, args, types, info, debug); + if let Some(value) = value { + print_type_into_buf(value, buf, cycles, args, types, info, debug); + } else { + // TODO sometimes the above is not always `None` as `None` can occur for complex keys... + buf.push_str("*empty*"); + } + } + buf.push(']'); + } + Err(n) => { + if let Some(n) = n { + crate::utilities::notify!("Printing array with length={:?}", n); } else { - // TODO sometimes the above is not always `None` as `None` can occur for complex keys... - buf.push_str("*empty*"); + crate::utilities::notify!("Printing array with no length"); + } + + if debug { + let properties = get_properties_on_single_type( + ty, + types, + info, + false, + TypeId::ANY_TYPE, + ); + crate::utilities::notify!("Array {:?}", properties); } + // TODO get property + write!(buf, "Array<*things*>").unwrap(); } - buf.push(']'); - } else { - // TODO get property - write!(buf, "Array").unwrap(); } } else { if let Some(prototype) = prototype { @@ -518,59 +616,121 @@ fn print_type_into_buf( } else { // crate::utilities::notify!("no P on {:?} during print", id); } + // Important! + let filter_enumerable = false; + let properties = get_properties_on_single_type( + ty, + types, + info, + filter_enumerable, + TypeId::ANY_TYPE, + ); + if properties.is_empty() { + buf.push_str("{}"); + return; + } + buf.push_str("{ "); - let properties = get_properties_on_single_type(ty, types, info); for (not_at_end, (publicity, key, value)) in properties.into_iter().nendiate() { if let Publicity::Private = publicity { buf.push('#'); } - let root; - let args = if let Some((id, to)) = key.mapped_generic_id(types) { - let mut map = crate::Map::default(); - map.insert(id, (to, SpanWithSource::NULL)); - root = GenericArguments::ExplicitRestrictions(map); - Some(GenericChainLink::append_to_link(id, args.as_ref(), &root)) - } else { - args - }; + // let root; + // let args = if let Some((id, to)) = key.mapped_generic_id(types) { + // let mut map = crate::Map::default(); + // map.insert(id, (to, SpanWithSource::NULL)); + // root = GenericArguments::ExplicitRestrictions(map); + // Some(GenericChainLink::append_to_link(id, args.as_ref(), &root)) + // } else { + // args + // }; - match value { + let is_optional = value.is_optional_simple(); + let is_readonly = value.is_writable_simple(); + + if is_readonly { + buf.push_str("readonly "); + } + + // TODO methods here + + match value.inner_simple() { PropertyValue::Value(value) => { print_property_key_into_buf( &key, buf, cycles, args, types, info, debug, ); - buf.push_str(": "); - print_type_into_buf(value, buf, cycles, args, types, info, debug); + buf.push_str(if is_optional { "?: " } else { ": " }); + print_type_into_buf(*value, buf, cycles, args, types, info, debug); } - PropertyValue::Getter(_) => { + PropertyValue::Getter(getter) => { print_property_key_into_buf( &key, buf, cycles, args, types, info, debug, ); - buf.push_str(": (getter)"); + buf.push_str(if is_optional { "?: " } else { ": " }); + buf.push_str("(getter) "); + print_type_into_buf( + getter.get_return_type(types), + buf, + cycles, + args, + types, + info, + debug, + ); + } + PropertyValue::GetterAndSetter { getter, setter } => { + print_property_key_into_buf( + &key, buf, cycles, args, types, info, debug, + ); + buf.push_str(if is_optional { "?: " } else { ": " }); + buf.push_str("(getter) "); + print_type_into_buf( + getter.get_return_type(types), + buf, + cycles, + args, + types, + info, + debug, + ); + buf.push_str(" + (setter) "); + print_type_into_buf( + setter.get_first_argument(types), + buf, + cycles, + args, + types, + info, + debug, + ); } - PropertyValue::Setter(_) => { + PropertyValue::Setter(setter) => { print_property_key_into_buf( &key, buf, cycles, args, types, info, debug, ); - buf.push_str(": (setter)"); + buf.push_str(if is_optional { "?: " } else { ": " }); + buf.push_str("(setter) "); + print_type_into_buf( + setter.get_first_argument(types), + buf, + cycles, + args, + types, + info, + debug, + ); } PropertyValue::Deleted => { print_property_key_into_buf( &key, buf, cycles, args, types, info, debug, ); - buf.push_str(": never"); + buf.push_str(if is_optional { "?: " } else { ": " }); + buf.push_str("never"); } - PropertyValue::ConditionallyExists { on: _, truthy } => { - if let PropertyValue::Value(value) = *truthy { - print_property_key_into_buf( - &key, buf, cycles, args, types, info, debug, - ); - buf.push_str("?: "); - print_type_into_buf(value, buf, cycles, args, types, info, debug); - } else { - todo!() - } + PropertyValue::ConditionallyExists { .. } + | PropertyValue::Configured { .. } => { + unreachable!("should be unreachable by `inner_simple`") } } @@ -582,9 +742,12 @@ fn print_type_into_buf( } } Type::SpecialObject(special_object) => match special_object { - SpecialObjects::Promise { events: () } => todo!(), - SpecialObjects::Generator { position: () } => todo!(), - SpecialObjects::Proxy(Proxy { handler, over }) => { + SpecialObject::Null => { + buf.push_str("null"); + } + SpecialObject::Promise { .. } => todo!(), + SpecialObject::Generator { .. } => todo!(), + SpecialObject::Proxy(Proxy { handler, over }) => { // Copies from node behavior buf.push_str("Proxy [ "); print_type_into_buf(*over, buf, cycles, args, types, info, debug); @@ -592,7 +755,7 @@ fn print_type_into_buf( print_type_into_buf(*handler, buf, cycles, args, types, info, debug); buf.push_str(" ]"); } - SpecialObjects::Import(exports) => { + SpecialObject::Import(exports) => { buf.push_str("{ "); for (not_at_end, (key, (variable, mutability))) in exports.named.iter().nendiate() { buf.push_str(key); @@ -605,66 +768,30 @@ fn print_type_into_buf( crate::features::variables::VariableMutability::Mutable { reassignment_constraint: _, } => todo!(), - }; + } if not_at_end { buf.push_str(", "); } } buf.push_str(" }"); } - SpecialObjects::RegularExpression(exp) => { - buf.push('/'); - buf.push_str(exp); - buf.push('/'); - } - SpecialObjects::Function(..) => unreachable!(), - SpecialObjects::ClassConstructor { name, prototype, constructor: _ } => { - if debug { - write!(buf, "constructor(for#{})@{name}#{}", prototype.0, ty.0).unwrap(); + SpecialObject::RegularExpression { content, .. } => { + // TODO flags + if let Type::Constant(crate::Constant::String(name)) = + types.get_type_by_id(*content) + { + write!(buf, "/{name}/").unwrap(); } else { - buf.push_str(name); + buf.push_str("RegExp"); } } + SpecialObject::Function(..) => unreachable!(), }, } cycles.remove(&ty); } -/// For getting `length` and stuff -fn get_simple_value( - ctx: &impl InformationChain, - on: TypeId, - property: &PropertyKey, - types: &TypeStore, -) -> Option { - fn get_logical(v: Logical) -> Option { - match v { - Logical::Pure(PropertyValue::Value(t)) => Some(t), - Logical::Implies { on, antecedent: _ } => get_logical(*on), - _ => None, - } - } - - get_property_unbound((on, None), (Publicity::Public, property, None), ctx, types) - .ok() - .and_then(get_logical) -} - -fn get_array_length( - ctx: &impl InformationChain, - on: TypeId, - types: &TypeStore, -) -> Option> { - let length_property = PropertyKey::String(std::borrow::Cow::Borrowed("length")); - let id = get_simple_value(ctx, on, &length_property, types)?; - if let Type::Constant(Constant::Number(n)) = types.get_type_by_id(id) { - Some(*n) - } else { - None - } -} - #[must_use] pub fn print_property_key( key: &PropertyKey, @@ -703,168 +830,3 @@ pub(crate) fn print_property_key_into_buf( } } } - -pub fn debug_effects( - buf: &mut String, - events: &[Event], - types: &TypeStore, - info: &C, - depth: u8, - debug: bool, -) { - use std::fmt::Write; - - let args = GenericChain::None; - - let mut idx = 0; - - while idx < events.len() { - for _ in 0..depth { - buf.push('\t'); - } - let event = &events[idx]; - match event { - Event::ReadsReference { reference, reflects_dependency, position: _ } => { - write!(buf, "read '{reference:?}' into {reflects_dependency:?}").unwrap(); - } - Event::SetsVariable(variable, value, _) => { - write!(buf, "{variable:?}' = ").unwrap(); - print_type_into_buf(*value, buf, &mut HashSet::new(), args, types, info, debug); - } - Event::Getter { - on, - under, - reflects_dependency, - publicity: _, - position: _, - bind_this: _, - } => { - buf.push_str("read "); - print_type_into_buf(*on, buf, &mut HashSet::new(), args, types, info, debug); - if let PropertyKey::String(_) = under { - buf.push('.'); - } - print_property_key_into_buf( - under, - buf, - &mut HashSet::new(), - args, - types, - info, - debug, - ); - write!(buf, " into {reflects_dependency:?}").unwrap(); - } - Event::Setter { on, under, new, initialization, publicity: _, position: _ } => { - if *initialization { - write!(buf, "initialise {:?} with ", *on).unwrap(); - if let PropertyValue::Value(new) = new { - print_type_into_buf( - *new, - buf, - &mut HashSet::new(), - args, - types, - info, - debug, - ); - } - } else { - print_type_into_buf(*on, buf, &mut HashSet::new(), args, types, info, debug); - buf.push('['); - print_property_key_into_buf( - under, - buf, - &mut HashSet::default(), - args, - types, - info, - debug, - ); - buf.push_str("] = "); - if let PropertyValue::Value(new) = new { - print_type_into_buf( - *new, - buf, - &mut HashSet::new(), - args, - types, - info, - debug, - ); - } - } - } - Event::CallsType { - on, - with: _, - reflects_dependency, - timing, - called_with_new: _, - position: _, - possibly_thrown: _, - } => { - buf.push_str("call "); - print_type_into_buf(*on, buf, &mut HashSet::new(), args, types, info, debug); - write!(buf, " into {reflects_dependency:?} ",).unwrap(); - buf.push_str(match timing { - crate::events::CallingTiming::Synchronous => "now", - crate::events::CallingTiming::QueueTask => "queue", - crate::events::CallingTiming::AtSomePointManyTimes => "sometime", - }); - // TODO args - } - Event::Conditionally { condition, truthy_events, otherwise_events, position: _ } => { - let truthy_events = *truthy_events as usize; - let otherwise_events = *otherwise_events as usize; - - buf.push_str("if "); - print_type_into_buf(*condition, buf, &mut HashSet::new(), args, types, info, debug); - buf.push_str(" then \n"); - let events_if_true = &events[(idx + 1)..=(idx + truthy_events)]; - debug_effects(buf, events_if_true, types, info, depth + 1, debug); - if otherwise_events != 0 { - let start = idx + truthy_events + 1; - let otherwise = &events[(start)..(start + otherwise_events)]; - buf.push_str(" else \n"); - debug_effects(buf, otherwise, types, info, depth + 1, debug); - } - idx += truthy_events + otherwise_events + 1; - continue; - } - Event::CreateObject { prototype: _, referenced_in_scope_as, position: _ } => { - write!(buf, "create object as {referenced_in_scope_as:?}").unwrap(); - } - Event::Iterate { iterate_over, initial: _, kind: _ } => { - buf.push_str("iterate\n"); - let inner_events = &events[(idx + 1)..(idx + 1 + *iterate_over as usize)]; - debug_effects(buf, inner_events, types, info, depth + 1, debug); - idx += *iterate_over as usize + 1; - continue; - } - Event::FinalEvent(FinalEvent::Throw { thrown, .. }) => { - buf.push_str("throw "); - print_type_into_buf(*thrown, buf, &mut HashSet::new(), args, types, info, debug); - } - Event::FinalEvent(FinalEvent::Break { .. }) => { - buf.push_str("break"); - } - Event::FinalEvent(FinalEvent::Continue { .. }) => { - buf.push_str("continue"); - } - Event::FinalEvent(FinalEvent::Return { returned, position: _ }) => { - buf.push_str("return "); - print_type_into_buf(*returned, buf, &mut HashSet::new(), args, types, info, debug); - } - Event::ExceptionTrap { .. } => todo!(), - Event::RegisterVariable { .. } => { - buf.push_str("register variable"); - } - Event::EndOfControlFlow(_) => { - buf.push_str("end"); - } - } - buf.push('\n'); - idx += 1; - } -} diff --git a/checker/src/types/properties.rs b/checker/src/types/properties.rs deleted file mode 100644 index ad8fb85c..00000000 --- a/checker/src/types/properties.rs +++ /dev/null @@ -1,1619 +0,0 @@ -use std::borrow::Cow; - -use crate::{ - context::{ - information::InformationChain, CallCheckingBehavior, Logical, PossibleLogical, - SetPropertyError, - }, - diagnostics::TypeStringRepresentation, - events::Event, - features::{ - functions::{FunctionBehavior, ThisValue}, - objects::{Proxy, SpecialObjects}, - }, - subtyping::{State, SubTypeResult}, - types::{ - calling::{self, FunctionCallingError}, - generics::generic_type_arguments::GenericArguments, - get_constraint, get_larger_type, printing, substitute, FunctionType, GenericChain, - GenericChainLink, ObjectNature, PartiallyAppliedGenerics, PolyNature, SynthesisedArgument, - }, - Constant, Environment, LocalInformation, TypeId, -}; - -use source_map::SpanWithSource; - -use super::{calling::CalledWithNew, Constructor, Type, TypeStore}; - -#[derive(PartialEq)] -pub enum PropertyKind { - Direct, - Getter, - Setter, - /// TODO unsure - Generic, -} - -/// TODO explain usage -/// For [private properties](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_properties) -#[derive(Debug, Clone, Copy, PartialEq, Eq, binary_serialize_derive::BinarySerializable)] -pub enum Publicity { - Private, - Public, -} - -/// TODO symbol -/// Implements basic definition equality, not type equality -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum PropertyKey<'a> { - String(Cow<'a, str>), - Type(TypeId), - // SomeThingLike(TypeId), -} - -// Cannot derive BinarySerializable because lifetime -impl crate::BinarySerializable for PropertyKey<'static> { - fn serialize(self, buf: &mut Vec) { - match self { - PropertyKey::String(s) => { - buf.push(0); - crate::BinarySerializable::serialize(s.into_owned(), buf); - } - PropertyKey::Type(t) => { - buf.push(1); - crate::BinarySerializable::serialize(t, buf); - } - } - } - - fn deserialize>(iter: &mut I, source: source_map::SourceId) -> Self { - match iter.next().unwrap() { - 0 => Self::String(Cow::Owned(crate::BinarySerializable::deserialize(iter, source))), - 1 => Self::Type(crate::BinarySerializable::deserialize(iter, source)), - _ => panic!("bad code"), - } - } -} - -impl<'a> PropertyKey<'a> { - #[must_use] - pub fn into_owned(&self) -> PropertyKey<'static> { - match self { - PropertyKey::String(s) => PropertyKey::String(Cow::Owned(s.to_string())), - PropertyKey::Type(s) => PropertyKey::Type(*s), - } - } - - pub(crate) fn from_type(ty: TypeId, types: &TypeStore) -> PropertyKey<'static> { - if let Type::Constant(c) = types.get_type_by_id(ty) { - match c { - Constant::Number(n) => { - // if n.fractional ?? - PropertyKey::from_usize(n.into_inner() as usize) - } - Constant::String(s) => PropertyKey::String(Cow::Owned(s.to_owned())), - Constant::Boolean(_) => todo!(), - Constant::Symbol { key: _ } => todo!(), - Constant::Undefined => todo!(), - Constant::Null => todo!(), - Constant::NaN => todo!(), - } - } else { - PropertyKey::Type(ty) - } - } - - pub(crate) fn as_number(&self, types: &TypeStore) -> Option { - match self { - PropertyKey::String(s) => s.parse::().ok(), - PropertyKey::Type(t) => { - if let Type::Constant(Constant::Number(n)) = types.get_type_by_id(*t) { - // TODO is there a better way - #[allow(clippy::float_cmp)] - if n.trunc() == **n { - Some(**n as usize) - } else { - None - } - } else { - None - } - } - } - } - - pub(crate) fn new_empty_property_key() -> Self { - PropertyKey::String(Cow::Borrowed("")) - } - - /// For quick things - #[must_use] - pub fn is_equal_to(&self, key: &str) -> bool { - match self { - PropertyKey::String(s) => s == key, - PropertyKey::Type(_t) => false, - } - } - - /// TODO when is this used - pub fn into_type(&self, types: &mut TypeStore) -> TypeId { - match self { - PropertyKey::String(s) => { - types.new_constant_type(Constant::String(s.clone().into_owned())) - } - PropertyKey::Type(t) => *t, - } - } - - pub(crate) fn mapped_generic_id(&self, types: &TypeStore) -> Option<(TypeId, TypeId)> { - match self { - PropertyKey::String(_) => None, - PropertyKey::Type(ty) => { - let get_type_by_id = types.get_type_by_id(*ty); - if let Type::RootPolyType(super::PolyNature::MappedGeneric { - eager_fixed, .. - }) = get_type_by_id - { - Some((*ty, *eager_fixed)) - } else { - None - } - } - } - } -} - -static NUMBERS: &str = "0123456789"; - -impl<'a> PropertyKey<'a> { - /// For small array indexes - #[must_use] - pub fn from_usize(a: usize) -> Self { - if a < 10 { - Self::String(Cow::Borrowed(&NUMBERS[a..=a])) - } else { - Self::String(Cow::Owned(a.to_string())) - } - } -} - -/// TODO type predicate based -/// -/// TODO getter, setting need a closure id -#[derive(Clone, Debug, binary_serialize_derive::BinarySerializable)] -pub enum PropertyValue { - Value(TypeId), - Getter(Box), - Setter(Box), - /// TODO doesn't exist Deleted | Optional - Deleted, - ConditionallyExists { - on: TypeId, - truthy: Box, - }, -} - -impl PropertyValue { - /// TODO wip - #[must_use] - pub fn as_get_type(&self) -> TypeId { - match self { - PropertyValue::Value(value) => *value, - PropertyValue::Getter(getter) => getter.return_type, - // TODO unsure about these two - PropertyValue::Setter(_) => TypeId::UNDEFINED_TYPE, - PropertyValue::Deleted => TypeId::NEVER_TYPE, - PropertyValue::ConditionallyExists { truthy, .. } => { - // TODO temp - truthy.as_get_type() - } - } - } - - #[must_use] - pub fn as_set_type(&self) -> TypeId { - match self { - PropertyValue::Value(value) => *value, - PropertyValue::Setter(setter) => setter.return_type, - // TODO unsure about these two - PropertyValue::Getter(_) => TypeId::UNDEFINED_TYPE, - PropertyValue::Deleted => TypeId::NEVER_TYPE, - PropertyValue::ConditionallyExists { truthy, .. } => { - // TODO temp - truthy.as_get_type() - } - } - } -} - -/// Also evaluates getter and binds `this` -/// -/// *be aware this creates a new type every time, bc of this binding. could cache this bound -/// types at some point* -/// TODO: `optional_chain` -#[allow(clippy::too_many_arguments)] -pub(crate) fn get_property( - on: TypeId, - publicity: Publicity, - under: &PropertyKey, - with: Option, - top_environment: &mut Environment, - behavior: &mut E, - types: &mut TypeStore, - position: SpanWithSource, - bind_this: bool, -) -> Option<(PropertyKind, TypeId)> { - if on == TypeId::ERROR_TYPE - || matches!(under, PropertyKey::Type(under) if *under == TypeId::ERROR_TYPE) - { - return Some((PropertyKind::Direct, TypeId::ERROR_TYPE)); - } - - if get_constraint(on, types).is_some() { - // // TODO temp fix for assigning to a poly type. What about unions etc - // if let Some(value) = top_environment.info_chain().find_map(|f| { - // f.current_properties.get(&on).and_then(|props| { - // props.iter().find_map(|(_publicity, key, value)| (key == &under).then_some(value)) - // }) - // }) { - // return Some((PropertyKind::Direct, value.as_get_type())); - // } - - evaluate_get_on_poly( - // constraint, - on, - publicity, - under.clone(), - with, - top_environment, - behavior, - types, - position, - bind_this, - ) - } else if top_environment.possibly_mutated_objects.contains(&on) { - let Some(constraint) = top_environment.get_object_constraint(on) else { - todo!("mutated property inference") - }; - - // TODO ... - evaluate_get_on_poly( - constraint, - publicity, - under.clone(), - with, - top_environment, - behavior, - types, - position, - bind_this, - ) - } else { - // if environment.get_poly_base(under, types).is_some() { - // todo!() - // } - // TODO - get_from_an_object(on, publicity, under, top_environment, behavior, types, bind_this) - } -} - -fn get_from_an_object( - on: TypeId, - publicity: Publicity, - under: &PropertyKey, - environment: &mut Environment, - behavior: &mut E, - types: &mut TypeStore, - bind_this: bool, -) -> Option<(PropertyKind, TypeId)> { - /// Generates closure arguments, values of this and more. Runs getters - fn resolve_property_on_logical( - logical: Logical, - on: TypeId, - generics: GenericChain, - environment: &mut Environment, - types: &mut TypeStore, - behavior: &mut E, - bind_this: bool, - ) -> Option<(PropertyKind, TypeId)> { - match logical { - Logical::Pure(property) => { - match property { - PropertyValue::Value(value) => { - let ty = types.get_type_by_id(value); - match ty { - Type::SpecialObject(SpecialObjects::Function(func, _state)) => { - let this_value = if bind_this { - ThisValue::Passed(on) - } else { - ThisValue::UseParent - }; - let func = types.register_type(Type::SpecialObject( - SpecialObjects::Function(*func, this_value), - )); - - Some((PropertyKind::Direct, func)) - } - Type::FunctionReference(_) => { - let ty = if let Some(arguments) = - generics.and_then(super::GenericChainLink::get_value).cloned() - { - // assert!(chain.parent.is_none()); - types.register_type(Type::PartiallyAppliedGenerics( - PartiallyAppliedGenerics { on: value, arguments }, - )) - } else { - value - }; - - Some((PropertyKind::Direct, ty)) - } - // TODO if uses generics - Type::SpecialObject(..) - | Type::Object(..) - | Type::RootPolyType { .. } - | Type::Constant(..) => Some((PropertyKind::Direct, value)), - Type::Class { .. } - | Type::Interface { .. } - | Type::And(_, _) - | Type::Or(_, _) => { - crate::utilities::notify!( - "property was {:?} {:?}, which should be NOT be able to be returned from a function", - property, ty - ); - - let value = types.register_type(Type::RootPolyType( - crate::types::PolyNature::Open(value), - )); - Some((PropertyKind::Direct, value)) - } - // For closures - Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: sg_on, - arguments, - }) => { - // TODO not great... need less overhead - if let Type::SpecialObject(SpecialObjects::Function(f, _p)) = - types.get_type_by_id(*sg_on) - { - let arguments = arguments.clone(); - let f = types.register_type(Type::SpecialObject( - SpecialObjects::Function(*f, ThisValue::Passed(on)), - )); - let ty = types.register_type(Type::PartiallyAppliedGenerics( - PartiallyAppliedGenerics { on: f, arguments }, - )); - Some((PropertyKind::Direct, ty)) - } else { - Some((PropertyKind::Direct, value)) - } - } - Type::Constructor(constructor) => { - unreachable!("Interesting property on {:?}", constructor); - } - Type::AliasTo { to: _, name: _, parameters: _ } => { - todo!() - } - } - } - PropertyValue::Getter(getter) => { - let call = getter.call( - CalledWithNew::None, - ThisValue::Passed(on), - source_map::Nullable::NULL, - &[], - None, - // TODO structure generics - None, - environment, - behavior, - types, - true, - ); - match call { - Ok(res) => { - let application_result = calling::application_result_to_return_type( - res.result, - environment, - types, - ); - Some((PropertyKind::Getter, application_result)) - } - Err(_) => { - todo!() - } - } - } - PropertyValue::Setter(_) => todo!(), - PropertyValue::Deleted => None, - PropertyValue::ConditionallyExists { on, truthy } => { - if let PropertyValue::Value(value) = *truthy { - let value = - types.new_conditional_type(on, value, TypeId::UNDEFINED_TYPE); - Some((PropertyKind::Direct, value)) - } else { - todo!() - } - } - } - } - Logical::Or { left, right, condition: based_on } => left - .map(|l| { - resolve_property_on_logical( - l, - based_on, - None, - environment, - types, - behavior, - bind_this, - ) - }) - .or_else(|_| { - right.map(|r| { - resolve_property_on_logical( - r, - based_on, - None, - environment, - types, - behavior, - bind_this, - ) - }) - }) - .ok() - .flatten(), - Logical::Implies { on: log_on, antecedent } => { - let generics = GenericChainLink::append( - TypeId::UNIMPLEMENTED_ERROR_TYPE, - generics.as_ref(), - &antecedent, - ); - resolve_property_on_logical( - *log_on, - on, - generics, - environment, - types, - behavior, - bind_this, - ) - } - } - } - - // TODO explain what happens around non constant strings - // TODO this breaks for conditionals maybe #120 fixes it? - if let Type::Constant(Constant::String(s)) = types.get_type_by_id(on) { - if let Some(n) = under.as_number(types) { - return s.chars().nth(n).map(|s| { - (PropertyKind::Direct, types.new_constant_type(Constant::String(s.to_string()))) - }); - } - } - - let result = get_property_unbound((on, None), (publicity, under, None), environment, types); - - match result { - Ok(logical) => { - resolve_property_on_logical(logical, on, None, environment, types, behavior, bind_this) - } - Err(err) => match err { - crate::context::MissingOrToCalculate::Missing => None, - crate::context::MissingOrToCalculate::Error => { - // Don't return none because that will raise error! - Some((PropertyKind::Direct, TypeId::ERROR_TYPE)) - } - // Can get through set prototype..? - crate::context::MissingOrToCalculate::Infer { .. } => { - crate::utilities::notify!("TODO set infer"); - Some((PropertyKind::Direct, TypeId::ERROR_TYPE)) - } - crate::context::MissingOrToCalculate::Proxy(Proxy { .. }) => { - todo!(); // #33 - // TODO pass down - } - }, - } -} - -#[allow(clippy::too_many_arguments)] -#[allow(clippy::needless_pass_by_value)] -fn evaluate_get_on_poly( - on: TypeId, - publicity: Publicity, - under: PropertyKey, - _with: Option, - top_environment: &mut Environment, - behavior: &mut E, - types: &mut TypeStore, - position: SpanWithSource, - bind_this: bool, -) -> Option<(PropertyKind, TypeId)> { - fn resolve_logical_with_poly( - fact: Logical, - on: TypeId, - under: PropertyKey, - // TODO generic chain - arguments: Option<&GenericArguments>, - environment: &mut Environment, - types: &mut TypeStore, - bind_this: bool, - ) -> Option { - match fact { - Logical::Pure(og) => { - Some(match og { - PropertyValue::Value(value) => match types.get_type_by_id(value) { - t @ (Type::And(_, _) - | Type::Or(_, _) - | Type::RootPolyType(_) - | Type::Constructor(_)) => { - let result = if let Some(arguments) = arguments { - let arguments = arguments.into_substitutable(); - substitute(value, &arguments, environment, types) - } else { - crate::utilities::notify!("Here, getting property on {:?}", t); - value - }; - - types.register_type(Type::Constructor(Constructor::Property { - on, - under: under.into_owned(), - result, - bind_this, - })) - } - // Don't need to set this here. It is picked up from `on` during lookup - Type::SpecialObject(SpecialObjects::Function(..)) - | Type::FunctionReference(..) - | Type::AliasTo { .. } - | Type::Object(ObjectNature::AnonymousTypeAnnotation) - | Type::Interface { .. } - | Type::Class { .. } => types.register_type(Type::Constructor(Constructor::Property { - on, - under: under.into_owned(), - result: value, - bind_this, - })), - Type::Constant(_) - | Type::PartiallyAppliedGenerics(_) - | Type::Object(ObjectNature::RealDeal) - | Type::SpecialObject(..) => value, - }, - PropertyValue::Getter(getter) => { - // if is_open_poly { - // crate::utilities::notify!("TODO evaluate getter..."); - // } else { - // crate::utilities::notify!("TODO don't evaluate getter"); - // } - // TODO : getter.return_type - types.register_type(Type::Constructor(Constructor::Property { - on, - under: under.into_owned(), - result: getter.return_type, - bind_this: false, - })) - } - PropertyValue::Setter(_) => todo!(), - // Very important - PropertyValue::Deleted => return None, - PropertyValue::ConditionallyExists { on, truthy } => { - // TODO can only get if optional ? - if let PropertyValue::Value(value) = *truthy { - types.new_conditional_type(on, value, TypeId::UNDEFINED_TYPE) - } else { - todo!() - } - } - }) - } - Logical::Or { condition: based_on, left, right } => { - // let left = resolve_logical_with_poly( - // *left, - // on, - // under.clone(), - // arguments, - // environment, - // types, - // ); - // let right = - // resolve_logical_with_poly(*right, on, under, arguments, environment, types); - - // crate::utilities::notify!("lr = {:?}", (left, right)); - - // TODO lots of information (and inference) lost here - if let (Ok(lhs), Ok(rhs)) = (*left, *right) { - let lhs = resolve_logical_with_poly( - lhs, - on, - under.clone(), - arguments, - environment, - types, - bind_this, - )?; - let rhs = resolve_logical_with_poly( - rhs, - on, - under, - arguments, - environment, - types, - bind_this, - )?; - Some(types.new_conditional_type(based_on, lhs, rhs)) - } else { - crate::utilities::notify!("TODO emit some diagnostic about missing"); - None - } - } - Logical::Implies { on: implies_on, antecedent } => { - if arguments.is_some() { - todo!("generics chain") - } - - resolve_logical_with_poly( - *implies_on, - on, - under.clone(), - Some(&antecedent), - environment, - types, - bind_this, - ) - } - } - } - - let fact = - get_property_unbound((on, None), (publicity, &under, None), top_environment, types).ok()?; - - crate::utilities::notify!("unbound is is {:?}", fact); - - let value = resolve_logical_with_poly( - fact, - on, - under.clone(), - None, - top_environment, - types, - bind_this, - )?; - - behavior.get_latest_info(top_environment).events.push(Event::Getter { - on, - under: under.into_owned(), - reflects_dependency: Some(value), - publicity, - position, - bind_this, - }); - - Some((PropertyKind::Direct, value)) -} - -/// Aka a assignment to a property, **INCLUDING initialization of a new one** -/// -/// Evaluates setters -#[allow(clippy::too_many_arguments)] // https://github.com/kaleidawave/ezno/pull/88 -pub(crate) fn set_property( - on: TypeId, - publicity: Publicity, - under: &PropertyKey, - new: PropertyValue, - environment: &mut Environment, - behavior: &mut E, - types: &mut TypeStore, - setter_position: SpanWithSource, -) -> Result, SetPropertyError> { - // TODO - // if environment.is_not_writeable(on, under) { - // return Err(SetPropertyError::NotWriteable); - // } - - // if E::CHECK_PARAMETERS { - if let Some(constraint) = environment.get_object_constraint(on) { - let property_constraint = - get_property_unbound((constraint, None), (publicity, under, None), environment, types); - - // crate::utilities::notify!("Property constraint .is_some() {:?}", property_constraint.is_some()); - - // crate::utilities::notify!( - // "Re-assignment constraint {}, prop={} {:?}", - // print_type(constraint, types, environment, true), - // print_type(under, types, environment, true), - // property_constraint - // ); - - if let Ok(property_constraint) = property_constraint { - // TODO ...? - let mut state = State { - already_checked: Default::default(), - mode: Default::default(), - contributions: Default::default(), - others: Default::default(), - object_constraints: Default::default(), - }; - - // TODO property value is readonly - // TODO state is writeable etc? - // TODO document difference with context.writeable - match &property_constraint { - Logical::Pure(PropertyValue::Value(p)) => { - if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: TypeId::READONLY_RESTRICTION, - .. - }) = types.get_type_by_id(*p) - { - return Err(SetPropertyError::NotWriteable); - } - } - Logical::Pure(_) => {} - Logical::Or { .. } => todo!(), - Logical::Implies { .. } => todo!(), - } - - match new { - PropertyValue::Value(value) => { - let result = crate::subtyping::type_is_subtype_of_property( - (&property_constraint, None), - value, - &mut state, - environment, - types, - ); - if let SubTypeResult::IsNotSubType(reason) = result { - return Err(SetPropertyError::DoesNotMeetConstraint { - property_constraint: TypeStringRepresentation::from_property_constraint( - property_constraint, - None, - environment, - types, - false, - ), - reason, - }); - } - } - PropertyValue::Getter(_) => todo!(), - PropertyValue::Setter(_) => todo!(), - PropertyValue::Deleted => todo!(), - PropertyValue::ConditionallyExists { .. } => todo!(), - } - - // environment - // .add_object_constraints(basic_subtyping.object_constraints.into_iter(), types); - - // environment.context_type.requests.append(todo!().parameter_constraint_request); - } else { - // TODO does not exist warning - // return Err(SetPropertyError::DoesNotMeetConstraint( - // new.as_get_type(), - // todo!("no property"), - // )); - } - } - - // crate::utilities::notify!( - // "setting {:?} {:?} {:?}", - // crate::types::printing::print_type(types, on, environment, true), - // crate::types::printing::print_type(types, under, environment, true), - // crate::types::printing::print_type(types, new.as_get_type(), environment, true), - // ); - - let current_property = - get_property_unbound((on, None), (publicity, under, None), environment, types); - - // crate::utilities::notify!("(2) Made it here assigning to {:?}", types.get_type_by_id(on)); - - // Cascade if it is a union (unsure tho) - if let Type::Constructor(Constructor::ConditionalResult { - truthy_result, - otherwise_result, - condition: _, - result_union: _, - }) = types.get_type_by_id(on) - { - let truthy = *truthy_result; - let otherwise_result = *otherwise_result; - set_property( - truthy, - publicity, - under, - new.clone(), - environment, - behavior, - types, - setter_position, - )?; - return set_property( - otherwise_result, - publicity, - under, - new, - environment, - behavior, - types, - setter_position, - ); - } - - if let Ok(fact) = current_property { - match fact { - Logical::Pure(og) => { - let result = run_setter_on_object( - og, - behavior, - environment, - on, - publicity, - under, - new, - types, - setter_position, - ); - if let Err(result) = result { - // TODO temp - for error in result { - match error { - FunctionCallingError::InvalidArgumentType { - parameter_type, - argument_type: _, - argument_position: _, - parameter_position: _, - restriction: _, - } => { - return Err(SetPropertyError::DoesNotMeetConstraint { - property_constraint: parameter_type, - reason: crate::subtyping::NonEqualityReason::Mismatch, - }) - } - FunctionCallingError::NeedsToBeCalledWithNewKeyword(_) - | FunctionCallingError::NoLogicForIdentifier(..) - | FunctionCallingError::NotCallable { .. } - | FunctionCallingError::ExcessArguments { .. } - | FunctionCallingError::ExcessTypeArguments { .. } - | FunctionCallingError::MissingArgument { .. } => unreachable!(), - FunctionCallingError::ReferenceRestrictionDoesNotMatch { .. } => { - todo!() - } - FunctionCallingError::CyclicRecursion(_, _) => todo!(), - FunctionCallingError::TDZ { .. } => todo!(), - FunctionCallingError::SetPropertyConstraint { .. } => todo!(), - FunctionCallingError::UnconditionalThrow { .. } => { - todo!() - } - FunctionCallingError::MismatchedThis { .. } => { - todo!() - } - FunctionCallingError::CannotCatch { .. } => todo!(), - } - } - } - } - Logical::Or { .. } => todo!(), - Logical::Implies { on: _implies_on, antecedent: _ } => { - crate::utilities::notify!("Check that `implies_on` could be a setter here"); - let info = behavior.get_latest_info(environment); - info.current_properties.entry(on).or_default().push(( - publicity, - under.into_owned(), - new.clone(), - )); - info.events.push(Event::Setter { - on, - new, - under: under.into_owned(), - publicity, - initialization: false, - position: setter_position, - }); - } - } - } else { - // TODO abstract - // TODO only if dependent? - let register_setter_event = true; - behavior.get_latest_info(environment).register_property( - on, - publicity, - under.into_owned(), - new, - register_setter_event, - setter_position, - ); - } - Ok(None) -} - -/// `Vec` from calling setter -#[allow(clippy::too_many_arguments)] -fn run_setter_on_object( - og: PropertyValue, - behavior: &mut E, - environment: &mut Environment, - on: TypeId, - publicity: Publicity, - under: &PropertyKey<'_>, - new: PropertyValue, - types: &mut TypeStore, - setter_position: SpanWithSource, -) -> Result<(), Vec> { - match og { - PropertyValue::Deleted | PropertyValue::Value(..) => { - let info = behavior.get_latest_info(environment); - info.current_properties.entry(on).or_default().push(( - publicity, - under.into_owned(), - new.clone(), - )); - info.events.push(Event::Setter { - on, - new, - under: under.into_owned(), - publicity, - initialization: false, - position: setter_position, - }); - - Ok(()) - } - PropertyValue::Getter(_) => todo!(), - PropertyValue::Setter(setter) => { - let arg = SynthesisedArgument { - position: setter_position, - spread: false, - value: match new { - PropertyValue::Value(type_id) => type_id, - _ => todo!(), - }, - }; - let result = setter.call( - CalledWithNew::None, - ThisValue::Passed(on), - setter_position, - &[arg], - None, - // TODO structure generics - None, - environment, - behavior, - types, - false, - ); - - match result { - // Ignore the result - Ok(_ok) => Ok(()), - Err(res) => Err(res.errors), - } - } - PropertyValue::ConditionallyExists { .. } => todo!(), - } -} - -pub(crate) fn get_property_unbound( - (on, on_type_arguments): (TypeId, GenericChain), - (publicity, under, under_type_arguments): (Publicity, &PropertyKey, GenericChain), - info_chain: &impl InformationChain, - types: &TypeStore, -) -> PossibleLogical { - /// Has to return `Logical` for mapped types - fn resolver( - (on, on_type_arguments): (TypeId, GenericChain), - (publicity, under, under_type_arguments): (Publicity, &PropertyKey, GenericChain), - info: &LocalInformation, - types: &TypeStore, - ) -> Option> { - // TODO if on == constant string and property == length. Need to be able to create types here - - info.current_properties.get(&on).and_then(|properties_on_on| { - { - let (on_properties, on_type_arguments) = (properties_on_on, on_type_arguments); - let (required_publicity, want_key, want_type_arguments) = - (publicity, under, under_type_arguments); - - // TODO conditional for conditional? - // let _acc = (); - - // 'rev' is important - for (publicity, key, value) in on_properties.iter().rev() { - if *publicity != required_publicity { - continue; - } - - let key_matches = key_matches( - (key, on_type_arguments), - (want_key, want_type_arguments), - types, - ); - if key_matches { - // TODO if conditional then continue to find then logical or - let pure = Logical::Pure(value.clone()); - return Some(if let Some((_parameter, _)) = key.mapped_generic_id(types) { - // Logical::Implies { - // on: Box::new(pure), - // antecedent: GenericArguments::ExplicitRestrictions(todo!( - // "want TypeArgument::PropertyKey 😀" - // )), - // } - crate::utilities::notify!( - "TODO to set property key as a type argument?" - ); - pure - } else { - pure - }); - } - } - - None - } - }) - } - - if on == TypeId::ERROR_TYPE { - return Err(crate::context::MissingOrToCalculate::Error); - } - if on == TypeId::ANY_TYPE { - // TODO any - return Err(crate::context::MissingOrToCalculate::Infer { on }); - } - - match types.get_type_by_id(on) { - Type::SpecialObject(SpecialObjects::Function(function_id, _)) => info_chain - .get_chain_of_info() - .find_map(|info| { - resolver( - (on, on_type_arguments), - (publicity, under, under_type_arguments), - info, - types, - ) - .or_else(|| { - if let (true, FunctionBehavior::Function { prototype, .. }) = ( - under.is_equal_to("prototype"), - &types.get_function_from_id(*function_id).behavior, - ) { - Some(Logical::Pure(PropertyValue::Value(*prototype))) - } else { - None - } - }) - .or_else(|| { - resolver( - (TypeId::FUNCTION_TYPE, on_type_arguments), - (publicity, under, under_type_arguments), - info, - types, - ) - }) - }) - .ok_or(crate::context::MissingOrToCalculate::Missing), - Type::FunctionReference(_) => info_chain - .get_chain_of_info() - .find_map(|info| { - resolver( - (TypeId::FUNCTION_TYPE, on_type_arguments), - (publicity, under, under_type_arguments), - info, - types, - ) - }) - .ok_or(crate::context::MissingOrToCalculate::Missing), - Type::AliasTo { to, .. } => { - get_property_unbound( - (*to, on_type_arguments), - (publicity, under, under_type_arguments), - info_chain, - types, - ) - // TODO why would an alias have a property - // let property_on_types = info_chain - // .get_chain_of_info() - // .find_map(|info| resolver(info, types, on, on_type_arguments,)) - } - Type::And(left, right) => get_property_unbound( - (*left, on_type_arguments), - (publicity, under, under_type_arguments), - info_chain, - types, - ) - .or_else(|_| { - get_property_unbound( - (*right, on_type_arguments), - (publicity, under, under_type_arguments), - info_chain, - types, - ) - }), - Type::Or(left, right) => { - let left = get_property_unbound( - (*left, on_type_arguments), - (publicity, under, under_type_arguments), - info_chain, - types, - ); - let right = get_property_unbound( - (*right, on_type_arguments), - (publicity, under, under_type_arguments), - info_chain, - types, - ); - - // TODO throwaway if both Missing::None - - Ok(Logical::Or { - condition: TypeId::BOOLEAN_TYPE, - left: Box::new(left), - right: Box::new(right), - }) - } - Type::RootPolyType(_nature) => { - // Can assign to properties on parameters etc - let aliases = get_constraint(on, types).expect("poly type with no constraint"); - - info_chain - .get_chain_of_info() - .find_map(|info| { - resolver( - (on, on_type_arguments), - (publicity, under, under_type_arguments), - info, - types, - ) - }) - .ok_or(crate::context::MissingOrToCalculate::Missing) - .or_else(|_| { - get_property_unbound( - (aliases, on_type_arguments), - (publicity, under, under_type_arguments), - info_chain, - types, - ) - }) - } - Type::PartiallyAppliedGenerics(crate::types::PartiallyAppliedGenerics { - on: base, - arguments, - }) => { - let on_sg_type = if let GenericArguments::Closure(_) = arguments { - info_chain - .get_chain_of_info() - .find_map(|info| { - resolver( - (on, on_type_arguments), - (publicity, under, under_type_arguments), - info, - types, - ) - }) - .ok_or(crate::context::MissingOrToCalculate::Missing) - } else { - Err(crate::context::MissingOrToCalculate::Missing) - }; - - on_sg_type.or_else(|_| { - let on_type_arguments = crate::types::GenericChainLink::append( - on, - on_type_arguments.as_ref(), - arguments, - ); - - crate::utilities::notify!("{:?}", on_type_arguments); - - get_property_unbound( - (*base, on_type_arguments), - (publicity, under, under_type_arguments), - info_chain, - types, - ) - .map(|fact| Logical::Implies { on: Box::new(fact), antecedent: arguments.clone() }) - }) - } - Type::Constructor(crate::types::Constructor::ConditionalResult { - condition, - truthy_result, - otherwise_result, - result_union: _, - }) => { - let left = get_property_unbound( - (*truthy_result, on_type_arguments), - (publicity, under, under_type_arguments), - info_chain, - types, - ); - let right = get_property_unbound( - (*otherwise_result, on_type_arguments), - (publicity, under, under_type_arguments), - info_chain, - types, - ); - - // TODO throwaway if both Missing::None - - Ok(Logical::Or { condition: *condition, left: Box::new(left), right: Box::new(right) }) - } - Type::Constructor(_constructor) => { - let on_constructor_type = info_chain - .get_chain_of_info() - .find_map(|info| { - resolver( - (on, on_type_arguments), - (publicity, under, under_type_arguments), - info, - types, - ) - }) - .ok_or(crate::context::MissingOrToCalculate::Missing); - - let aliases = get_constraint(on, types).expect("no constraint for constructor"); - - on_constructor_type.or_else(|_| { - get_property_unbound( - (aliases, on_type_arguments), - (publicity, under, under_type_arguments), - info_chain, - types, - ) - }) - } - Type::Object(..) => { - let object_constraint_structure_generics = - crate::types::get_structure_arguments_based_on_object_constraint( - on, info_chain, types, - ); - - let prototype = - info_chain.get_chain_of_info().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 - }; - - info_chain - .get_chain_of_info() - .find_map(|info| { - let on_self = resolver( - (on, on_type_arguments), - (publicity, under, under_type_arguments), - info, - types, - ); - - let result = if let (Some(prototype), None) = (prototype, &on_self) { - resolver( - (prototype, on_type_arguments), - (publicity, under, under_type_arguments), - info, - types, - ) - } else { - on_self - }; - - result.map(|result| { - if let Some(ref generics) = generics { - // TODO clone - Logical::Implies { on: Box::new(result), antecedent: generics.clone() } - } else { - result - } - }) - }) - .ok_or(crate::context::MissingOrToCalculate::Missing) - } - Type::Interface { .. } => info_chain - .get_chain_of_info() - .find_map(|env| { - resolver( - (on, on_type_arguments), - (publicity, under, under_type_arguments), - env, - types, - ) - }) - .ok_or(crate::context::MissingOrToCalculate::Missing) - .or_else(|_| { - // TODO class and class constructor extends etc - if let Some(extends) = types.interface_extends.get(&on) { - get_property_unbound( - (*extends, on_type_arguments), - (publicity, under, under_type_arguments), - info_chain, - types, - ) - } else { - Err(crate::context::MissingOrToCalculate::Missing) - } - }), - Type::SpecialObject(SpecialObjects::ClassConstructor { .. }) | Type::Class { .. } => { - info_chain - .get_chain_of_info() - .find_map(|env| { - resolver( - (on, on_type_arguments), - (publicity, under, under_type_arguments), - env, - types, - ) - }) - .ok_or(crate::context::MissingOrToCalculate::Missing) - .or_else(|_| { - if let Some(prototype) = - info_chain.get_chain_of_info().find_map(|info| info.prototypes.get(&on)) - { - get_property_unbound( - (*prototype, on_type_arguments), - (publicity, under, under_type_arguments), - info_chain, - types, - ) - } else { - Err(crate::context::MissingOrToCalculate::Missing) - } - }) - } - Type::Constant(cst) => info_chain - .get_chain_of_info() - .find_map(|env| { - resolver( - (on, on_type_arguments), - (publicity, under, under_type_arguments), - env, - types, - ) - }) - .ok_or(crate::context::MissingOrToCalculate::Missing) - .or_else(|_| { - let backing_type = cst.get_backing_type_id(); - get_property_unbound( - (backing_type, on_type_arguments), - (publicity, under, under_type_arguments), - info_chain, - types, - ) - }), - Type::SpecialObject(SpecialObjects::Promise { .. }) => { - todo!() - } - Type::SpecialObject(SpecialObjects::Import(..)) => { - todo!() - } - Type::SpecialObject(SpecialObjects::Proxy(proxy)) => { - Err(crate::context::MissingOrToCalculate::Proxy(*proxy)) - } - Type::SpecialObject(SpecialObjects::Generator { .. }) => { - todo!() - } - Type::SpecialObject(SpecialObjects::RegularExpression(..)) => { - todo!() - } - } -} - -/// Does lhs equal want -/// Aka is `want_key in { [lhs_key]: ... }` -pub(crate) fn key_matches( - (key, key_type_arguments): (&PropertyKey<'_>, GenericChain), - (want, want_type_arguments): (&PropertyKey<'_>, GenericChain), - types: &TypeStore, -) -> bool { - // crate::utilities::notify!( - // "Key equality: have {:?} want {:?}", - // (key, key_type_arguments), - // (want, want_type_arguments) - // ); - - match (key, want) { - (PropertyKey::String(left), PropertyKey::String(right)) => left == right, - (PropertyKey::String(s), PropertyKey::Type(want)) => { - if let Some(substituted_key) = - want_type_arguments.and_then(|args| args.get_single_argument(*want)) - { - crate::utilities::notify!("Here"); - return key_matches( - (key, key_type_arguments), - (&PropertyKey::Type(substituted_key), want_type_arguments), - types, - ); - } - let want_ty = types.get_type_by_id(*want); - crate::utilities::notify!("{:?} key_ty={:?}", s, want_ty); - if let Type::Or(lhs, rhs) = want_ty { - key_matches( - (key, key_type_arguments), - (&PropertyKey::Type(*lhs), key_type_arguments), - types, - ) || key_matches( - (key, key_type_arguments), - (&PropertyKey::Type(*rhs), key_type_arguments), - types, - ) - } else if let Type::RootPolyType(PolyNature::MappedGeneric { - eager_fixed: to, .. - }) - | Type::AliasTo { to, .. } = want_ty - { - key_matches( - (key, key_type_arguments), - (&PropertyKey::Type(*to), want_type_arguments), - types, - ) - } else if let Type::Constant(c) = want_ty { - crate::utilities::notify!("{:?}", c); - // TODO - *s == c.as_js_string() - } else { - false - } - } - (PropertyKey::Type(key), PropertyKey::String(s)) => { - if let Some(substituted_key) = - key_type_arguments.and_then(|args| args.get_single_argument(*key)) - { - return key_matches( - (&PropertyKey::Type(substituted_key), key_type_arguments), - (want, want_type_arguments), - types, - ); - } - - let key = *key; - let key_type = types.get_type_by_id(key); - - if let Type::RootPolyType(PolyNature::MappedGeneric { eager_fixed: to, .. }) = key_type - { - crate::utilities::notify!("Special behavior?"); - return key_matches( - (&PropertyKey::Type(*to), key_type_arguments), - (want, want_type_arguments), - types, - ); - } - - if let Type::AliasTo { to, .. } = key_type { - return key_matches( - (&PropertyKey::Type(*to), key_type_arguments), - (want, want_type_arguments), - types, - ); - } - - if let Type::Or(l, r) = key_type { - return key_matches( - (&PropertyKey::Type(*l), key_type_arguments), - (want, want_type_arguments), - types, - ) || key_matches( - (&PropertyKey::Type(*r), key_type_arguments), - (want, want_type_arguments), - types, - ); - } - - // TODO WIP - if key == TypeId::ANY_TYPE { - true - } else if let Type::Constant(Constant::String(ks)) = key_type { - ks == s - } else if key == TypeId::BOOLEAN_TYPE { - s == "true" || s == "false" - } else if key == TypeId::NUMBER_TYPE { - s.parse::().is_ok() - } else if key == TypeId::STRING_TYPE { - s.parse::().is_err() - } else { - false - } - } - (PropertyKey::Type(left), PropertyKey::Type(right)) => { - crate::utilities::notify!( - "{:?} {:?}", - types.get_type_by_id(*left), - types.get_type_by_id(*right) - ); - // TODO subtyping - *left == get_larger_type(*right, types) - } - } -} - -pub type Properties = Vec<(Publicity, PropertyKey<'static>, PropertyValue)>; - -/// Get properties on a type (for printing and other non-one property uses) -/// -/// - TODO prototypes -/// - TODO could this be an iterator -/// - TODO return whether it is fixed -/// - TODO doesn't evaluate properties -pub fn get_properties_on_single_type( - base: TypeId, - types: &TypeStore, - info: &impl InformationChain, -) -> Properties { - match types.get_type_by_id(base) { - Type::Interface { .. } | Type::Class { .. } | Type::Object(_) => { - let reversed_flattened_properties = info - .get_chain_of_info() - .filter_map(|info| info.current_properties.get(&base).map(|v| v.iter().rev())) - .flatten(); - - let mut deleted_or_existing_properties = - std::collections::HashSet::::new(); - - // This retains ordering here - - let mut properties = Vec::new(); - for (publicity, key, prop) in reversed_flattened_properties { - if let PropertyValue::Deleted = prop { - // TODO doesn't cover constants :( - deleted_or_existing_properties.insert(key.clone()); - } else if deleted_or_existing_properties.insert(key.clone()) { - properties.push((*publicity, key.to_owned(), prop.clone())); - } - } - - properties.reverse(); - properties - } - t @ (Type::SpecialObject(_) - | Type::Constructor(_) - | Type::RootPolyType(_) - | Type::Or(..) - | Type::PartiallyAppliedGenerics(_) - | Type::Constant(_) - | Type::AliasTo { .. } - | Type::FunctionReference(_) - | Type::And(_, _)) => panic!("Cannot get all properties on {t:?}"), - } -} - -pub fn get_property_as_string( - property: &PropertyKey, - types: &mut TypeStore, - environment: &mut Environment, -) -> String { - match property { - PropertyKey::String(s) => s.to_string(), - PropertyKey::Type(t) => printing::print_type(*t, types, environment, false), - } -} - -pub fn special_type(base: TypeId, types: &mut TypeStore) -> bool { - matches!( - types.get_type_by_id(base), - Type::SpecialObject(_) - | Type::Constructor(_) - | Type::RootPolyType(_) - | Type::Or(..) - | Type::PartiallyAppliedGenerics(_) - | Type::Constant(_) - | Type::AliasTo { .. } - | Type::FunctionReference(_) - | Type::And(_, _) - ) -} - -pub fn get_property_key_names_on_a_single_type( - base: TypeId, - types: &mut TypeStore, - environment: &mut Environment, -) -> Vec { - if special_type(base, types) { - return vec![]; - } - - get_properties_on_single_type(base, types, environment) - .into_iter() - .map(|property| get_property_as_string(&property.1, types, environment)) - .collect() -} diff --git a/checker/src/types/properties/access.rs b/checker/src/types/properties/access.rs new file mode 100644 index 00000000..0effa81a --- /dev/null +++ b/checker/src/types/properties/access.rs @@ -0,0 +1,1126 @@ +use source_map::{Nullable, SpanWithSource}; + +use crate::{ + context::{CallCheckingBehavior, Environment, InformationChain, LocalInformation}, + events::Event, + features::objects::Proxy, + types::{ + calling::{Callable, CallingDiagnostics, ThisValue}, + generics::{ + contributions::CovariantContribution, generic_type_arguments::GenericArguments, + }, + get_conditional, get_constraint, is_inferrable_type, is_pseudo_continous, + logical::{ + BasedOnKey, Invalid, Logical, LogicalOrValid, NeedsCalculation, PossibleLogical, + PropertyOn, + }, + Constructor, GenericChain, GenericChainLink, PartiallyAppliedGenerics, SliceArguments, + SpecialObject, TypeStore, + }, + Constant, Type, TypeId, +}; + +use super::{PropertyKey, PropertyKind, PropertyValue, Publicity}; + +/// Has to return `Logical` for mapped types +/// +/// Resolver runs information lookup on **ONE** `TypeId` +#[allow(clippy::similar_names)] +pub(crate) fn resolver( + (on, on_type_arguments): (TypeId, GenericChain), + (publicity, under, under_type_arguments): (Publicity, &PropertyKey, GenericChain), + info_chain: &impl InformationChain, + types: &TypeStore, +) -> Option<(PropertyValue, Option, bool)> { + // TODO if on == constant string and property == length. Need to be able to create types here + info_chain.get_chain_of_info().find_map(|info: &LocalInformation| { + info.current_properties.get(&on).and_then(|on_properties| { + { + let (required_publicity, want_key, want_type_arguments) = + (publicity, under, under_type_arguments); + + // crate::utilities::notify!("{:?} {:?}", on, on_properties); + + // TODO trailing for conditional? + + // This is fine on the same type! + let mut trailing_getter = None::; + let mut trailing_setter = None::; + + // 'rev' is important + for (publicity, key, value) in on_properties.iter().rev() { + if *publicity != required_publicity { + continue; + } + + if let PropertyValue::ConditionallyExists { .. } = value { + crate::utilities::notify!("TODO trailing"); + // continue; + } + + let (matched, key_arguments) = super::key_matches( + (key, on_type_arguments), + (want_key, want_type_arguments), + info_chain, + types, + ); + + if matched { + // crate::utilities::notify!("{:?} {:?}", (key, want_key), value); + // TODO if conditional then continue to find then logical or + + // TODO should come from key_matches + let is_key_continous = matches!(key, PropertyKey::Type(ty) if is_pseudo_continous((*ty, on_type_arguments), types)); + + match value.inner_simple() { + PropertyValue::Getter(getter) => { + if let Some(setter) = trailing_setter { + return Some((PropertyValue::GetterAndSetter { getter: *getter, setter }, key_arguments.into_some(), is_key_continous)); + } + trailing_getter = Some(*getter); + } + PropertyValue::Setter(setter) => { + if let Some(getter) = trailing_getter { + return Some((PropertyValue::GetterAndSetter { getter, setter: *setter }, key_arguments.into_some(), is_key_continous)); + } + trailing_setter = Some(*setter); + } + _ => { + return Some((value.clone(), key_arguments.into_some(), is_key_continous)); + } + } + } + } + + #[allow(clippy::manual_map)] + if let Some(setter) = trailing_setter { + Some((PropertyValue::Setter(setter), None, false)) + } else if let Some(getter) = trailing_getter { + Some((PropertyValue::Getter(getter), None, false)) + } else { + None + } + } + }) + }) +} + +pub(crate) fn get_property_unbound( + (on, on_type_arguments): (TypeId, GenericChain), + (publicity, under, under_type_arguments): (Publicity, &PropertyKey, GenericChain), + require_both_logical: bool, + info_chain: &impl InformationChain, + types: &TypeStore, +) -> PossibleLogical { + fn wrap( + (value, slice_arguments, is_key_continous): (PropertyValue, Option, bool), + ) -> Logical { + let value = if is_key_continous { + crate::utilities::notify!("Value {:?}", value); + if let PropertyValue::ConditionallyExists { .. } = value { + // Don't make double conditional + value + } else { + PropertyValue::ConditionallyExists { + condition: TypeId::OPEN_BOOLEAN_TYPE, + truthy: Box::new(value), + } + } + } else { + value + }; + let logical_value = Logical::Pure(value); + if let Some(slice_arguments) = slice_arguments { + Logical::BasedOnKey(BasedOnKey::Left { + value: Box::new(logical_value), + key_arguments: slice_arguments, + }) + } else { + logical_value + } + } + + // Intermediate function to avoid cyclic recursion + // TODO this should return whether the result is on a exclusive type + fn get_property_on_type_unbound( + (on, on_type_arguments): (TypeId, GenericChain), + (publicity, under, under_type_arguments): (Publicity, &PropertyKey, GenericChain), + require_both_logical: bool, + info_chain: &impl InformationChain, + types: &TypeStore, + ) -> PossibleLogical { + match types.get_type_by_id(on) { + Type::SpecialObject(SpecialObject::Function(function_id, _)) => resolver( + (on, on_type_arguments), + (publicity, under, under_type_arguments), + info_chain, + types, + ) + .map(wrap) + .or_else(|| { + let get_function_from_id = types.get_function_from_id(*function_id); + let ty = if let ( + true, + crate::types::functions::FunctionBehavior::Function { prototype, .. } + | crate::types::functions::FunctionBehavior::Constructor { prototype, .. }, + ) = (under.is_equal_to("prototype"), &get_function_from_id.behavior) + { + Some(*prototype) + } else if under.is_equal_to("name") { + Some(get_function_from_id.behavior.get_name()) + } else { + None + }; + ty.map(PropertyValue::Value).map(Logical::Pure) + }) + .or_else(|| { + resolver( + (TypeId::FUNCTION_TYPE, on_type_arguments), + (publicity, under, under_type_arguments), + info_chain, + types, + ) + .map(wrap) + }) + .map(LogicalOrValid::Logical) + .ok_or(Invalid(on)), + Type::FunctionReference(_) => resolver( + (TypeId::FUNCTION_TYPE, on_type_arguments), + (publicity, under, under_type_arguments), + info_chain, + types, + ) + .map(wrap) + .map(LogicalOrValid::Logical) + .ok_or(Invalid(on)), + Type::AliasTo { to, .. } => { + get_property_on_type_unbound( + (*to, on_type_arguments), + (publicity, under, under_type_arguments), + require_both_logical, + info_chain, + types, + ) + // TODO would an alias have a property? + // let property_on_types = info_chain + // .get_chain_of_info() + // .find_map(|info| resolver(info, types, on, on_type_arguments,)) + } + Type::And(left, right) => get_property_on_type_unbound( + (*left, on_type_arguments), + (publicity, under, under_type_arguments), + require_both_logical, + info_chain, + types, + ) + .or_else(|_| { + get_property_on_type_unbound( + (*right, on_type_arguments), + (publicity, under, under_type_arguments), + require_both_logical, + info_chain, + types, + ) + }), + Type::RootPolyType(_nature) => { + if let Some(on) = on_type_arguments.and_then(|args| args.get_single_argument(on)) { + resolver( + (on, on_type_arguments), + (publicity, under, under_type_arguments), + info_chain, + types, + ) + .map(wrap) + .map(LogicalOrValid::Logical) + .ok_or(Invalid(on)) + } else { + // I think this is for assigning to properties on parameters + resolver( + (on, on_type_arguments), + (publicity, under, under_type_arguments), + info_chain, + types, + ) + .map(wrap) + .map(LogicalOrValid::Logical) + .ok_or(Invalid(on)) + .or_else(|_| { + // Can assign to properties on parameters etc + let aliases = + get_constraint(on, types).expect("poly type with no constraint"); + + if is_inferrable_type(aliases) { + Ok(LogicalOrValid::NeedsCalculation(NeedsCalculation::Infer { on })) + } else { + get_property_on_type_unbound( + (aliases, on_type_arguments), + (publicity, under, under_type_arguments), + require_both_logical, + info_chain, + types, + ) + } + }) + } + } + Type::PartiallyAppliedGenerics(crate::types::PartiallyAppliedGenerics { + on: base, + arguments, + }) => { + let on_sg_type = if let GenericArguments::Closure(_) = arguments { + resolver( + (on, on_type_arguments), + (publicity, under, under_type_arguments), + info_chain, + types, + ) + .map(wrap) + .map(LogicalOrValid::Logical) + .ok_or(Invalid(on)) + } else { + Err(Invalid(on)) + }; + + on_sg_type.or_else(|_| { + let on_type_arguments = crate::types::GenericChainLink::append( + on, + on_type_arguments.as_ref(), + arguments, + ); + + crate::utilities::notify!("{:?}", on_type_arguments); + + let property = get_property_on_type_unbound( + (*base, on_type_arguments), + (publicity, under, under_type_arguments), + require_both_logical, + info_chain, + types, + )?; + + Ok(if let LogicalOrValid::Logical(fact) = property { + Logical::Implies { on: Box::new(fact), antecedent: arguments.clone() } + .into() + } else { + property + }) + }) + } + Type::Constructor(crate::types::Constructor::ConditionalResult { .. }) + | Type::Or(..) => { + let (condition, truthy_result, otherwise_result) = + get_conditional(on, types).expect("case above != get_conditional"); + + if require_both_logical { + let left = get_property_on_type_unbound( + (truthy_result, on_type_arguments), + (publicity, under, under_type_arguments), + require_both_logical, + info_chain, + types, + )?; + let right = get_property_on_type_unbound( + (otherwise_result, on_type_arguments), + (publicity, under, under_type_arguments), + require_both_logical, + info_chain, + types, + )?; + + Ok(Logical::Or { condition, left: Box::new(left), right: Box::new(right) } + .into()) + } else { + let left = get_property_on_type_unbound( + (truthy_result, on_type_arguments), + (publicity, under, under_type_arguments), + require_both_logical, + info_chain, + types, + ); + let right = get_property_on_type_unbound( + (otherwise_result, on_type_arguments), + (publicity, under, under_type_arguments), + require_both_logical, + info_chain, + types, + ); + if 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, + ))); + Ok(Logical::Or { condition, left: Box::new(left), right: Box::new(right) } + .into()) + } + } + } + Type::Constructor(_constructor) => { + let on_constructor_type = resolver( + (on, on_type_arguments), + (publicity, under, under_type_arguments), + info_chain, + types, + ) + .map(wrap) + .map(LogicalOrValid::Logical) + .ok_or(Invalid(on)); + + let aliases = get_constraint(on, types).expect("no constraint for constructor"); + + on_constructor_type.or_else(|_| { + get_property_on_type_unbound( + (aliases, on_type_arguments), + (publicity, under, under_type_arguments), + require_both_logical, + info_chain, + types, + ) + }) + } + Type::Object(..) => { + let object_constraint_structure_generics = + crate::types::get_structure_arguments_based_on_object_constraint( + on, info_chain, types, + ); + + let prototype = info_chain + .get_chain_of_info() + .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), + info_chain, + types, + ); + + let result = if let (Some(prototype), None) = (prototype, &on_self) { + resolver( + (prototype, on_type_arguments), + (publicity, under, under_type_arguments), + 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)) + } + Type::Interface { extends, .. } => resolver( + (on, on_type_arguments), + (publicity, under, under_type_arguments), + info_chain, + types, + ) + .map(wrap) + .map(LogicalOrValid::Logical) + .ok_or(Invalid(on)) + .or_else(|_| { + if let Some(extends) = extends { + get_property_on_type_unbound( + (*extends, on_type_arguments), + (publicity, under, under_type_arguments), + require_both_logical, + info_chain, + types, + ) + } else { + Err(Invalid(on)) + } + }), + Type::SpecialObject(SpecialObject::Null) => Err(Invalid(on)), + // Type::SpecialObject(SpecialObject::ClassConstructor { .. }) | + Type::Class { .. } => resolver( + (on, on_type_arguments), + (publicity, under, under_type_arguments), + info_chain, + types, + ) + .map(wrap) + .map(LogicalOrValid::Logical) + .ok_or(Invalid(on)) + .or_else(|_| { + if let Some(prototype) = + info_chain.get_chain_of_info().find_map(|info| info.prototypes.get(&on)) + { + get_property_on_type_unbound( + (*prototype, on_type_arguments), + (publicity, under, under_type_arguments), + require_both_logical, + info_chain, + types, + ) + } else { + Err(Invalid(on)) + } + }), + // Fix for string indexing + Type::Constant(Constant::String(s)) if under.as_number(types).is_some() => { + // TODO temp TypeId::STRING_GENERIC for slice member + let idx: usize = under.as_number(types).unwrap(); + let character = s.chars().nth(idx); + if let Some(character) = character { + Ok(Logical::BasedOnKey(BasedOnKey::Left { + value: Box::new(Logical::Pure(PropertyValue::Value( + TypeId::STRING_GENERIC, + ))), + key_arguments: crate::Map::from_iter([( + TypeId::STRING_GENERIC, + (CovariantContribution::String(character.to_string()), 0), + )]), + }) + .into()) + } else { + Err(Invalid(on)) + } + } + Type::Constant(Constant::String(s)) if under.is_equal_to("length") => { + // 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_GENERIC))), + key_arguments: crate::Map::from_iter([( + TypeId::NUMBER_GENERIC, + (CovariantContribution::Number(count as f64), 0), + )]), + }) + .into()) + } + Type::Constant(cst) => resolver( + (on, on_type_arguments), + (publicity, under, under_type_arguments), + info_chain, + types, + ) + .map(wrap) + .map(LogicalOrValid::Logical) + .ok_or(Invalid(on)) + .or_else(|_| { + let backing_type = cst.get_backing_type_id(); + get_property_on_type_unbound( + (backing_type, on_type_arguments), + (publicity, under, under_type_arguments), + require_both_logical, + info_chain, + types, + ) + }), + Type::SpecialObject(SpecialObject::Promise { .. }) => { + todo!() + } + Type::SpecialObject(SpecialObject::Import(..)) => { + todo!() + } + Type::SpecialObject(SpecialObject::Proxy(proxy)) => { + Ok(LogicalOrValid::NeedsCalculation(NeedsCalculation::Proxy(*proxy, on))) + } + Type::SpecialObject(SpecialObject::Generator { .. }) => { + todo!() + } + Type::SpecialObject(SpecialObject::RegularExpression { .. }) => { + get_property_on_type_unbound( + (TypeId::REGEXP_TYPE, None), + (publicity, under, under_type_arguments), + require_both_logical, + info_chain, + types, + ) + } + } + } + + if let PropertyKey::Type(key) = under { + let key = *key; + // if *key == TypeId::ERROR_TYPE { + // return Err(MissingOrToCalculate::Error); + // } else + if let Some((condition, truthy_result, otherwise_result)) = get_conditional(key, types) { + let left = get_property_unbound( + (on, on_type_arguments), + (publicity, &PropertyKey::Type(truthy_result), under_type_arguments), + require_both_logical, + info_chain, + types, + )?; + let right = get_property_unbound( + (on, on_type_arguments), + (publicity, &PropertyKey::Type(otherwise_result), under_type_arguments), + require_both_logical, + info_chain, + types, + )?; + + Ok(Logical::Or { condition, left: Box::new(left), right: Box::new(right) }.into()) + } else if types.get_type_by_id(key).is_constant() { + crate::utilities::notify!("Here at constant"); + get_property_on_type_unbound( + (on, on_type_arguments), + (publicity, &PropertyKey::Type(key), under_type_arguments), + require_both_logical, + info_chain, + types, + ) + } else { + // { + // let debug = true; + // crate::utilities::notify!( + // "Key is {}", + // crate::types::printing::print_type(key, types, info_chain, debug) + // ); + // } + + if let Type::RootPolyType(crate::types::PolyNature::MappedGeneric { .. }) = + types.get_type_by_id(key) + { + if let Some(argument) = + under_type_arguments.and_then(|v| v.get_single_argument(key)) + { + return get_property_on_type_unbound( + (on, on_type_arguments), + (publicity, &PropertyKey::Type(argument), under_type_arguments), + require_both_logical, + info_chain, + types, + ); + } + + crate::utilities::notify!("No mapped argument, returning BasedOnKey::Right"); + Ok(Logical::BasedOnKey(BasedOnKey::Right(PropertyOn { on, key })).into()) + } else if get_constraint(on, types).is_some_and(is_inferrable_type) + || is_inferrable_type(on) + { + // TODO or above temp + Ok(LogicalOrValid::NeedsCalculation(NeedsCalculation::Infer { on })) + } else { + crate::utilities::notify!("{:?}", types.get_type_by_id(on)); + + Ok(Logical::BasedOnKey(BasedOnKey::Right(PropertyOn { on, key })).into()) + } + } + } else { + get_property_on_type_unbound( + (on, on_type_arguments), + (publicity, under, under_type_arguments), + require_both_logical, + info_chain, + types, + ) + } +} + +#[derive(Debug, Clone, Copy, binary_serialize_derive::BinarySerializable)] +pub enum AccessMode { + Regular, + /// For destructuring + DoNotBindThis, +} + +/// Also evaluates getter and binds `this` +/// +/// *be aware this creates a new type every time, bc of this binding. could cache this bound +/// types at some point* +/// TODO: `optional_chain` +#[allow(clippy::too_many_arguments)] +pub(crate) fn get_property( + on: TypeId, + publicity: Publicity, + under: &PropertyKey, + top_environment: &mut Environment, + (behavior, diagnostics): (&mut B, &mut CallingDiagnostics), + types: &mut TypeStore, + position: SpanWithSource, + mode: AccessMode, +) -> Option<(PropertyKind, TypeId)> { + // TODO remove this + if on == TypeId::ERROR_TYPE + || matches!(under, PropertyKey::Type(under) if *under == TypeId::ERROR_TYPE) + { + return Some((PropertyKind::Direct, TypeId::ERROR_TYPE)); + } + + // TODO + // Ok(new_conditional_context( + // environment, + // (is_lhs_null, lhs.1), + // |env: &mut Environment, data: &mut CheckingData| { + // A::synthesise_expression(rhs, TypeId::ANY_TYPE, env, data) + // }, + // Some(|_env: &mut Environment, _data: &mut CheckingData| lhs.0), + // checking_data, + // )) + + let (to_index, via) = if let Some(constraint) = get_constraint(on, types) { + (constraint, Some(on)) + } else if let Some(constraint) = top_environment.possibly_mutated_objects.get(&on).copied() { + (constraint, Some(on)) + } else { + (on, None) + }; + + let require_both_logical = true; + let result = get_property_unbound( + (to_index, None), + (publicity, under, None), + require_both_logical, + top_environment, + types, + ); + + { + crate::utilities::notify!("Access result {:?}", result); + } + + match result { + Ok(LogicalOrValid::Logical(logical)) => { + let (kind, result) = resolve_property_on_logical( + (logical, None), + (on, under), + top_environment, + types, + (behavior, diagnostics), + mode, + )?; + + let is_constant = types.get_type_by_id(result).is_constant(); + + if let (false, Some(_via)) = (is_constant, via) { + let constructor = types.register_type(Type::Constructor(Constructor::Property { + on, + under: under.into_owned(), + result, + mode, + })); + // TODO if not constant etc + behavior.get_latest_info(top_environment).events.push(Event::Getter { + on, + under: under.into_owned(), + reflects_dependency: Some(constructor), + publicity, + position, + mode, + }); + + Some((kind, constructor)) + } else { + Some((kind, result)) + } + } + Ok(LogicalOrValid::NeedsCalculation(NeedsCalculation::Proxy(proxy, proxy_ty))) => { + proxy_access((proxy, proxy_ty), under, (behavior, diagnostics), top_environment, types) + } + Ok(LogicalOrValid::NeedsCalculation(NeedsCalculation::Infer { .. })) => { + crate::utilities::notify!("Here infer constraint"); + let constructor = types.register_type(Type::Constructor(Constructor::Property { + on, + under: under.into_owned(), + result: TypeId::ANY_TO_INFER_TYPE, + mode, + })); + // TODO if not constant etc + behavior.get_latest_info(top_environment).events.push(Event::Getter { + on, + under: under.into_owned(), + reflects_dependency: Some(constructor), + publicity, + position, + mode, + }); + + Some((PropertyKind::Direct, constructor)) + } + Err(_err) => None, + } +} + +/// Generates closure arguments, values of this and more. Runs getters +/// +/// TODO generics +fn resolve_property_on_logical( + (logical, generics): (Logical, GenericChain), + (on, under): (TypeId, &PropertyKey), + environment: &mut Environment, + types: &mut TypeStore, + (behavior, diagnostics): (&mut B, &mut CallingDiagnostics), + mode: AccessMode, +) -> Option<(PropertyKind, TypeId)> { + match logical { + Logical::Pure(property) => { + match property { + PropertyValue::Value(value) => { + let ty = types.get_type_by_id(value); + // TODO some of these need proper `substitute` I think + match ty { + Type::SpecialObject(SpecialObject::Function(func, _state)) => { + let this_value = if let AccessMode::DoNotBindThis = mode { + // Not `ThisValue::Passed`, but not sure about this + ThisValue::UseParent + } else { + ThisValue::Passed(on) + }; + let func = types.register_type(Type::SpecialObject( + SpecialObject::Function(*func, this_value), + )); + + Some((PropertyKind::Direct, func)) + } + Type::FunctionReference(_) => { + let ty = if let Some(arguments) = + generics.and_then(GenericChainLink::get_value).cloned() + { + // assert!(chain.parent.is_none()); + types.register_type(Type::PartiallyAppliedGenerics( + PartiallyAppliedGenerics { on: value, arguments }, + )) + } else { + value + }; + + Some((PropertyKind::Direct, ty)) + } + Type::Constructor(_) | Type::RootPolyType { .. } => { + if let Some(generics) = generics { + let arguments = generics.build_substitutable(types); + let value = + crate::types::substitute(value, &arguments, environment, types); + + if value == TypeId::NEVER_TYPE { + crate::utilities::notify!("Here"); + } + Some((PropertyKind::Direct, value)) + } else { + Some((PropertyKind::Direct, value)) + } + // if let Some(entries) = generics + // .and_then(|args| args.get_argument(value, environment, types)) + // { + // let mut iter = entries.into_iter(); + // let mut ty = iter.next().unwrap(); + // for other in iter { + // ty = types.new_or_type(ty, other); + // } + // Some(( + // PropertyKind::Direct, + // if types.get_type_by_id(ty).is_constant() { + // ty + // } else { + // types.register_type(Type::Constructor( + // Constructor::Property { + // on, + // under: under.into_owned(), + // result: ty, + // mode, + // }, + // )) + // }, + // )) + // } else { + // } + } + Type::SpecialObject(..) | Type::Object(..) | Type::Constant(..) => { + Some((PropertyKind::Direct, value)) + } + Type::Class { .. } + | Type::Interface { .. } + | Type::And(_, _) + | Type::AliasTo { to: _, name: _, parameters: _ } + | Type::Or(_, _) => { + crate::utilities::notify!( + "property was {:?} {:?}, which should be NOT be able to be returned from a function", + property, ty + ); + + Some((PropertyKind::Direct, value)) + } + // For closures + Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: sg_on, + arguments, + }) => { + // TODO not great +2 types... need less overhead + if let Type::SpecialObject(SpecialObject::Function(f, _p)) = + types.get_type_by_id(*sg_on) + { + let arguments = arguments.clone(); + let f = types.register_type(Type::SpecialObject( + SpecialObject::Function(*f, ThisValue::Passed(on)), + )); + let ty = types.register_type(Type::PartiallyAppliedGenerics( + PartiallyAppliedGenerics { on: f, arguments }, + )); + Some((PropertyKind::Direct, ty)) + } else { + Some((PropertyKind::Direct, value)) + } + } + } + } + PropertyValue::GetterAndSetter { getter, setter: _ } + | PropertyValue::Getter(getter) => { + use crate::types::calling::{ + application_result_to_return_type, CalledWithNew, CallingInput, + }; + + let input = CallingInput { + called_with_new: CalledWithNew::GetterOrSetter { this_type: on }, + // TODO + call_site: source_map::Nullable::NULL, + // TODO + max_inline: 0, + }; + let result = + getter.call(Vec::new(), input, environment, (behavior, diagnostics), types); + if let Ok(res) = result { + let application_result = + application_result_to_return_type(res.result, environment, types); + Some((PropertyKind::Getter, application_result)) + } else { + crate::utilities::notify!("TODO merge calling"); + Some((PropertyKind::Getter, TypeId::ERROR_TYPE)) + } + } + PropertyValue::Setter(_) => { + crate::utilities::notify!("Found setter. TODO warning"); + None + } + PropertyValue::Deleted => None, + PropertyValue::ConditionallyExists { condition, truthy } => { + crate::utilities::notify!("{:?} {:?}", condition, generics); + + let condition = if let TypeId::NON_OPTIONAL_KEY_ARGUMENT = condition { + // `unwrap_or(TypeId::TRUE)` temp fix for argument not passed along + generics + .as_ref() + .and_then(|link| link.get_single_argument(condition)) + .unwrap_or(TypeId::TRUE) + } else { + generics + .as_ref() + .and_then(|link| link.get_single_argument(condition)) + .unwrap_or(condition) + }; + + if let TypeId::FALSE = condition { + None + } else { + let (kind, value) = resolve_property_on_logical( + (Logical::Pure(*truthy), generics), + (on, under), + environment, + types, + (behavior, diagnostics), + mode, + )?; + let value = if let TypeId::TRUE = condition { + value + } else { + types.new_conditional_type(condition, value, TypeId::UNDEFINED_TYPE) + }; + Some((kind, value)) + } + } + PropertyValue::Configured { on: value, .. } => resolve_property_on_logical( + (Logical::Pure(*value), generics), + (on, under), + environment, + types, + (behavior, diagnostics), + mode, + ), + } + } + Logical::Or { left, right, condition } => { + // if let (Ok(lhs), Ok(rhs)) = (*left, *right) { + let (_, left) = if let LogicalOrValid::Logical(left) = *left { + resolve_property_on_logical( + (left, generics), + (on, under), + environment, + types, + (behavior, diagnostics), + mode, + )? + } else { + todo!() + }; + let (_, right) = if let LogicalOrValid::Logical(right) = *right { + resolve_property_on_logical( + (right, generics), + (on, under), + environment, + types, + (behavior, diagnostics), + mode, + )? + } else { + todo!() + }; + Some((PropertyKind::Direct, types.new_conditional_type(condition, left, right))) + // } else { + // crate::utilities::notify!("TODO emit some diagnostic about missing"); + // None + // } + } + Logical::Implies { on: log_on, antecedent } => { + crate::utilities::notify!("from=TypeId::UNIMPLEMENTED_ERROR_TYPE here"); + crate::utilities::notify!("antecedent={:?}", antecedent); + let generics = GenericChainLink::append( + // TODO + TypeId::UNIMPLEMENTED_ERROR_TYPE, + generics.as_ref(), + &antecedent, + ); + resolve_property_on_logical( + (*log_on, generics), + (on, under), + environment, + types, + (behavior, diagnostics), + mode, + ) + } + Logical::BasedOnKey(kind) => match kind { + BasedOnKey::Left { value, key_arguments } => { + let generics = Some(GenericChainLink::MappedPropertyLink { + parent_link: generics.as_ref(), + value: &key_arguments, + }); + + resolve_property_on_logical( + (*value, generics), + (on, under), + environment, + types, + (behavior, diagnostics), + mode, + ) + } + BasedOnKey::Right(property_on) => { + crate::utilities::notify!("{:?}", generics); + let result = property_on.get_on(generics, environment, types)?; + + // { + // let constructor = types.register_type(Type::Constructor(Constructor::Property { + // on, + // under: under.into_owned(), + // result, + // mode, + // })); + // // TODO if not constant etc + // behavior.get_latest_info(environment).events.push(Event::Getter { + // on, + // under: under.into_owned(), + // reflects_dependency: Some(constructor), + // // always this + // publicity: Publicity::Public, + // // TODO + // position: SpanWithSource::NULL, + // mode, + // }); + // Some((PropertyKind::Direct, constructor)) + // } + + Some((PropertyKind::Direct, result)) + } + }, + } +} + +pub(crate) fn proxy_access( + (Proxy { handler, over }, resolver): (Proxy, TypeId), + under: &PropertyKey, + (behavior, diagnostics): (&mut B, &mut CallingDiagnostics), + environment: &mut Environment, + types: &mut TypeStore, +) -> Option<(PropertyKind, TypeId)> { + use crate::types::calling::{ + application_result_to_return_type, CalledWithNew, CallingInput, SynthesisedArgument, + }; + + // TODO pass down + let position = SpanWithSource::NULL; + let property_key = PropertyKey::String(std::borrow::Cow::Borrowed("get")); + let result = get_property( + handler, + Publicity::Public, + &property_key, + environment, + (behavior, diagnostics), + types, + position, + AccessMode::DoNotBindThis, + ); + + if let Some((_, get_trap)) = result { + let key_to_pass_to_function = under.into_type(types); + // TODO receiver + let arguments = vec![ + SynthesisedArgument { spread: false, value: over, position }, + SynthesisedArgument { spread: false, value: key_to_pass_to_function, position }, + SynthesisedArgument { spread: false, value: resolver, position }, + ]; + let input = CallingInput { + // TOOD special + called_with_new: CalledWithNew::GetterOrSetter { this_type: handler }, + // TODO + call_site: source_map::Nullable::NULL, + // TODO + max_inline: 0, + }; + let result = crate::types::calling::Callable::Type(get_trap).call( + arguments, + input, + environment, + (behavior, diagnostics), + types, + ); + if let Ok(res) = result { + let application_result = + application_result_to_return_type(res.result, environment, types); + Some((PropertyKind::Getter, application_result)) + } else { + crate::utilities::notify!("TODO merge calling"); + Some((PropertyKind::Getter, TypeId::ERROR_TYPE)) + } + } else { + get_property( + over, + Publicity::Public, + under, + environment, + (behavior, diagnostics), + types, + position, + AccessMode::DoNotBindThis, + ) + } +} diff --git a/checker/src/types/properties/assignment.rs b/checker/src/types/properties/assignment.rs new file mode 100644 index 00000000..1038f3a1 --- /dev/null +++ b/checker/src/types/properties/assignment.rs @@ -0,0 +1,618 @@ +use super::{get_property_unbound, Descriptor, PropertyKey, PropertyValue, Publicity}; + +use crate::{ + context::CallCheckingBehavior, + diagnostics::{PropertyKeyRepresentation, TypeStringRepresentation}, + events::Event, + features::objects::Proxy, + subtyping::{State, SubTypeResult}, + types::{ + calling::{CallingDiagnostics, CallingOutput, SynthesisedArgument}, + get_constraint, + logical::{BasedOnKey, Logical, LogicalOrValid}, + tuple_like, Constructor, GenericChain, NeedsCalculation, PartiallyAppliedGenerics, + TypeStore, + }, + Environment, Type, TypeId, +}; + +use source_map::SpanWithSource; + +pub enum SetPropertyError { + /// Both readonly readonly modifier (TS), readonly property (TS), frozen and not writable + NotWriteable { + property: PropertyKeyRepresentation, + position: SpanWithSource, + }, + DoesNotMeetConstraint { + property_constraint: TypeStringRepresentation, + value_type: TypeStringRepresentation, + reason: crate::types::subtyping::NonEqualityReason, + position: SpanWithSource, + }, + /// I Has no effect but doesn't throw an error + /// + /// [Although only a strict mode issue][https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only] it can break the type syetem + AssigningToGetter { + property: PropertyKeyRepresentation, + position: SpanWithSource, + }, + // TODO #170 + // AssigningToTuple, + AssigningToNonExistent { + property: PropertyKeyRepresentation, + position: SpanWithSource, + }, +} + +pub type SetPropertyResult = Result<(), SetPropertyError>; + +/// Aka a assignment to a property, **INCLUDING initialization of a new one** +/// - Evaluates setters +/// - This handles both objects and poly types +/// - Only handles `PropertyValue`. See info.register for conditionals, getters etc +pub fn set_property( + on: TypeId, + (publicity, under, new): (Publicity, &PropertyKey, TypeId), + position: SpanWithSource, + environment: &mut Environment, + (behavior, diagnostics): (&mut B, &mut CallingDiagnostics), + types: &mut TypeStore, +) -> SetPropertyResult { + // Frozen checks + { + if environment.info.frozen.contains(&on) { + return Err(SetPropertyError::NotWriteable { + property: PropertyKeyRepresentation::new(under, environment, types), + position, + }); + } + } + + // Doing this regardless of E::CHECK_PARAMETERS is how #18 works + { + let object_constraint = + environment.get_object_constraint(on).or_else(|| get_constraint(on, types)); + + if let Some(object_constraint) = object_constraint { + // crate::utilities::notify!("constraint={:?}", types.get_type_by_id(constraint)); + + if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::READONLY_RESTRICTION, + .. + }) = types.get_type_by_id(object_constraint) + { + return Err(SetPropertyError::NotWriteable { + property: PropertyKeyRepresentation::new(under, environment, types), + position, + }); + } + + let property_constraint = get_property_unbound( + (object_constraint, None), + (publicity, under, None), + false, + environment, + types, + ); + + // crate::utilities::notify!("Property constraint .is_some() {:?}", property_constraint.is_some()); + + // crate::utilities::notify!( + // "Re-assignment constraint {}, prop={} {:?}", + // print_type(constraint, types, environment, true), + // print_type(under, types, environment, true), + // property_constraint + // ); + + if let Ok(LogicalOrValid::Logical(property_constraint)) = property_constraint { + // TODO property value is readonly + // TODO state is writeable etc? + // TODO document difference with context.writeable + + // match new { + // PropertyValue::Value(value) => { + // TODO ...? + let mut state = State { + already_checked: Default::default(), + mode: Default::default(), + contributions: Default::default(), + others: Default::default(), + object_constraints: Default::default(), + }; + let result = crate::subtyping::type_is_subtype_of_property( + (&property_constraint, None), + new, + &mut state, + environment, + types, + ); + if let SubTypeResult::IsNotSubType(reason) = result { + let is_modifying_tuple_length = under.is_equal_to("length") + && tuple_like(object_constraint, types, environment); + + crate::utilities::notify!( + "is_modifying_tuple_length={:?}", + is_modifying_tuple_length + ); + + let property_constraint = TypeStringRepresentation::from_property_constraint( + property_constraint, + None, + environment, + types, + false, + ); + + let value_type = + TypeStringRepresentation::from_type_id(new, environment, types, false); + + return Err(SetPropertyError::DoesNotMeetConstraint { + property_constraint, + value_type, + reason, + position, + }); + } + // if get_constraint(on, types).is_none() { + // crate::utilities::notify!("Here"); + // return Err(SetPropertyError::AssigningToNonExistent { + // property: PropertyKeyRepresentation::new(under, environment, types), + // position, + // }); + // } + crate::utilities::notify!("TODO assigning to non existent"); + + // PropertyValue::Getter(_) + // | PropertyValue::Setter(_) + // | PropertyValue::GetterAndSetter { .. } + // | PropertyValue::Deleted => {} + // PropertyValue::ConditionallyExists { truthy: ref _truthy, .. } => { + // crate::utilities::notify!("Here assigning to conditional. TODO recursive"); + // } + // PropertyValue::Configured { descriptor, .. } => { + // crate::utilities::notify!("TODO nested"); + // // TODO doesn't account for other types here + // if !matches!(descriptor.writable, TypeId::TRUE) { + // return Err(SetPropertyError::NotWriteable { + // property: PropertyKeyRepresentation::new(under, environment, types), + // position, + // }); + // } + // } + } + } + } + + // crate::utilities::notify!( + // "setting {:?} {:?} {:?}", + // crate::types::printing::print_type(types, on, environment, true), + // crate::types::printing::print_type(types, under, environment, true), + // crate::types::printing::print_type(types, new.as_get_type(), environment, true), + // ); + + // crate::utilities::notify!("(2) Made it here assigning to {:?}", types.get_type_by_id(on)); + + // Cascade if it is a union (unsure tho) + if let Type::Constructor(Constructor::ConditionalResult { + truthy_result, + otherwise_result, + condition: _, + result_union: _, + }) = types.get_type_by_id(on) + { + crate::utilities::notify!("Here"); + let truthy = *truthy_result; + let otherwise_result = *otherwise_result; + + // TODO under conditionals for side effected setter + set_property( + truthy, + (publicity, under, new), + position, + environment, + (behavior, diagnostics), + types, + )?; + return set_property( + otherwise_result, + (publicity, under, new), + position, + environment, + (behavior, diagnostics), + types, + ); + } + + // IMPORTANT: THIS ALSO CAPTURES POLY CONSTRAINTS + let current_property = + get_property_unbound((on, None), (publicity, under, None), false, environment, types); + + if let Ok(value) = current_property { + crate::utilities::notify!("Got {:?}", value); + match value { + LogicalOrValid::Logical(fact) => set_on_logical( + fact, + (behavior, diagnostics), + environment, + (on, None), + (publicity, under, new), + types, + position, + ), + LogicalOrValid::NeedsCalculation(NeedsCalculation::Infer { on }) => { + crate::utilities::notify!("TODO add assignment request on {:?}", on); + behavior.get_latest_info(environment).events.push(Event::Setter { + on, + new, + under: under.into_owned(), + publicity, + position, + }); + Ok(()) + } + LogicalOrValid::NeedsCalculation(NeedsCalculation::Proxy(proxy, on)) => proxy_assign( + (proxy, on), + under, + new, + position, + (behavior, diagnostics), + environment, + types, + ), + } + } else if get_constraint(on, types).is_some() { + Err(SetPropertyError::AssigningToNonExistent { + property: PropertyKeyRepresentation::new(under, environment, types), + position, + }) + } else { + crate::utilities::notify!("No property on object, assigning anyway"); + let info = behavior.get_latest_info(environment); + info.register_property( + on, + publicity, + under.into_owned(), + PropertyValue::Value(new), + position, + ); + Ok(()) + } +} + +fn set_on_logical( + existing: Logical, + (behavior, diagnostics): (&mut B, &mut CallingDiagnostics), + environment: &mut Environment, + (on, generics): (TypeId, GenericChain), + (publicity, under, new): (Publicity, &PropertyKey, TypeId), + types: &mut TypeStore, + position: SpanWithSource, +) -> SetPropertyResult { + match existing { + Logical::Pure(existing) => run_setter_on_object( + (existing, None), + (behavior, diagnostics), + environment, + (on, generics), + (publicity, under, new), + types, + position, + ), + Logical::Or { .. } => { + if types.get_type_by_id(on).is_dependent() { + { + let info = behavior.get_latest_info(environment); + // TODO cannot do because if substituted property is getter then the following is not true + // info.current_properties.entry(on).or_default().push(( + // publicity, + // under.into_owned(), + // new.clone(), + // )); + info.events.push(Event::Setter { + on, + new, + under: under.into_owned(), + publicity, + position, + }); + } + } else { + crate::utilities::notify!("TODO "); + } + Ok(()) + } + Logical::Implies { on: og, antecedent } => { + crate::utilities::notify!("antecedent={:?}", antecedent); + let generics = crate::types::GenericChainLink::FunctionRoot { + parent_arguments: Some(&antecedent), + call_site_type_arguments: None, + // TODO weird? + type_arguments: &crate::Map::default(), + }; + set_on_logical( + *og, + (behavior, diagnostics), + environment, + (on, Some(generics)), + (publicity, under, new), + types, + position, + ) + } + Logical::BasedOnKey(kind) => { + if let BasedOnKey::Left { value, key_arguments } = kind { + let generics = crate::types::GenericChainLink::MappedPropertyLink { + parent_link: generics.as_ref(), + value: &key_arguments, + }; + set_on_logical( + *value, + (behavior, diagnostics), + environment, + (on, Some(generics)), + (publicity, under, new), + types, + position, + ) + } else { + crate::utilities::notify!("Here {:?}", kind); + + { + let info = behavior.get_latest_info(environment); + info.current_properties.entry(on).or_default().push(( + publicity, + under.into_owned(), + PropertyValue::Value(new), + )); + info.events.push(Event::Setter { + on, + new, + under: under.into_owned(), + publicity, + position, + }); + } + + // set_on_logical( + // *og, + // behavior, + // environment, + // (on, None), + // (publicity, under, new), + // types, + // position, + // ) + Ok(()) + } + } + } +} + +/// `Vec` from calling setter +/// +/// `og` = current last value, `position` = assignment position +#[allow(clippy::too_many_arguments)] +fn run_setter_on_object( + (existing, descriptor): (PropertyValue, Option), + (behavior, diagnostics): (&mut B, &mut CallingDiagnostics), + environment: &mut Environment, + (on, generics): (TypeId, GenericChain), + (publicity, under, new): (Publicity, &PropertyKey<'_>, TypeId), + types: &mut TypeStore, + position: SpanWithSource, +) -> SetPropertyResult { + match existing { + PropertyValue::Deleted | PropertyValue::Value(..) => { + if let (Some(_), PropertyValue::Value(constraint_for_new)) = + (get_constraint(on, types), existing) + { + // TODO ...? + let mut state = State { + already_checked: Default::default(), + mode: Default::default(), + contributions: Default::default(), + others: Default::default(), + object_constraints: Default::default(), + }; + let result = crate::subtyping::type_is_subtype_with_generics( + (constraint_for_new, generics), + (new, None), + &mut state, + environment, + types, + ); + if let SubTypeResult::IsNotSubType(reason) = result { + { + crate::utilities::notify!( + "{} {:?}", + crate::types::printing::print_type( + constraint_for_new, + types, + environment, + true + ), + generics + ); + } + + let property_constraint = TypeStringRepresentation::from_property_constraint( + Logical::Pure(PropertyValue::Value(constraint_for_new)), + generics, + environment, + types, + false, + ); + let value_type = + TypeStringRepresentation::from_type_id(new, environment, types, false); + + // TOOD generics + return Err(SetPropertyError::DoesNotMeetConstraint { + property_constraint, + value_type, + reason, + position, + }); + } + } + + let value = if let Some(descriptor) = descriptor { + PropertyValue::Configured { on: Box::new(PropertyValue::Value(new)), descriptor } + } else { + PropertyValue::Value(new) + }; + + let info = behavior.get_latest_info(environment); + info.current_properties.entry(on).or_default().push(( + publicity, + under.into_owned(), + value, + )); + info.events.push(Event::Setter { + on, + new, + under: under.into_owned(), + publicity, + position, + }); + Ok(()) + } + PropertyValue::Getter(_) => Err(SetPropertyError::AssigningToGetter { + property: PropertyKeyRepresentation::new(under, environment, types), + position, + }), + PropertyValue::GetterAndSetter { setter, getter: _ } | PropertyValue::Setter(setter) => { + use crate::types::calling::{CalledWithNew, CallingInput}; + + let arg = SynthesisedArgument { position, spread: false, value: new }; + let this_type = generics.and_then(|arg| arg.get_origin()).unwrap_or(on); + let input = CallingInput { + called_with_new: CalledWithNew::GetterOrSetter { this_type }, + call_site: position, + // TODO + max_inline: 0, + }; + + let result = setter.call(vec![arg], input, environment, (behavior, diagnostics), types); + + // TODO add event if dependent + + match result { + Ok(CallingOutput { + result: _, + called: _, + special: _, + result_was_const_computation: _, + }) => Ok(()), + Err(_errors) => Ok(()), + } + } + PropertyValue::ConditionallyExists { condition: _condition, truthy } => { + crate::utilities::notify!("TODO conditionally"); + + run_setter_on_object( + (*truthy, descriptor), + (behavior, diagnostics), + environment, + (on, generics), + (publicity, under, new), + types, + position, + ) + } + PropertyValue::Configured { on: existing_value, descriptor } => { + crate::utilities::notify!("descriptor={:?} {:?}", descriptor, generics); + + let writable = generics + .as_ref() + .and_then(|link| link.get_single_argument(descriptor.writable)) + .unwrap_or(descriptor.writable); + + if !matches!(writable, TypeId::TRUE) { + crate::utilities::notify!("{:?}", writable); + return Err(SetPropertyError::NotWriteable { + property: PropertyKeyRepresentation::new(under, environment, types), + position, + }); + } + + // let new = PropertyValue::Configured { on: Box::new(new), descriptor }; + + run_setter_on_object( + (*existing_value, Some(descriptor)), + (behavior, diagnostics), + environment, + (on, generics), + (publicity, under, new), + types, + position, + ) + } + } +} + +pub(crate) fn proxy_assign( + (Proxy { handler, over }, resolver): (Proxy, TypeId), + under: &PropertyKey, + new: TypeId, + position: SpanWithSource, + (behavior, diagnostics): (&mut B, &mut CallingDiagnostics), + environment: &mut Environment, + types: &mut TypeStore, +) -> SetPropertyResult { + use crate::types::calling::{CalledWithNew, CallingInput, SynthesisedArgument}; + use crate::types::properties::{get_property, AccessMode}; + + let property_key = PropertyKey::String(std::borrow::Cow::Borrowed("set")); + let result = get_property( + handler, + Publicity::Public, + &property_key, + environment, + (behavior, diagnostics), + types, + position, + AccessMode::DoNotBindThis, + ); + + if let Some((_, set_trap)) = result { + let key_to_pass_to_function = under.into_type(types); + // TODO receiver + let arguments = vec![ + SynthesisedArgument { spread: false, value: over, position }, + SynthesisedArgument { spread: false, value: key_to_pass_to_function, position }, + SynthesisedArgument { spread: false, value: new, position }, + SynthesisedArgument { spread: false, value: resolver, position }, + ]; + let input = CallingInput { + // TOOD special + called_with_new: CalledWithNew::GetterOrSetter { this_type: handler }, + // TODO + call_site: source_map::Nullable::NULL, + // TODO + max_inline: 0, + }; + let result = crate::types::calling::Callable::Type(set_trap).call( + arguments, + input, + environment, + (behavior, diagnostics), + types, + ); + if let Ok(_res) = result { + Ok(()) + } else { + crate::utilities::notify!("TODO Proxy.set failed but returning Ok() (as difference captured in CallingDiagnostics)"); + Ok(()) + } + } else { + set_property( + over, + (Publicity::Public, under, new), + position, + environment, + (behavior, diagnostics), + types, + ) + } +} diff --git a/checker/src/types/properties/list.rs b/checker/src/types/properties/list.rs new file mode 100644 index 00000000..06bb2d2a --- /dev/null +++ b/checker/src/types/properties/list.rs @@ -0,0 +1,252 @@ +use super::{PropertyKey, PropertyValue}; +use crate::{ + context::{InformationChain, Properties}, + types::{GenericChain, SliceArguments}, + Type, TypeId, TypeStore, +}; +use std::collections::{BTreeMap, HashMap}; + +/// Get properties on a type (for printing and other non-one property uses) +/// +/// - TODO prototypes? +/// - TODO return whether it is fixed (conditional + conditional enumerable + non string keys) +/// - TODO doesn't evaluate properties +/// - TODO don't have to reverse at end +/// - `filter_enumerable` for printing vs `for in` loops +pub fn get_properties_on_single_type( + base: TypeId, + types: &TypeStore, + info: &impl InformationChain, + filter_enumerable: bool, + filter_type: TypeId, +) -> Properties { + match types.get_type_by_id(base) { + Type::Interface { .. } | Type::Class { .. } | Type::Object(_) => { + // Reversed needed for deleted + let flattened_properties = info + .get_chain_of_info() + .filter_map(|info| info.current_properties.get(&base).map(|v| v.iter())) + .flatten(); + + let mut existing_properties = HashMap::::new(); + + // This retains ordering here + + let mut properties = Vec::new(); + let mut numerical_properties = BTreeMap::new(); + + for (publicity, key, value) in flattened_properties { + if let PropertyValue::Configured { on: _, ref descriptor } = value { + // TODO what about if not `TypeId::TRUE | TypeId::FALSE` + crate::utilities::notify!("descriptor.enumerable={:?}", descriptor.enumerable); + if filter_enumerable && !matches!(descriptor.enumerable, TypeId::TRUE) { + continue; + } + } + + let existing = existing_properties.insert(key.clone(), properties.len()); + if let PropertyValue::Deleted = value { + if let Some(existing) = existing { + properties.remove(existing); + } + // TODO only covers constant keys :( + continue; + } + + if !matches!(filter_type, TypeId::ANY_TYPE) { + let on_type_arguments = None; // TODO + let (key_matches, key_arguments) = super::key_matches( + (&PropertyKey::Type(filter_type), on_type_arguments), + (key, None), + info, + types, + ); + + crate::utilities::notify!("key_arguments={:?}", key_arguments); + + if !key_matches { + if let Some(existing) = existing { + properties.remove(existing); + } + continue; + } + } + + if let Some(idx) = existing { + let value = (*publicity, key.to_owned(), value.clone()); + + if let Some(n) = key.as_number(types) { + numerical_properties.insert(n, value); + } else { + // TODO temp fix + if idx >= properties.len() { + crate::utilities::notify!("Here! {:?}", properties); + continue; + } + properties[idx] = value; + } + } else { + // crate::utilities::notify!("Here just with {:?}", key); + + let value = (*publicity, key.to_owned(), value.clone()); + + if let Some(n) = key.as_number(types) { + numerical_properties.insert(n, value); + } else { + properties.push(value); + } + } + } + + if numerical_properties.is_empty() { + properties + } else { + numerical_properties.into_values().chain(properties).collect() + } + } + t @ (Type::SpecialObject(_) + | Type::Constructor(_) + | Type::RootPolyType(_) + | Type::Or(..) + | Type::PartiallyAppliedGenerics(_) + | Type::Constant(_) + | Type::AliasTo { .. } + | Type::FunctionReference(_) + | Type::And(_, _)) => panic!("Cannot get all properties on {t:?}"), + } +} + +/// WIP TODO remove filter +/// Slightly different to regular `get_properties_on_single_type` +/// - appends key argument +/// - no numerical sorting +/// - no enumerable +pub fn get_properties_on_single_type2( + (base, base_arguments): (TypeId, GenericChain), + types: &TypeStore, + info: &impl InformationChain, + filter_type: TypeId, +) -> Vec<(PropertyKey<'static>, PropertyValue, SliceArguments)> { + match types.get_type_by_id(base) { + Type::Interface { .. } | Type::Class { .. } | Type::Object(_) => { + // Reversed needed for deleted + let flattened_properties = info + .get_chain_of_info() + .filter_map(|info| info.current_properties.get(&base).map(|v| v.iter())) + .flatten(); + + let mut existing_properties = HashMap::::new(); + + // This retains ordering here + + let mut properties = Vec::new(); + + for (_publicity, key, value) in flattened_properties { + let existing = existing_properties.insert(key.clone(), properties.len()); + if let PropertyValue::Deleted = value { + if let Some(existing) = existing { + properties.remove(existing); + } + // TODO only covers constant keys :( + continue; + } + + // if !matches!(filter_type, TypeId::ANY_TYPE) { + let on_type_arguments = None; // TODO + let (key_matches, key_arguments) = super::key_matches( + (&PropertyKey::Type(filter_type), on_type_arguments), + (key, None), + info, + types, + ); + + // crate::utilities::notify!("key_arguments={:?}", key_arguments); + + if !key_matches { + continue; + } + // } + + if let Some(idx) = existing { + let value = (key.to_owned(), value.clone(), key_arguments); + properties[idx] = value; + } else { + let value = (key.to_owned(), value.clone(), key_arguments); + properties.push(value); + } + } + + properties + // if numerical_properties.is_empty() { + // } else { + // todo!() + // // numerical_properties.into_values().chain(properties).collect() + // } + } + Type::Constructor(_) | Type::RootPolyType(_) => { + if let Some(argument) = + base_arguments.as_ref().and_then(|args| args.get_single_argument(base)) + { + get_properties_on_single_type2((argument, base_arguments), types, info, filter_type) + } else { + todo!("Getting properties on generic") + } + } + Type::PartiallyAppliedGenerics(crate::types::PartiallyAppliedGenerics { + on, + arguments, + }) => { + // Temp fix + if *on == TypeId::ARRAY_TYPE { + let value = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap(); + vec![( + PropertyKey::Type(TypeId::NUMBER_TYPE), + PropertyValue::Value(value), + Default::default(), + )] + } else { + // let result = super::access::get_property_unbound( + // (base, base_arguments), + // (Publicity::Public, &PropertyKey::Type(filter_type), None), + // false, + // info, + // types, + // ); + todo!("{:?}", (on, arguments)); + } + } + t @ (Type::SpecialObject(_) + | Type::Or(..) + | Type::Constant(_) + | Type::AliasTo { .. } + | Type::FunctionReference(_) + | Type::And(_, _)) => panic!("Cannot get all properties on {t:?}"), + } +} + +pub fn get_property_key_names_on_a_single_type( + base: TypeId, + types: &TypeStore, + environment: &mut crate::Environment, +) -> Vec { + let is_special = matches!( + types.get_type_by_id(base), + Type::SpecialObject(_) + | Type::Constructor(_) + | Type::RootPolyType(_) + | Type::Or(..) + | Type::PartiallyAppliedGenerics(_) + | Type::Constant(_) + | Type::AliasTo { .. } + | Type::FunctionReference(_) + | Type::And(_, _) + ); + if is_special { + return vec![]; + } + + get_properties_on_single_type(base, types, environment, false, TypeId::ANY_TYPE) + .into_iter() + .map(|property| super::get_property_as_string(&property.1, types, environment)) + .collect() +} diff --git a/checker/src/types/properties/mod.rs b/checker/src/types/properties/mod.rs new file mode 100644 index 00000000..3b5a3b52 --- /dev/null +++ b/checker/src/types/properties/mod.rs @@ -0,0 +1,486 @@ +pub mod access; +pub mod assignment; +pub mod list; + +pub use access::*; +pub use assignment::set_property; +pub use list::*; + +use super::{Type, TypeStore}; +use crate::{ + context::InformationChain, + subtyping::{slice_matches_type, SliceArguments, SubTypingOptions}, + types::{calling::Callable, generics::contributions::Contributions, GenericChain, PolyNature}, + Constant, Environment, TypeId, +}; +use std::borrow::Cow; + +#[derive(PartialEq)] +pub enum PropertyKind { + Direct, + Getter, + Setter, + /// TODO unsure + Generic, +} + +/// TODO explain usage +/// For [private properties](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_properties) +#[derive(Debug, Clone, Copy, PartialEq, Eq, binary_serialize_derive::BinarySerializable)] +pub enum Publicity { + Private, + Public, +} + +/// TODO symbol +/// Implements basic definition equality, not type equality +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum PropertyKey<'a> { + String(Cow<'a, str>), + /// Use [`PropertyKey::from_type`] for canonicalisation (generating [`PropertyKey::String`] when possible) + Type(TypeId), + // SomeThingLike(TypeId), +} + +impl From<&'static str> for PropertyKey<'static> { + fn from(value: &'static str) -> Self { + Self::String(Cow::Borrowed(value)) + } +} + +// Cannot auto derive BinarySerializable because lifetime +impl crate::BinarySerializable for PropertyKey<'static> { + fn serialize(self, buf: &mut Vec) { + match self { + PropertyKey::String(s) => { + buf.push(0); + crate::BinarySerializable::serialize(s.into_owned(), buf); + } + PropertyKey::Type(t) => { + buf.push(1); + crate::BinarySerializable::serialize(t, buf); + } + } + } + + fn deserialize>(iter: &mut I, source: source_map::SourceId) -> Self { + match iter.next().unwrap() { + 0 => Self::String(Cow::Owned(crate::BinarySerializable::deserialize(iter, source))), + 1 => Self::Type(crate::BinarySerializable::deserialize(iter, source)), + _ => panic!("bad code"), + } + } +} + +impl<'a> PropertyKey<'a> { + #[must_use] + pub fn into_owned(&self) -> PropertyKey<'static> { + match self { + PropertyKey::String(s) => PropertyKey::String(Cow::Owned(s.to_string())), + PropertyKey::Type(s) => PropertyKey::Type(*s), + } + } + + pub(crate) fn from_type(ty: TypeId, types: &TypeStore) -> PropertyKey<'static> { + if let Type::Constant(c) = types.get_type_by_id(ty) { + match c { + Constant::Number(n) => { + // if n.fractional ?? + PropertyKey::from_usize(n.into_inner() as usize) + } + Constant::String(s) => PropertyKey::String(Cow::Owned(s.to_owned())), + Constant::Boolean(_) => todo!(), + Constant::Symbol { .. } => { + // Okay I think? + PropertyKey::Type(ty) + } + Constant::NaN => todo!(), + } + } else { + PropertyKey::Type(ty) + } + } + + pub(crate) fn as_number(&self, types: &TypeStore) -> Option { + match self { + PropertyKey::String(s) => s.parse::().ok(), + PropertyKey::Type(t) => { + if let Type::Constant(Constant::Number(n)) = types.get_type_by_id(*t) { + // TODO is there a better way + #[allow(clippy::float_cmp)] + if n.trunc() == **n { + Some(**n as usize) + } else { + None + } + } else { + None + } + } + } + } + + pub(crate) fn new_empty_property_key() -> Self { + PropertyKey::String(Cow::Borrowed("")) + } + + /// For quick things + #[must_use] + pub fn is_equal_to(&self, key: &str) -> bool { + match self { + PropertyKey::String(s) => s == key, + PropertyKey::Type(_t) => false, + } + } + + /// TODO when is this used + pub fn into_type(&self, types: &mut TypeStore) -> TypeId { + match self { + PropertyKey::String(s) => { + types.new_constant_type(Constant::String(s.clone().into_owned())) + } + PropertyKey::Type(t) => *t, + } + } + + pub fn into_name_type(&self, types: &mut TypeStore) -> TypeId { + match self { + PropertyKey::String(s) => { + types.new_constant_type(Constant::String(s.clone().into_owned())) + } + PropertyKey::Type(t) => { + crate::utilities::notify!("TODO Symbol has different printing here"); + *t + } + } + } + + pub(crate) fn substitute( + &self, + type_arguments: &super::SubstitutionArguments, + top_environment: &Environment, + types: &mut TypeStore, + ) -> Self { + match self { + PropertyKey::Type(under) => { + let ty = super::substitute(*under, type_arguments, top_environment, types); + PropertyKey::from_type(ty, types) + } + under @ PropertyKey::String(_) => under.clone(), + } + } +} + +// WIP quick hack for static property keys under < 10 +static NUMBERS: &str = "0123456789"; + +impl<'a> PropertyKey<'a> { + /// For small array indexes + #[must_use] + pub fn from_usize(a: usize) -> Self { + if a < 10 { + Self::String(Cow::Borrowed(&NUMBERS[a..=a])) + } else { + Self::String(Cow::Owned(a.to_string())) + } + } +} + +/// TODO getter, setter need a closure id (or implement using `Callable::Type`) +#[derive(Clone, Debug, binary_serialize_derive::BinarySerializable)] +pub enum PropertyValue { + Value(TypeId), + /// These need to be [`Callable`], because the getter can be a type via `Object.defineProperty` + Getter(Callable), + /// These need to be [`Callable`], because the getter can be a type via `Object.defineProperty` + Setter(Callable), + /// These need to be [`Callable`], because the getter can be a type via `Object.defineProperty` + GetterAndSetter { + getter: Callable, + setter: Callable, + }, + /// TODO this could have a bit more information. Whether it came from a `delete` or from TS Optional annotation + Deleted, + /// This is the solution for ors. Can be a getter added, a value removed. + ConditionallyExists { + /// If `condition == TypeId::TRUE` then truthy is the value + condition: TypeId, + /// Should be always Value | Getter | Setter | Deleted + truthy: Box, + }, + /// TODO while this works it does mean that there can be long linked lists via Box (I think) + Configured { + /// This points to the previous. Can be conditionally via [`PropertyValue::ConditionallyExists`] + on: Box, + descriptor: Descriptor, + }, +} + +/// TODO link to MDN +#[derive(Copy, Clone, Debug, binary_serialize_derive::BinarySerializable)] +pub struct Descriptor { + pub writable: TypeId, + pub enumerable: TypeId, + pub configurable: TypeId, +} + +// The default descriptor for 'a' for `{ a: 2 }` +impl Default for Descriptor { + fn default() -> Self { + Self { writable: TypeId::TRUE, enumerable: TypeId::TRUE, configurable: TypeId::TRUE } + } +} + +impl PropertyValue { + #[must_use] + pub fn as_get_type(&self, types: &TypeStore) -> TypeId { + match self { + PropertyValue::Value(value) => *value, + PropertyValue::GetterAndSetter { getter, setter: _ } + | PropertyValue::Getter(getter) => { + crate::utilities::notify!("Here. Should pass down types"); + getter.get_return_type(types) + } + // TODO unsure about these two + PropertyValue::Setter(_) => TypeId::UNDEFINED_TYPE, + PropertyValue::Deleted => TypeId::NEVER_TYPE, + PropertyValue::ConditionallyExists { truthy, .. } => { + // TODO temp + truthy.as_get_type(types) + } + PropertyValue::Configured { on, .. } => on.as_get_type(types), + } + } + + #[must_use] + pub fn as_set_type(&self, types: &TypeStore) -> TypeId { + match self { + PropertyValue::Value(value) => *value, + PropertyValue::Getter(_) => TypeId::UNDEFINED_TYPE, + PropertyValue::GetterAndSetter { getter: _, setter } + | PropertyValue::Setter(setter) => { + crate::utilities::notify!("Here. Should pass down types"); + setter.get_first_argument(types) + } + // TODO unsure about these two + PropertyValue::Deleted => TypeId::NEVER_TYPE, + PropertyValue::ConditionallyExists { truthy, .. } => { + // TODO temp + truthy.as_set_type(types) + } + PropertyValue::Configured { on, .. } => on.as_set_type(types), + } + } + + // For printing and debugging + pub fn inner_simple(&self) -> &Self { + if let PropertyValue::ConditionallyExists { truthy: on, .. } + | PropertyValue::Configured { on, descriptor: _ } = self + { + on.inner_simple() + } else { + self + } + } + + // For printing and debugging + pub fn is_optional_simple(&self) -> bool { + if let PropertyValue::ConditionallyExists { condition, truthy: _ } = self { + // crate::utilities::notify!("condition={:?}", *condition); + !matches!(*condition, TypeId::NON_OPTIONAL_KEY_ARGUMENT) + } else { + false + } + } + + // For printing and debugging + pub fn is_writable_simple(&self) -> bool { + if let PropertyValue::ConditionallyExists { condition: _, truthy } = self { + truthy.is_writable_simple() + } else if let PropertyValue::Configured { on: _, descriptor } = self { + !matches!(descriptor.writable, TypeId::TRUE | TypeId::WRITABLE_KEY_ARGUMENT) + } else { + false + } + } + + pub fn is_configuable_simple(&self) -> bool { + if let PropertyValue::ConditionallyExists { condition: _, truthy } = self { + truthy.is_configuable_simple() + } else if let PropertyValue::Configured { on: _, descriptor } = self { + descriptor.configurable == TypeId::TRUE + } else { + true + } + } +} + +/// Does lhs equal want. Aka is `want_key in { [lhs_key]: ... }` +/// +/// This is very much like [`type_is_subtype`] but for [`PropertyKey`] +pub(crate) fn key_matches( + (key, key_type_arguments): (&PropertyKey<'_>, GenericChain), + (want, want_type_arguments): (&PropertyKey<'_>, GenericChain), + info_chain: &impl InformationChain, + types: &TypeStore, +) -> (bool, SliceArguments) { + { + // crate::utilities::notify!( + // "{{ [{:?}({:?})]: ... }}[{:?}({:?})]", + // key, + // key_type_arguments, + // want, + // want_type_arguments + // ); + } + + match (key, want) { + (PropertyKey::String(left), PropertyKey::String(right)) => { + let matches = if let Some(transform) = + key_type_arguments.and_then(|a| a.get_string_transform()) + { + super::intrinsics::apply_string_intrinsic(transform, left).as_str() == right + } else { + left == right + }; + (matches, SliceArguments::default()) + } + (PropertyKey::Type(key), PropertyKey::String(s)) => { + // crate::utilities::notify!( + // "Key equality: have {:?} want {:?}", + // (key, key_type_arguments), + // (want, want_type_arguments) + // ); + + if let Some(substituted_key) = + key_type_arguments.and_then(|args| args.get_single_argument(*key)) + { + key_matches( + (&PropertyKey::Type(substituted_key), key_type_arguments), + (want, want_type_arguments), + info_chain, + types, + ) + } else { + let key = *key; + // First some special bases just for property keys + let mut contributions = SliceArguments::default(); + let result = slice_matches_type( + (key, key_type_arguments), + s.as_ref(), + Some(&mut contributions), + info_chain, + types, + true, + ); + (result, contributions) + } + } + (PropertyKey::String(s), PropertyKey::Type(want)) => { + // This is a special branch because it can refer to many properties + if let Some(substituted_key) = + want_type_arguments.and_then(|args| args.get_single_argument(*want)) + { + key_matches( + (key, key_type_arguments), + (&PropertyKey::Type(substituted_key), want_type_arguments), + info_chain, + types, + ) + } else { + let want_ty = types.get_type_by_id(*want); + // crate::utilities::notify!("{:?} key_ty={:?}", s, want_ty); + if let Type::Or(lhs, rhs) = want_ty { + // TODO + if let matched @ (true, _) = key_matches( + (key, key_type_arguments), + (&PropertyKey::Type(*lhs), key_type_arguments), + info_chain, + types, + ) { + matched + } else { + key_matches( + (key, key_type_arguments), + (&PropertyKey::Type(*rhs), key_type_arguments), + info_chain, + types, + ) + } + } else if let Type::RootPolyType(PolyNature::MappedGeneric { + extends: to, .. + }) + | Type::AliasTo { to, .. } = want_ty + { + key_matches( + (key, key_type_arguments), + (&PropertyKey::Type(*to), want_type_arguments), + info_chain, + types, + ) + } else if let Type::Constructor(crate::types::Constructor::KeyOf(on)) = want_ty { + let matches = get_properties_on_single_type2( + (*on, want_type_arguments), + types, + info_chain, + TypeId::ANY_TYPE, + ) + .iter() + .all(|(rhs_key, _, _)| { + // TODO what about keys here + key_matches( + (key, key_type_arguments), + (rhs_key, want_type_arguments), + info_chain, + types, + ) + .0 + }); + (matches, Default::default()) + } else if let Type::Constant(c) = want_ty { + // crate::utilities::notify!("{:?}", c); + // TODO + (*s == c.as_js_string(), SliceArguments::default()) + } else if *want == TypeId::NUMBER_TYPE { + (s.parse::().is_ok(), SliceArguments::default()) + } else if *want == TypeId::STRING_TYPE { + // Nuance about symbol here. TODO + (true, SliceArguments::default()) + } else { + (false, SliceArguments::default()) + } + } + } + (PropertyKey::Type(left), PropertyKey::Type(right)) => { + let mut state = crate::types::subtyping::State { + already_checked: Default::default(), + mode: Default::default(), + contributions: Some(Contributions::default()), + object_constraints: Default::default(), + others: SubTypingOptions::default(), + }; + let result = crate::types::subtyping::type_is_subtype( + *left, *right, &mut state, info_chain, types, + ); + + let contributions = state.contributions.unwrap(); + crate::utilities::notify!( + "Here contributions {:?}", + &contributions.staging_contravariant + ); + + (result.is_subtype(), contributions.staging_contravariant) + } + } +} + +pub fn get_property_as_string( + property: &PropertyKey, + types: &TypeStore, + environment: &Environment, +) -> String { + match property { + PropertyKey::String(s) => s.to_string(), + PropertyKey::Type(t) => crate::types::printing::print_type(*t, types, environment, false), + } +} diff --git a/checker/src/types/store.rs b/checker/src/types/store.rs index 867ab293..b0684766 100644 --- a/checker/src/types/store.rs +++ b/checker/src/types/store.rs @@ -1,15 +1,15 @@ use std::collections::{HashMap, HashSet}; -use crate::Map as SmallMap; -use source_map::SpanWithSource; +use crate::{types::intrinsics::Intrinsic, Constant, Map as SmallMap}; +use source_map::{Nullable, SpanWithSource}; use crate::{ - context::Logical, - features::{ - functions::{ClosureId, FunctionBehavior}, - objects::SpecialObjects, + features::{functions::ClosureId, objects::SpecialObject}, + types::{ + functions::{FunctionBehavior, FunctionType}, + logical::{Logical, LogicalOrValid}, + PolyNature, Type, }, - types::{FunctionType, PolyNature, Type}, Environment, FunctionId, TypeId, }; @@ -27,23 +27,11 @@ pub struct TypeStore { /// Some types are prototypes but have generic parameters but pub(crate) lookup_generic_map: HashMap, - /// Set after the interface [`Type`] is created, so here - /// TODO private - pub(crate) interface_extends: HashMap, - - /// Set after the interface [`Type`] is created, so here - interface_type_parameter_extends: HashMap, - /// Contains all the function types /// /// TODO is there a faster alternative to a [`HashMap`] like how [`Type`]s are stored in a [`Vec`] pub(crate) functions: HashMap, - // TODO - pub(crate) _dependent_dependencies: HashMap>, - // TODO - pub(crate) _specialisations: HashMap>, - /// can be used for tree shaking pub called_functions: HashSet, @@ -57,36 +45,39 @@ impl Default for TypeStore { let types = vec![ // TODO will `TypeId::ANY_TYPE` cause any problems Type::RootPolyType(PolyNature::Error(TypeId::ANY_TYPE)), - Type::Interface { name: "never".to_owned(), parameters: None, nominal: true }, - Type::Interface { name: "any".to_owned(), parameters: None, nominal: true }, - Type::Class { name: "boolean".to_owned(), parameters: None }, - Type::Class { name: "number".to_owned(), parameters: None }, - Type::Class { name: "string".to_owned(), parameters: None }, - Type::Constant(crate::Constant::Undefined), - Type::Constant(crate::Constant::Null), + Type::Interface { name: "never".to_owned(), parameters: None, extends: None }, + Type::Interface { name: "any".to_owned(), parameters: None, extends: None }, + Type::Class { name: "boolean".to_owned(), type_parameters: None }, + Type::Class { name: "number".to_owned(), type_parameters: None }, + Type::Class { name: "string".to_owned(), type_parameters: None }, + // sure? + Type::Interface { name: "undefined".to_owned(), parameters: None, extends: None }, + Type::SpecialObject(SpecialObject::Null), // `void` type. Has special subtyping in returns Type::AliasTo { to: TypeId::UNDEFINED_TYPE, name: "void".into(), parameters: None }, - Type::Class { name: "Array".to_owned(), parameters: Some(vec![TypeId::T_TYPE]) }, - Type::Class { name: "Promise".to_owned(), parameters: Some(vec![TypeId::T_TYPE]) }, + Type::Class { name: "Array".to_owned(), type_parameters: Some(vec![TypeId::T_TYPE]) }, + Type::Class { name: "Promise".to_owned(), type_parameters: Some(vec![TypeId::T_TYPE]) }, // Array and Promise type parameter. Simplifies things Type::RootPolyType(PolyNature::StructureGeneric { name: "T".into(), - constrained: false, + extends: TypeId::ANY_TYPE, }), - Type::Interface { name: "object".to_owned(), parameters: None, nominal: false }, - Type::Class { name: "Function".to_owned(), parameters: None }, - Type::Class { name: "RegExp".to_owned(), parameters: None }, + Type::Interface { name: "object".to_owned(), parameters: None, extends: None }, + Type::Class { name: "Function".to_owned(), type_parameters: None }, + Type::Class { name: "RegExp".to_owned(), type_parameters: None }, Type::Or(TypeId::STRING_TYPE, TypeId::NUMBER_TYPE), // true - Type::Constant(crate::Constant::Boolean(true)), + Type::Constant(Constant::Boolean(true)), // false - Type::Constant(crate::Constant::Boolean(false)), + Type::Constant(Constant::Boolean(false)), // zero - Type::Constant(crate::Constant::Number(0.into())), + Type::Constant(Constant::Number(0.into())), // one - Type::Constant(crate::Constant::Number(1.into())), + Type::Constant(Constant::Number(1.into())), // NaN - Type::Constant(crate::Constant::NaN), + Type::Constant(Constant::NaN), + // "" + Type::Constant(Constant::String(String::new())), // inferred this free variable shortcut Type::RootPolyType(PolyNature::FreeVariable { reference: crate::events::RootReference::This, @@ -95,14 +86,84 @@ impl Default for TypeStore { Type::RootPolyType(PolyNature::FunctionGeneric { name: "new.target".to_owned(), // TODO - eager_fixed: TypeId::ANY_TYPE, + extends: TypeId::ANY_TYPE, + }), + Type::Interface { name: "ImportMeta".to_owned(), parameters: None, extends: None }, + Type::Constant(Constant::Symbol { key: "iterator".to_owned() }), + Type::Constant(Constant::Symbol { key: "asyncIterator".to_owned() }), + Type::Constant(Constant::Symbol { key: "hasInstance".to_owned() }), + Type::Constant(Constant::Symbol { key: "toPrimitive".to_owned() }), + Type::RootPolyType(PolyNature::StructureGeneric { + name: "S".into(), + extends: TypeId::STRING_TYPE, }), - // TODO Symbols, needs Constant::Symbol Type::AliasTo { - name: "SymbolToPrimitive".into(), - to: TypeId::ANY_TYPE, - parameters: None, + to: TypeId::STRING_TYPE, + name: "Uppercase".into(), + parameters: Some(vec![TypeId::STRING_GENERIC]), + }, + Type::AliasTo { + to: TypeId::STRING_TYPE, + name: "Lowercase".into(), + parameters: Some(vec![TypeId::STRING_GENERIC]), + }, + Type::AliasTo { + to: TypeId::STRING_TYPE, + name: "Capitalize".into(), + parameters: Some(vec![TypeId::STRING_GENERIC]), + }, + Type::AliasTo { + to: TypeId::STRING_TYPE, + name: "Uncapitalize".into(), + parameters: Some(vec![TypeId::STRING_GENERIC]), + }, + // Yeah + Type::AliasTo { + to: TypeId::T_TYPE, + name: "NoInfer".into(), + parameters: Some(vec![TypeId::T_TYPE]), + }, + Type::AliasTo { + name: "Readonly".into(), + to: TypeId::T_TYPE, + parameters: Some(vec![TypeId::T_TYPE]), + }, + Type::RootPolyType(PolyNature::MappedGeneric { + name: "NonOptional".into(), + extends: TypeId::BOOLEAN_TYPE, + }), + Type::RootPolyType(PolyNature::MappedGeneric { + name: "Writable".into(), + extends: TypeId::BOOLEAN_TYPE, + }), + Type::RootPolyType(PolyNature::StructureGeneric { + name: "T".into(), + extends: TypeId::NUMBER_TYPE, + }), + Type::AliasTo { + to: TypeId::NUMBER_TYPE, + name: "LessThan".into(), + parameters: Some(vec![TypeId::NUMBER_GENERIC]), }, + Type::AliasTo { + to: TypeId::NUMBER_TYPE, + name: "GreaterThan".into(), + parameters: Some(vec![TypeId::NUMBER_GENERIC]), + }, + Type::AliasTo { + to: TypeId::NUMBER_TYPE, + name: "MultipleOf".into(), + parameters: Some(vec![TypeId::NUMBER_GENERIC]), + }, + // Intermediate for the below + Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::NOT_RESTRICTION, + arguments: GenericArguments::ExplicitRestrictions(crate::Map::from_iter([( + TypeId::T_TYPE, + (TypeId::NAN, SpanWithSource::NULL), + )])), + }), + Type::And(TypeId::NUMBER_TYPE, TypeId::NOT_NOT_A_NUMBER), // TODO WIP Type::AliasTo { name: "Literal".into(), @@ -110,11 +171,20 @@ impl Default for TypeStore { parameters: Some(vec![TypeId::T_TYPE]), }, Type::AliasTo { - name: "Readonly".into(), + name: "Exclusive".into(), to: TypeId::T_TYPE, parameters: Some(vec![TypeId::T_TYPE]), }, - Type::Interface { name: "ImportMeta".to_owned(), parameters: None, nominal: false }, + Type::AliasTo { + name: "Not".into(), + to: TypeId::ANY_TYPE, + parameters: Some(vec![TypeId::T_TYPE]), + }, + Type::AliasTo { + name: "CaseInsensitive".into(), + to: TypeId::STRING_TYPE, + parameters: Some(vec![TypeId::STRING_GENERIC]), + }, ]; // Check that above is correct, TODO eventually a macro @@ -128,33 +198,32 @@ impl Default for TypeStore { Self { types, lookup_generic_map, - functions: HashMap::new(), - _dependent_dependencies: Default::default(), - _specialisations: Default::default(), + functions: Default::default(), called_functions: Default::default(), closure_counter: 0, - interface_extends: Default::default(), - interface_type_parameter_extends: Default::default(), } } } impl TypeStore { - pub fn new_constant_type(&mut self, constant: crate::Constant) -> crate::TypeId { + pub fn count_of_types(&self) -> usize { + self.types.len() + } + + pub fn new_constant_type(&mut self, constant: Constant) -> crate::TypeId { // Reuse existing ids rather than creating new types sometimes match constant { - crate::Constant::Number(number) if number == 1f64 => TypeId::ONE, - crate::Constant::Number(number) if number == 0f64 => TypeId::ZERO, - crate::Constant::Boolean(value) => { + Constant::String(s) if s.is_empty() => TypeId::EMPTY_STRING, + Constant::Number(number) if number == 0f64 => TypeId::ZERO, + Constant::Number(number) if number == 1f64 => TypeId::ONE, + Constant::Boolean(value) => { if value { TypeId::TRUE } else { TypeId::FALSE } } - crate::Constant::Undefined => TypeId::UNDEFINED_TYPE, - crate::Constant::Null => TypeId::NULL_TYPE, - crate::Constant::NaN => TypeId::NAN_TYPE, + Constant::NaN => TypeId::NAN, _ => { let ty = Type::Constant(constant); // TODO maybe separate id @@ -163,7 +232,8 @@ impl TypeStore { } } - pub(crate) fn register_type(&mut self, ty: Type) -> TypeId { + // pub(crate) fn register_type(&mut self, ty: Type) -> TypeId { + pub fn register_type(&mut self, ty: Type) -> TypeId { let id = TypeId(self.types.len().try_into().expect("too many types!")); self.types.push(ty); id @@ -180,7 +250,7 @@ impl TypeStore { } if let (TypeId::TRUE, TypeId::FALSE) | (TypeId::FALSE, TypeId::TRUE) = (lhs, rhs) { - return TypeId::BOOLEAN_TYPE; + return TypeId::OPEN_BOOLEAN_TYPE; } if let TypeId::NEVER_TYPE = lhs { return rhs; @@ -270,21 +340,22 @@ impl TypeStore { self.new_conditional_type(on, true_result, false_result) } + #[allow(clippy::if_same_then_else)] pub fn new_conditional_type( &mut self, condition: TypeId, truthy_result: TypeId, otherwise_result: TypeId, ) -> TypeId { - // TODO raise warning + // TODO raise warning for the first 4 branches if truthy_result == otherwise_result { - return truthy_result; - } - - // TODO reverse as well - if truthy_result == TypeId::TRUE && otherwise_result == TypeId::FALSE { + truthy_result + } else if condition == TypeId::TRUE { + truthy_result + } else if condition == TypeId::FALSE { + otherwise_result + } else if truthy_result == TypeId::TRUE && otherwise_result == TypeId::FALSE { condition - // self.new_logical_or_type(condition, otherwise_result) } else { // TODO on is negation then swap operands let ty = Type::Constructor(super::Constructor::ConditionalResult { @@ -311,6 +382,16 @@ impl TypeStore { self.register_type(ty) } + /// Doesn't evaluate events + pub(crate) fn new_logical_and_type(&mut self, left: TypeId, right: TypeId) -> TypeId { + self.new_conditional_type(left, right, TypeId::FALSE) + } + + /// Doesn't evaluate events + pub(crate) fn new_logical_or_type(&mut self, left: TypeId, right: TypeId) -> TypeId { + self.new_conditional_type(left, TypeId::TRUE, right) + } + pub fn new_closure_id(&mut self) -> ClosureId { self.closure_counter += 1; ClosureId(self.closure_counter) @@ -324,7 +405,7 @@ impl TypeStore { pub fn new_function_type(&mut self, function_type: FunctionType) -> TypeId { let id = function_type.id; self.functions.insert(id, function_type); - self.register_type(Type::SpecialObject(SpecialObjects::Function(id, Default::default()))) + self.register_type(Type::SpecialObject(SpecialObject::Function(id, Default::default()))) } pub fn new_hoisted_function_type(&mut self, function_type: FunctionType) -> TypeId { @@ -341,47 +422,47 @@ impl TypeStore { indexer: TypeId, environment: &Environment, ) -> TypeId { - if let Some(base) = get_constraint(indexee, self) { + use super::properties::{get_property_unbound, AccessMode, Publicity}; + if get_constraint(indexee, self).is_some() { let under = PropertyKey::from_type(indexer, self); let ty = Type::Constructor(Constructor::Property { on: indexee, under, - result: base, - bind_this: true, + result: TypeId::ANY_TYPE, + mode: AccessMode::Regular, }); self.register_type(ty) - } else if let Ok(prop) = super::properties::get_property_unbound( - (indexee, None), - ( - crate::types::properties::Publicity::Public, - &PropertyKey::from_type(indexer, self), - None, - ), - environment, - self, - ) { - match prop { - Logical::Pure(ty) => ty.as_get_type(), - Logical::Or { .. } => todo!(), - Logical::Implies { .. } => todo!(), - } } else { - crate::utilities::notify!("Error: no index on type annotation"); - TypeId::ERROR_TYPE + let result = get_property_unbound( + (indexee, None), + (Publicity::Public, &PropertyKey::from_type(indexer, self), None), + false, + environment, + self, + ); + if let Ok(prop) = result { + match prop { + LogicalOrValid::Logical(Logical::Pure(ty)) => ty.as_get_type(self), + value => { + crate::utilities::notify!("value={:?}", value); + TypeId::ERROR_TYPE + } // Logical::Or { .. } => todo!(), + // Logical::Implies { .. } => todo!(), + // Logical::BasedOnKey { .. } => todo!(), + } + } else { + crate::utilities::notify!("Error: no index on type annotation"); + TypeId::ERROR_TYPE + } } } - /// TODO flags - pub fn new_regex(&mut self, pattern: String) -> TypeId { - self.register_type(Type::SpecialObject( - crate::features::objects::SpecialObjects::RegularExpression(pattern), - )) - } - pub fn new_function_parameter(&mut self, parameter_constraint: TypeId) -> TypeId { // TODO this has problems if there are two generic types. Aka `(a: T, b: T) -> T`. Although I have // no idea why this is possible so should be fine? - if let Type::RootPolyType(_) = self.get_type_by_id(parameter_constraint) { + if let Type::RootPolyType(PolyNature::FunctionGeneric { .. }) = + self.get_type_by_id(parameter_constraint) + { parameter_constraint } else { self.register_type(Type::RootPolyType(crate::types::PolyNature::Parameter { @@ -415,12 +496,18 @@ impl TypeStore { } /// *Dangerous* type modifying types. TODO this might be modified in the future + /// TODO check disjoint pub(crate) fn modify_interface_type_parameter_constraint( &mut self, ty: TypeId, constraint: TypeId, ) { - self.interface_type_parameter_extends.insert(ty, constraint); + let ty = &mut self.types[ty.0 as usize]; + if let Type::RootPolyType(PolyNature::StructureGeneric { extends, .. }) = ty { + *extends = constraint; + } else { + todo!("{:?}", ty) + } } /// *Dangerous* . TODO WIP @@ -436,22 +523,36 @@ impl TypeStore { /// *Dangerous* type modifying types. TODO this might be modified in the future pub(crate) fn set_extends_on_interface(&mut self, interface_type: TypeId, extends: TypeId) { - self.interface_extends.insert(interface_type, extends); + if let Type::Interface { extends: Some(ref mut old), .. } = + self.types[interface_type.0 as usize] + { + *old = extends; + } } - pub(crate) fn new_class_constructor_type( - &mut self, - name: String, - constructor: FunctionType, - constructs: TypeId, - ) -> TypeId { + /// *Dangerous* type modifying types. TODO this might be modified in the future + pub(crate) fn update_alias(&mut self, alias_type: TypeId, to: TypeId) { + if let Type::AliasTo { to: ref mut old, .. } = self.types[alias_type.0 as usize] { + *old = to; + } + } + + /// *Dangerous* type modifying types. TODO this might be modified in the future + pub(crate) fn update_generic_extends(&mut self, generic: TypeId, to: TypeId) { + if let Type::RootPolyType(PolyNature::StructureGeneric { ref mut extends, .. }) = + self.types[generic.0 as usize] + { + *extends = to; + } + } + + pub(crate) fn new_class_constructor_type(&mut self, constructor: FunctionType) -> TypeId { let id = constructor.id; self.functions.insert(id, constructor); - self.register_type(Type::SpecialObject(SpecialObjects::ClassConstructor { - name, - constructor: id, - prototype: constructs, - })) + self.register_type(Type::SpecialObject(SpecialObject::Function( + id, + crate::types::calling::ThisValue::UseParent, + ))) } pub(crate) fn create_this_object(&mut self) -> TypeId { @@ -461,4 +562,30 @@ impl TypeStore { pub(crate) fn new_key_of(&mut self, of: TypeId) -> TypeId { self.register_type(Type::Constructor(Constructor::KeyOf(of))) } + + pub(crate) fn new_intrinsic(&mut self, intrinsic: &Intrinsic, argument: TypeId) -> TypeId { + let (on, to_pair) = match intrinsic { + Intrinsic::Uppercase => (TypeId::STRING_UPPERCASE, TypeId::STRING_GENERIC), + Intrinsic::Lowercase => (TypeId::STRING_LOWERCASE, TypeId::STRING_GENERIC), + Intrinsic::Capitalize => (TypeId::STRING_CAPITALIZE, TypeId::STRING_GENERIC), + Intrinsic::Uncapitalize => (TypeId::STRING_UNCAPITALIZE, TypeId::STRING_GENERIC), + Intrinsic::NoInfer => (TypeId::NO_INFER, TypeId::T_TYPE), + Intrinsic::Literal => (TypeId::LITERAL_RESTRICTION, TypeId::T_TYPE), + Intrinsic::LessThan => (TypeId::LESS_THAN, TypeId::NUMBER_GENERIC), + Intrinsic::GreaterThan => (TypeId::GREATER_THAN, TypeId::NUMBER_GENERIC), + Intrinsic::MultipleOf => (TypeId::MULTIPLE_OF, TypeId::NUMBER_GENERIC), + Intrinsic::Exclusive => (TypeId::EXCLUSIVE_RESTRICTION, TypeId::T_TYPE), + Intrinsic::Not => (TypeId::NOT_RESTRICTION, TypeId::T_TYPE), + Intrinsic::CaseInsensitive => (TypeId::CASE_INSENSITIVE, TypeId::STRING_GENERIC), + }; + let arguments = GenericArguments::ExplicitRestrictions(crate::Map::from_iter([( + to_pair, + (argument, ::NULL), + )])); + + self.register_type(Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on, + arguments, + })) + } } diff --git a/checker/src/types/subtyping.rs b/checker/src/types/subtyping.rs index ee50bcbc..adcabe37 100644 --- a/checker/src/types/subtyping.rs +++ b/checker/src/types/subtyping.rs @@ -1,22 +1,30 @@ //! Type subtype checking. (making sure the RHS type contains all the properties the LHS type requires) -use source_map::{Nullable, SpanWithSource}; +use source_map::SpanWithSource; use crate::{ - context::{information::InformationChain, Environment, GeneralContext, Logical}, - features::objects::SpecialObjects, + context::{GeneralContext, InformationChain}, + features::{ + objects::{self, SpecialObject}, + operations::MathematicalAndBitwise, + }, types::{ - generics::{contributions::Contributions, generic_type_arguments::GenericArguments}, + generics::{ + chain::{GenericChain, GenericChainLink, SpecialGenericChainLink}, + contributions::{ContributionDepth, Contributions, CovariantContribution, TriMap}, + generic_type_arguments::GenericArguments, + }, + intrinsics::apply_string_intrinsic, + logical::{BasedOnKey, Logical, LogicalOrValid, NeedsCalculation, PropertyOn}, printing::print_type, - properties::{get_property_unbound, key_matches, Publicity}, - GenericChainLink, ObjectNature, TypeStore, + properties::{get_properties_on_single_type2, get_property_unbound, Publicity}, + ObjectNature, Type, TypeStore, }, - Constant, PropertyValue, TypeId, + Constant, Environment, PropertyValue, TypeId, }; use super::{ - get_constraint, properties::PropertyKey, Constructor, GenericChain, PartiallyAppliedGenerics, - PolyNature, Type, + get_constraint, properties::PropertyKey, Constructor, PartiallyAppliedGenerics, PolyNature, }; pub use super::{NonEqualityReason, PropertyError}; @@ -40,7 +48,6 @@ impl SubTypeResult { } } -/// TODO document which one is which #[derive(Clone, Copy)] pub enum SubTypingMode { /// *output* @@ -137,7 +144,7 @@ pub fn type_is_subtype_object( let result = type_is_subtype(base_type, ty, &mut state, environment, types); environment.add_object_constraints(state.object_constraints.unwrap().into_iter(), types); - // TODO environment.add_inferred_constraints(x, types); + // TODO information.add_inferred_constraints(x, types); result } @@ -150,14 +157,14 @@ pub fn type_is_subtype( base_type: TypeId, ty: TypeId, state: &mut State, - environment: &Environment, + information: &impl InformationChain, types: &TypeStore, ) -> SubTypeResult { type_is_subtype_with_generics( (base_type, GenericChain::None), (ty, GenericChain::None), state, - environment, + information, types, ) } @@ -198,32 +205,34 @@ impl<'a> State<'a> { let [already_checked, contributions_covariant, contributions_contravariant, object_constraint_count] = last; - self.already_checked.drain((already_checked as usize)..); + let _ = self.already_checked.drain((already_checked as usize)..); if let Some(ref mut contributions) = self.contributions { - contributions.staging_covariant.drop_range((contributions_covariant as usize)..); - contributions + let _ = + contributions.staging_covariant.drop_range((contributions_covariant as usize)..); + let _ = contributions .staging_contravariant .drop_range((contributions_contravariant as usize)..); } if let Some(ref mut object_constraints) = self.object_constraints { - object_constraints.drain((object_constraint_count as usize)..); + let _ = object_constraints.drain((object_constraint_count as usize)..); } } } pub(crate) fn type_is_subtype_with_generics( - (base_type, base_structure_arguments): (TypeId, GenericChain), + (base_type, base_type_arguments): (TypeId, GenericChain), (ty, ty_structure_arguments): (TypeId, GenericChain), state: &mut State, - environment: &Environment, + information: &impl InformationChain, types: &TypeStore, ) -> SubTypeResult { { let debug = true; crate::utilities::notify!( - "Checking {} :>= {}", - print_type(base_type, types, environment, debug), - print_type(ty, types, environment, debug) + "Checking {} :>= {}, with {:?}", + print_type(base_type, types, information, debug), + print_type(ty, types, information, debug), + base_type_arguments ); } @@ -238,6 +247,7 @@ pub(crate) fn type_is_subtype_with_generics( { // Prevents cycles if state.already_checked.iter().any(|(a, b)| *a == base_type && *b == ty) { + crate::utilities::notify!("Subtyping recursion"); return SubTypeResult::IsSubType; } @@ -249,23 +259,32 @@ pub(crate) fn type_is_subtype_with_generics( // Eager things match right_ty { + Type::AliasTo { to: right, .. } => { + return type_is_subtype_with_generics( + (base_type, base_type_arguments), + (*right, ty_structure_arguments), + state, + information, + types, + ); + } Type::Or(left, right) => { let right = *right; - crate::utilities::notify!("OR RHS: left and right"); + // crate::utilities::notify!("OR RHS: left and right"); let left_result = type_is_subtype_with_generics( - (base_type, base_structure_arguments), + (base_type, base_type_arguments), (*left, ty_structure_arguments), state, - environment, + information, types, ); return if let SubTypeResult::IsSubType = left_result { type_is_subtype_with_generics( - (base_type, base_structure_arguments), + (base_type, base_type_arguments), (right, ty_structure_arguments), state, - environment, + information, types, ) } else { @@ -274,65 +293,123 @@ pub(crate) fn type_is_subtype_with_generics( }; } // Type::And(left, right) => { + // let left_is_operator_right_is_not = + // left_ty.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_structure_arguments, - // *left, - // ty_structure_arguments, + // (base_type, base_type_arguments), + // (*left, ty_structure_arguments), // state, - // environment, + // information, // types, - // mode, - // already_checked // ); // return if let SubTypeResult::IsSubType = left_result { // left_result // } else { // type_is_subtype_with_generics( - // base_type, - // base_structure_arguments, - // right, - // ty_structure_arguments, + // (base_type, base_type_arguments), + // (right, ty_structure_arguments), // state, - // environment, + // information, // types, - // mode, - // already_checked // ) // }; // } - Type::PartiallyAppliedGenerics(..) => {} + // } + // TODO others + Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: on @ TypeId::NOT_RESTRICTION, + 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 + + // Swapped + let result = super::disjoint::types_are_disjoint( + inner, + ty, + &mut state.already_checked, + information, + types, + ); + // crate::utilities::notify!("Here {:?}", (&result, inner)); + return if result { + SubTypeResult::IsSubType + } else { + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + }; + } + _ => unreachable!(), + } + } t @ (Type::RootPolyType(..) | Type::Constructor(..)) => { if let Type::RootPolyType(PolyNature::Error(to)) = t { // (unless specified) treat as subtype as error would have already been thrown return if state.others.allow_errors && *to == TypeId::ANY_TYPE { SubTypeResult::IsSubType } else { - type_is_subtype(base_type, *to, state, environment, types) + type_is_subtype(base_type, *to, state, information, types) }; } - if let Some(args) = - ty_structure_arguments.and_then(|tas| tas.get_argument(ty, environment, types)) + 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)) { - // TODO what - for arg in args { - let result = type_is_subtype_with_generics( - (base_type, base_structure_arguments), - (arg, ty_structure_arguments), + return match arg { + CovariantContribution::TypeId(ty) => type_is_subtype_with_generics( + (base_type, base_type_arguments), + (ty, ty_structure_arguments), state, - environment, + information, types, - ); - - if let e @ SubTypeResult::IsNotSubType(_) = result { - return e; + ), + CovariantContribution::String(string) => { + let contributions = + state.contributions.as_mut().map(|n| &mut n.staging_contravariant); + let matches = slice_matches_type( + (base_type, base_type_arguments), + &string, + contributions, + information, + types, + false, + ); + if matches { + SubTypeResult::IsSubType + } else { + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + } } - } - - return SubTypeResult::IsSubType; + CovariantContribution::SliceOf(s, (l, r)) => todo!("{:?}", (s, (l, r))), + CovariantContribution::CaseInsensitive(ci) => todo!("{:?}", ci), + CovariantContribution::Number(n) => { + let contributions = + state.contributions.as_mut().map(|n| &mut n.staging_contravariant); + let matches = number_matches_type( + (base_type, base_type_arguments), + n, + contributions, + information, + types, + ); + if matches { + SubTypeResult::IsSubType + } else { + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + } + } + }; } if let SubTypingMode::Covariant { position: _ } = state.mode { @@ -346,6 +423,16 @@ 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(); + + 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() + ) + ); + // This is important that LHS is not operator let left_is_operator_right_is_not = left_ty.is_operator() && !types.get_type_by_id(right_arg).is_operator(); @@ -361,10 +448,10 @@ pub(crate) fn type_is_subtype_with_generics( if !edge_case { let result = type_is_subtype_with_generics( - (base_type, base_structure_arguments), + (base_type, base_type_arguments), (right_arg, ty_structure_arguments), state, - environment, + information, types, ); @@ -380,16 +467,17 @@ pub(crate) fn type_is_subtype_with_generics( }; } } + Type::PartiallyAppliedGenerics(..) => {} _ => (), } match left_ty { Type::FunctionReference(left_func) - | Type::SpecialObject(SpecialObjects::Function(left_func, _)) => subtype_function( - (*left_func, base_structure_arguments), + | Type::SpecialObject(SpecialObject::Function(left_func, _)) => subtype_function( + (*left_func, base_type_arguments), (right_ty, ty, ty_structure_arguments), state, - environment, + information, types, ), Type::Constant(lhs) => { @@ -415,14 +503,14 @@ pub(crate) fn type_is_subtype_with_generics( // assert!(matches!(nature, ObjectNature::AnonymousTypeAnnotation)); subtype_properties( - (base_type, base_structure_arguments), + (base_type, base_type_arguments), (ty, ty_structure_arguments), state, - environment, + information, types, ) - // let _left = print_type(base_type, types, environment, true); + // let _left = print_type(base_type, types, information, true); // crate::utilities::notify!("Left object {}", left); @@ -434,21 +522,21 @@ pub(crate) fn type_is_subtype_with_generics( } Type::And(left, right) => { let right = *right; - crate::utilities::notify!("AND: Checking left and right"); + // crate::utilities::notify!("AND: Checking left and right"); let left_result = type_is_subtype_with_generics( - (*left, base_structure_arguments), + (*left, base_type_arguments), (ty, ty_structure_arguments), state, - environment, + information, types, ); if let SubTypeResult::IsSubType = left_result { type_is_subtype_with_generics( - (right, base_structure_arguments), + (right, base_type_arguments), (ty, ty_structure_arguments), state, - environment, + information, types, ) } else { @@ -461,10 +549,10 @@ pub(crate) fn type_is_subtype_with_generics( let save_point = state.produce_save_point(); let left_result = type_is_subtype_with_generics( - (*left, base_structure_arguments), + (*left, base_type_arguments), (ty, ty_structure_arguments), state, - environment, + information, types, ); @@ -472,10 +560,10 @@ pub(crate) fn type_is_subtype_with_generics( if state.contributions.is_some() { // only for double generics specialisation. Otherwise short-circuiting is fine let _res = type_is_subtype_with_generics( - (right, base_structure_arguments), + (right, base_type_arguments), (ty, ty_structure_arguments), state, - environment, + information, types, ); } @@ -485,10 +573,10 @@ pub(crate) fn type_is_subtype_with_generics( state.reset(save_point); type_is_subtype_with_generics( - (right, base_structure_arguments), + (right, base_type_arguments), (ty, ty_structure_arguments), state, - environment, + information, types, ) } @@ -499,31 +587,55 @@ pub(crate) fn type_is_subtype_with_generics( } // TODO little weird, handing two very different cases beside each other. Might introduce bugs.. :( - let base_argument_for_current = base_structure_arguments - .and_then(|args| args.get_argument(base_type, environment, types)); + let base_arg = + base_type_arguments.and_then(|args| args.get_argument_covariant(base_type)); - if let Some(args) = base_argument_for_current { - // TODO what - for arg in args { - let result = type_is_subtype_with_generics( - (arg, base_structure_arguments), + if let Some(base_arg) = base_arg { + match base_arg { + CovariantContribution::TypeId(base_arg) => type_is_subtype_with_generics( + (base_arg, base_type_arguments), (ty, ty_structure_arguments), state, - environment, + information, types, - ); - - if let e @ SubTypeResult::IsNotSubType(_) = result { - return e; + ), + CovariantContribution::String(left_string) => { + if let Type::Constant(Constant::String(right_string)) = right_ty { + if &left_string == right_string { + SubTypeResult::IsSubType + } else { + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + } + } else { + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + } + } + CovariantContribution::SliceOf(s, (l, r)) => todo!("{:?}", (s, (l, r))), + CovariantContribution::CaseInsensitive(ci) => todo!("{:?}", (ci)), + CovariantContribution::Number(n) => { + unreachable!("{:?}", n) + // crate::utilities::notify!("Here?"); + // if let Type::Constant(Constant::String(right_string)) = right_ty { + // if left_string == right_string { + // SubTypeResult::IsSubType + // } else { + // SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + // } + // } else { + // SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + // } } } - - SubTypeResult::IsSubType } else if let Some(ref mut contributions) = state.contributions { match state.mode { SubTypingMode::Contravariant { depth } => { // With <*base_type* extends *under> check ty is under + crate::utilities::notify!( + "contributions.parent={:?}", + contributions.parent + ); + let result = if let Some(under) = contributions.get_standard_restriction(base_type) { @@ -531,20 +643,17 @@ pub(crate) fn type_is_subtype_with_generics( (under, GenericChain::None), (ty, ty_structure_arguments), state, - environment, + information, types, ) - } else if let Some(constraint) = nature.try_get_constraint() { + } else { type_is_subtype_with_generics( - (constraint, GenericChain::None), + (nature.get_constraint(), GenericChain::None), (ty, ty_structure_arguments), state, - environment, + information, types, ) - } else { - crate::utilities::notify!("TODO no constraint for {:?}", nature); - SubTypeResult::IsSubType }; state @@ -605,15 +714,201 @@ pub(crate) fn type_is_subtype_with_generics( let constraint = get_constraint(base_type, types).unwrap(); type_is_subtype_with_generics( - (constraint, base_structure_arguments), + (constraint, base_type_arguments), (ty, ty_structure_arguments), state, - environment, + information, types, ) } } Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on, arguments }) => { + match *on { + TypeId::READONLY_RESTRICTION => { + crate::utilities::notify!("TODO temp readonly inner check"); + let inner = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap(); + // Some(GenericChainLink::SpecialGenericChainLink { + // parent_link: ty_structure_arguments.as_ref(), + // special: SpecialGenericChainLink::Readonly, + // }) + return if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::READONLY_RESTRICTION, + arguments, + }) = right_ty + { + let ty = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap(); + type_is_subtype_with_generics( + (inner, ty_structure_arguments), + (ty, base_type_arguments), + state, + information, + types, + ) + } else if information.get_chain_of_info().any(|info| info.frozen.contains(&ty)) + || matches!(right_ty, Type::Constant(_)) + || matches!( + ty, + TypeId::STRING_TYPE | TypeId::BOOLEAN_TYPE | TypeId::NUMBER_TYPE + ) { + type_is_subtype_with_generics( + (inner, ty_structure_arguments), + (ty, base_type_arguments), + state, + information, + types, + ) + } else { + // TODO is not readonly + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + }; + } + TypeId::EXCLUSIVE_RESTRICTION => { + let inner = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap(); + return type_is_subtype_with_generics( + ( + inner, + Some(GenericChainLink::SpecialGenericChainLink { + parent_link: ty_structure_arguments.as_ref(), + special: SpecialGenericChainLink::Exclusive, + }), + ), + (ty, base_type_arguments), + state, + information, + types, + ); + } + 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_iff_disjoint_left + + let result = super::disjoint::types_are_disjoint( + ty, + inner, + &mut state.already_checked, + information, + types, + ); + // crate::utilities::notify!("Here {:?}", (&result, inner)); + return if result { + SubTypeResult::IsSubType + } else { + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + }; + } + // TSC intrinsics + TypeId::STRING_CAPITALIZE + | TypeId::STRING_UNCAPITALIZE + | TypeId::STRING_LOWERCASE + | TypeId::STRING_UPPERCASE => { + if let Type::Constant(Constant::String(rs)) = right_ty { + let contributions = + state.contributions.as_mut().map(|n| &mut n.staging_contravariant); + let matches = slice_matches_type( + (base_type, base_type_arguments), + rs, + contributions, + information, + types, + false, + ); + return if matches { + SubTypeResult::IsSubType + } else { + // TODO remove contributions + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + }; + } + } + // Ezno intrinsic + TypeId::LITERAL_RESTRICTION => { + let inner = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap(); + return if let Type::Constant(rhs_constant) = right_ty { + type_is_subtype_with_generics( + (inner, base_type_arguments), + (rhs_constant.get_backing_type_id(), ty_structure_arguments), + state, + information, + types, + ) + } else { + // TODO what about if the rhs == TypeId::CONSTANT_RESTRICTION + // TODO non-constant error + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + }; + } + TypeId::NO_INFER => { + let on = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap(); + let current_contributing = + state.contributions.as_ref().map(|c| c.staging_contravariant.len()); + let result = type_is_subtype_with_generics( + (on, base_type_arguments), + (ty, ty_structure_arguments), + state, + information, + types, + ); + // Drop any infer-ed results + if let (Some(contributions), Some(current_contributing)) = + (state.contributions.as_mut(), current_contributing) + { + let _ = + contributions.staging_contravariant.drop_range(current_contributing..); + } + return result; + } + TypeId::LESS_THAN | TypeId::GREATER_THAN | TypeId::MULTIPLE_OF => { + let argument = + arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(); + return if let ( + Type::Constant(Constant::Number(argument)), + Type::Constant(Constant::Number(value)), + ) = (types.get_type_by_id(argument), right_ty) + { + let result = match *on { + TypeId::LESS_THAN => value < argument, + TypeId::GREATER_THAN => value > argument, + TypeId::MULTIPLE_OF => value % argument == 0f64, + _ => unreachable!(), + }; + if result { + SubTypeResult::IsSubType + } else { + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + } + } else { + crate::utilities::notify!( + "Returning NonEqualityReason::Mismatch {:?}", + right_ty + ); + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + }; + } + TypeId::CASE_INSENSITIVE => { + if let Type::Constant(Constant::String(rs)) = right_ty { + let contributions = + state.contributions.as_mut().map(|n| &mut n.staging_contravariant); + // Slice matches handles this + let matches = slice_matches_type( + (base_type, base_type_arguments), + rs, + contributions, + information, + types, + false, + ); + + return if matches { + SubTypeResult::IsSubType + } else { + // TODO remove contributions + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + }; + } + } + _ => {} + } + if let Some(lookup) = types.lookup_generic_map.get(on) { fn get_structure_generics_on( r#type: &Type, @@ -642,10 +937,10 @@ pub(crate) fn type_is_subtype_with_generics( for (lk, (lv, _)) in left.iter() { let (rv, _) = right.get(lk).unwrap(); let argument_is_subtype = type_is_subtype_with_generics( - (*lv, base_structure_arguments), + (*lv, base_type_arguments), (*rv, ty_structure_arguments), state, - environment, + information, types, ); if let err @ SubTypeResult::IsNotSubType(_) = argument_is_subtype { @@ -658,7 +953,7 @@ pub(crate) fn type_is_subtype_with_generics( } } else if let Type::Object(super::ObjectNature::RealDeal) = right_ty { let prototype = - environment.get_chain_of_info().find_map(|info| info.prototypes.get(&ty)); + information.get_chain_of_info().find_map(|info| info.prototypes.get(&ty)); crate::utilities::notify!("prototype is {:?}", prototype); @@ -667,9 +962,9 @@ pub(crate) fn type_is_subtype_with_generics( // TODO no vec let backing_type = arguments.get_structure_restriction(*argument).unwrap(); - for value in lookup.calculate_lookup(environment, ty) { + for value in lookup.calculate_lookup(information, types, ty) { let type_is_subtype = - type_is_subtype(backing_type, value, state, environment, types); + type_is_subtype(backing_type, value, state, information, types); if let e @ SubTypeResult::IsNotSubType(_) = type_is_subtype { return e; } @@ -691,7 +986,7 @@ pub(crate) fn type_is_subtype_with_generics( crate::utilities::notify!( "Array type is {}", - print_type(backing_type, types, environment, false) + print_type(backing_type, types, information, false) ); // TODO temp fix for general parameters @@ -704,7 +999,7 @@ pub(crate) fn type_is_subtype_with_generics( // }; // TODO don't create vec - // for value in lookup_restriction.calculate_lookup(environment) { + // for value in lookup_restriction.calculate_lookup(information) { // } @@ -726,10 +1021,10 @@ pub(crate) fn type_is_subtype_with_generics( // TODO unsure about arguments here type_is_subtype_with_generics( - (left_arg, base_structure_arguments), + (left_arg, base_type_arguments), (right_arg, ty_structure_arguments), state, - environment, + information, types, ) } else { @@ -737,39 +1032,71 @@ pub(crate) fn type_is_subtype_with_generics( SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) } } else { - let into = arguments.clone(); + fn is_arguments_cyclic(a: &GenericArguments) -> bool { + if let GenericArguments::ExplicitRestrictions(arguments) = a { + arguments.iter().any(|(left, (right, _))| left == right) + } else { + false + } + } + // TODO temp fix + if is_arguments_cyclic(arguments) { + let GenericArguments::ExplicitRestrictions(arguments) = arguments else { + unreachable!(); + }; - let base_type_arguments = - GenericChainLink::append(base_type, base_structure_arguments.as_ref(), &into); + let filtered: crate::Map<_, _> = arguments + .iter() + .filter(|(left, (right, _))| left != right) + .copied() + .collect(); + let refe = GenericArguments::ExplicitRestrictions(filtered); + let base_type_arguments = + GenericChainLink::append(base_type, base_type_arguments.as_ref(), &refe); - type_is_subtype_with_generics( - (*on, base_type_arguments), - (ty, ty_structure_arguments), - state, - environment, - types, - ) + type_is_subtype_with_generics( + (*on, base_type_arguments), + (ty, ty_structure_arguments), + state, + information, + types, + ) + } else { + let base_type_arguments = GenericChainLink::append( + base_type, + base_type_arguments.as_ref(), + arguments, + ); + + type_is_subtype_with_generics( + (*on, base_type_arguments), + (ty, ty_structure_arguments), + state, + information, + types, + ) + } } } Type::Constructor(cst) => match cst { // For template literal types Constructor::BinaryOperator { - lhs, - rhs, operator: crate::types::MathematicalAndBitwise::Add, + .. } => { if let Type::Constant(Constant::String(rs)) = right_ty { - // TODO abstract - if let Type::Constant(Constant::String(ls)) = types.get_type_by_id(*lhs) { - let matches = rs.starts_with(ls); - if let (true, TypeId::STRING_TYPE) = (matches, *rhs) { - SubTypeResult::IsSubType - } else { - crate::utilities::notify!("TODO more complex {:?}", (matches, rhs)); - SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) - } + let matches = slice_matches_type( + (base_type, base_type_arguments), + rs, + state.contributions.as_mut().map(|n| &mut n.staging_contravariant), + information, + types, + false, + ); + if matches { + SubTypeResult::IsSubType } else { - crate::utilities::notify!("TODO prefix equality"); + // TODO clear contributions SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) } } else { @@ -782,21 +1109,27 @@ pub(crate) fn type_is_subtype_with_generics( | Constructor::UnaryOperator { .. } => unreachable!("invalid constructor on LHS"), Constructor::TypeOperator(_) => todo!(), Constructor::TypeRelationOperator(_) => todo!(), - Constructor::ConditionalResult { + Constructor::Image { on: _, with: _, result } + | Constructor::ConditionalResult { condition: _, truthy_result: _, otherwise_result: _, - result_union: _, - } => todo!(), - Constructor::Image { on: _, with: _, result: _ } => todo!(), - Constructor::Property { on, under, result: _, bind_this: _ } => { + result_union: result, + } => type_is_subtype_with_generics( + (*result, base_type_arguments), + (ty, ty_structure_arguments), + state, + information, + types, + ), + Constructor::Property { on, under, result: _, mode: _ } => { // Ezno custom state // TODO might be based of T if let Type::Constructor(Constructor::Property { on: r_on, under: r_under, result: _, - bind_this: _, + mode: _, }) = right_ty { if on == r_on && under == r_under { @@ -805,65 +1138,163 @@ pub(crate) fn type_is_subtype_with_generics( } // TODO this only seems to work in simple cases. For mapped types - if let Some(on) = - base_structure_arguments.and_then(|args| args.get_single_argument(*on)) + // crate::utilities::notify!( + // "base_structure_arguments={:?}, ty_structure_arguments={:?}, *on={:?}", + // base_type_arguments, + // ty_structure_arguments, + // on + // ); + + if let Some(on) = base_type_arguments.and_then(|args| args.get_single_argument(*on)) { - crate::utilities::notify!("Here got on"); - if let PropertyKey::Type(under) = under { + let new_under; + let under = if let PropertyKey::Type(original) = under { + // let ty = types.get_type_by_id(*original); crate::utilities::notify!( - "{:?} with {:?}", - under, - base_structure_arguments.as_ref() + "original={:?}, bta={:?}", + original, + base_type_arguments ); - if let Some(under) = base_structure_arguments - .and_then(|args| args.get_single_argument(*under)) + // let original = if let Type::RootPolyType( + // crate::types::PolyNature::MappedGeneric { name, extends }, + // ) = ty + // { + // *extends + // } else { + // *original + // }; + + let original = *original; + new_under = if let Some(under) = base_type_arguments + .and_then(|args| args.get_argument_covariant(original)) { - crate::utilities::notify!("Here 2"); - let property = get_property_unbound( - (on, base_structure_arguments), - ( - Publicity::Public, - &PropertyKey::Type(under), - ty_structure_arguments, - ), - environment, - types, + under.into_property_key() + } else { + crate::utilities::notify!( + "Could not find key type {:?} {:?}", + original, + base_type_arguments ); - if let Ok(property) = property { - crate::utilities::notify!("Here 3"); - match property { - Logical::Pure(PropertyValue::Value(property)) => { - crate::utilities::notify!("Here 4"); - return type_is_subtype_with_generics( - (property, base_structure_arguments), - (ty, ty_structure_arguments), - state, - environment, - types, - ); - } - value => todo!("{:?}", value), // Logical::Or { based_on, left, right } => todo!(), - // Logical::Implies { on, antecedent } => todo!(), - } + PropertyKey::from_type(original, types) + }; + &new_under + } else { + under + }; + + crate::utilities::notify!( + "Here got under={:?}, on={:?}", + under, + types.get_type_by_id(on) + ); + let property = get_property_unbound( + (on, base_type_arguments), + (Publicity::Public, under, ty_structure_arguments), + false, + information, + types, + ); + if let Ok(LogicalOrValid::Logical(property)) = property { + crate::utilities::notify!("Here 3"); + match property { + Logical::Pure(property) => { + crate::utilities::notify!("Here 4 {:?}", property); + let property_value = property.as_get_type(types); + return type_is_subtype_with_generics( + (property_value, base_type_arguments), + (ty, ty_structure_arguments), + state, + information, + types, + ); + } + Logical::BasedOnKey(BasedOnKey::Right(PropertyOn { on, key })) => { + crate::utilities::notify!("TODO {:?}", (on, key)); + // let filter = get_constraint(key, types).unwrap_or(key); + + // let properties = + // crate::types::properties::get_properties_on_single_type( + // on, + // types, + // information, + // false, + // filter, + // ); + + // for (_, _, value) in properties { + // crate::utilities::notify!("{:?}", value); + // let result = type_is_subtype_with_generics( + // (property, base_type_arguments), + // (value.as_set_type(), ty_structure_arguments), + // state, + // information, + // types, + // ); + + // if let SubTypeResult::IsNotSubType(_) = result { + // return result; + // } + // } } + value => { + crate::utilities::notify!("TODO not checking with {:?}", value); + // SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + } // Logical::Or { based_on, left, right } => todo!(), + // Logical::Implies { on, antecedent } => todo!(), } } + } else { + crate::utilities::notify!( + "Could not find argument for {:?}", + (on, base_type_arguments) + ); } + // else if let Type::Interface { .. } + // | Type::Object(ObjectNature::AnonymousTypeAnnotation) + // | Type::AliasTo { .. } = types.get_type_by_id(*on) + // { + // let property = get_property_unbound( + // (*on, base_structure_arguments), + // (Publicity::Public, under, ty_structure_arguments), + // information, + // types, + // ); + // if let Ok(property) = property { + // crate::utilities::notify!("Here"); + // match property { + // Logical::Pure(PropertyValue::Value(property)) => { + // crate::utilities::notify!("Here"); + // return type_is_subtype_with_generics( + // (property, base_structure_arguments), + // (ty, ty_structure_arguments), + // state, + // information, + // types, + // ); + // } + // value => todo!("{:?}", value), // Logical::Or { based_on, left, right } => todo!(), + // // Logical::Implies { on, antecedent } => todo!(), + // } + // } + // } + crate::utilities::notify!("Here *on={:?}", types.get_type_by_id(*on)); crate::utilities::notify!("Mismatched property"); + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) } Constructor::Awaited { .. } => todo!(), Constructor::KeyOf(on) => { if let Type::Constant(crate::Constant::String(s)) = right_ty { - let get_property_unbound = &get_property_unbound( - (*on, base_structure_arguments), + let get_property_unbound = get_property_unbound( + (*on, base_type_arguments), ( Publicity::Public, &PropertyKey::String(std::borrow::Cow::Borrowed(s)), ty_structure_arguments, ), - environment, + false, + information, types, ); if get_property_unbound.is_ok() { @@ -872,46 +1303,28 @@ pub(crate) fn type_is_subtype_with_generics( SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) } } else { - crate::utilities::notify!("TODO"); + crate::utilities::notify!("TODO keyof stuff"); SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) } } }, - // TODO aliasing might work differently Type::AliasTo { to, parameters, name: _ } => { - if base_type == TypeId::LITERAL_RESTRICTION { - crate::utilities::notify!("Here"); - return if let Type::Constant(rhs_constant) = right_ty { - type_is_subtype_with_generics( - (*to, base_structure_arguments), - (rhs_constant.get_backing_type_id(), ty_structure_arguments), - state, - environment, - types, - ) - } else { - // TODO what about if the rhs == TypeId::CONSTANT_RESTRICTION - // TODO non-constant error - SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) - }; - } - let base_structure_arguments = if let Some(parameters) = parameters { crate::utilities::notify!("Skipping looking at parameters {:?}", parameters); - base_structure_arguments + base_type_arguments } else { - base_structure_arguments + base_type_arguments }; type_is_subtype_with_generics( (*to, base_structure_arguments), (ty, ty_structure_arguments), state, - environment, + information, types, ) } - // TODO WIP + // TODO WIP nominal mechanism Type::Class { .. } => match right_ty { Type::Constant(constant) => { if constant.get_backing_type_id() == base_type { @@ -923,7 +1336,7 @@ pub(crate) fn type_is_subtype_with_generics( Type::Object(..) => { // WIP Nominal-ness for #128 if let Some(prototype) = - environment.get_chain_of_info().find_map(|info| info.prototypes.get(&ty)) + information.get_chain_of_info().find_map(|info| info.prototypes.get(&ty)) { if *prototype == base_type { SubTypeResult::IsSubType @@ -939,37 +1352,37 @@ pub(crate) fn type_is_subtype_with_generics( let right = (*on, GenericChainLink::append(ty, ty_structure_arguments.as_ref(), &into)); type_is_subtype_with_generics( - (base_type, base_structure_arguments), + (base_type, base_type_arguments), right, state, - environment, + information, types, ) } - _ => SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch), + Type::And(a, b) => { + // TODO more + crate::utilities::notify!("Here LHS class, RHS and"); + if *a == base_type || *b == base_type { + SubTypeResult::IsSubType + } else { + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + } + } + Type::SpecialObject(SpecialObject::Function(..)) | Type::FunctionReference(..) + if base_type == TypeId::FUNCTION_TYPE => + { + SubTypeResult::IsSubType + } + ty => { + crate::utilities::notify!("Does {:?} not match class", ty); + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + } }, - Type::Interface { nominal: base_type_nominal, .. } => { - // If type matched type it would have been cleared before. So looking at properties and - // prototypes here - - // These do **NOT** check for properties - let skip_nominal_branch = *base_type_nominal - && !matches!( - right_ty, - Type::RootPolyType(..) - | Type::Constructor(..) | Type::Constant(..) - | Type::Or(..) | Type::And(..) - ); - - if skip_nominal_branch { - crate::utilities::notify!( - "Short circuited {:?} is nominal and RHS={:?}", - left_ty, - right_ty - ); - // TODO not primitive error - // TODO this might break with *properties* proofs on primitives - // e.g. number :< Nat + Type::Interface { .. } => { + // TODO weird that these are interfaces + // If not captured above + if matches!(base_type, TypeId::UNDEFINED_TYPE | TypeId::NULL_TYPE | TypeId::NEVER_TYPE) + { return SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch); } @@ -983,13 +1396,13 @@ pub(crate) fn type_is_subtype_with_generics( } } Type::Object(..) => subtype_properties( - (base_type, base_structure_arguments), + (base_type, base_type_arguments), (ty, ty_structure_arguments), state, - environment, + information, types, ), - Type::SpecialObject(SpecialObjects::Function(..)) => { + Type::SpecialObject(SpecialObject::Function(..)) => { crate::utilities::notify!("TODO implement function checking"); SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) } @@ -1004,39 +1417,16 @@ pub(crate) fn type_is_subtype_with_generics( } Type::Or(_left, _right) => { unreachable!() - // TODO fails if RHS is also OR type :( - // let right = *right; - // let left = type_is_subtype2( - // base_type, - // *left, - // ty_arguments.as_deref(), - // state, - // environment, - // types, - // ); - // if let SubTypeResult::IsSubType = left { - // type_is_subtype2( - // base_type, - // right, - // ty_arguments, - // state, - // environment, - // types, - // ) - // } else { - // crate::utilities::notify!("Left failed"); - // SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) - // } } Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on, arguments }) => { let into = arguments.clone(); let append = GenericChainLink::append(ty, ty_structure_arguments.as_ref(), &into); type_is_subtype_with_generics( - (base_type, base_structure_arguments), + (base_type, base_type_arguments), (*on, append), state, - environment, + information, types, ) } @@ -1046,45 +1436,52 @@ pub(crate) fn type_is_subtype_with_generics( SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) } Type::Constructor(..) | Type::RootPolyType(..) => { - let arg = base_structure_arguments - .and_then(|args| args.get_argument(base_type, environment, types)); + let arg = + base_type_arguments.and_then(|args| args.get_argument_covariant(base_type)); - if let Some(args) = arg { - for arg in args { - let result = type_is_subtype_with_generics( - (arg, base_structure_arguments), - (ty, ty_structure_arguments), - state, - environment, - types, - ); + crate::utilities::notify!("TODO {:?}", arg); + SubTypeResult::IsSubType - if let e @ SubTypeResult::IsNotSubType(_) = result { - return e; - } - } - SubTypeResult::IsSubType - } else { - let to = get_constraint(ty, types).unwrap(); + // if let Some(args) = arg { + // for arg in args { + // let result = type_is_subtype_with_generics( + // (arg, base_type_arguments), + // (ty, ty_structure_arguments), + // state, + // information, + // types, + // ); + + // if let e @ SubTypeResult::IsNotSubType(_) = result { + // return e; + // } + // } + // SubTypeResult::IsSubType + // } else { + // let to = get_constraint(ty, types).unwrap(); - if to == TypeId::ANY_TYPE { - crate::utilities::notify!("Modify constraint for equality"); - } + // if to == TypeId::ANY_TYPE { + // crate::utilities::notify!("Modify constraint for equality"); + // } - type_is_subtype_with_generics( - (base_type, base_structure_arguments), - (to, ty_structure_arguments), - state, - environment, - types, - ) - } + // type_is_subtype_with_generics( + // (base_type, base_type_arguments), + // (to, ty_structure_arguments), + // state, + // information, + // types, + // ) + // } } Type::FunctionReference(_) => todo!(), Type::SpecialObject(_) => todo!(), Type::Class { .. } => todo!(), } } + Type::SpecialObject(SpecialObject::Null) => { + crate::utilities::notify!("rhs={:?}", right_ty); + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + } Type::SpecialObject(_) => todo!(), } } @@ -1093,20 +1490,20 @@ fn subtype_function( (left_func, base_type_arguments): (crate::FunctionId, GenericChain), (right_ty, ty, right_type_arguments): (&Type, TypeId, GenericChain), state: &mut State, - environment: &Environment, + information: &impl InformationChain, types: &TypeStore, ) -> SubTypeResult { crate::utilities::notify!("Subtyping a function"); let right_func = if let Type::FunctionReference(right_func) - | Type::SpecialObject(SpecialObjects::Function(right_func, _)) = right_ty + | Type::SpecialObject(SpecialObject::Function(right_func, _)) = right_ty { right_func } else if let Some(constraint) = get_constraint(ty, types) { // TODO explain why get_constraint early breaks a bunch of tests let right_ty = types.get_type_by_id(constraint); if let Type::FunctionReference(right_func) - | Type::SpecialObject(SpecialObjects::Function(right_func, _)) = right_ty + | Type::SpecialObject(SpecialObject::Function(right_func, _)) = right_ty { right_func } else { @@ -1122,44 +1519,45 @@ fn subtype_function( let right_func = types.functions.get(right_func).unwrap(); for (idx, lhs_param) in left_func.parameters.parameters.iter().enumerate() { - match right_func.parameters.get_parameter_type_at_index(idx) { - Some((right_param_ty, position)) => { - let last_mode = - std::mem::replace(&mut state.mode, SubTypingMode::Covariant { position }); - - // Reverse is important - let result = type_is_subtype_with_generics( - (right_param_ty, right_type_arguments), - (lhs_param.ty, base_type_arguments), - state, - environment, - types, - ); + if let Some((right_param_ty, position)) = + right_func.parameters.get_parameter_type_at_index(idx) + { + let last_mode = + std::mem::replace(&mut state.mode, SubTypingMode::Covariant { position }); - if let err @ SubTypeResult::IsNotSubType(_) = result { - let lhs = print_type(right_param_ty, types, environment, true); - let rhs = print_type(lhs_param.ty, types, environment, true); - crate::utilities::notify!( - "Parameter invalid rhs ({:?} {:?}) <- lhs ({:?} {:?})", - rhs, - right_type_arguments, - lhs, - base_type_arguments - ); - // TODO don't short circuit - return err; - } + // Reverse is important + let result = type_is_subtype_with_generics( + (right_param_ty, right_type_arguments), + (lhs_param.ty, base_type_arguments), + state, + information, + types, + ); - state.mode = last_mode; - } - None => { - if !lhs_param.is_optional { - crate::utilities::notify!("Expected parameter, for non optional parameter"); - return SubTypeResult::IsNotSubType(NonEqualityReason::MissingParameter); - } + if let err @ SubTypeResult::IsNotSubType(_) = result { + let lhs = print_type(right_param_ty, types, information, true); + let rhs = print_type(lhs_param.ty, types, information, true); + crate::utilities::notify!( + "Parameter invalid rhs ({:?} {:?}) <- lhs ({:?} {:?})", + rhs, + right_type_arguments, + lhs, + base_type_arguments + ); + // TODO don't short circuit + return err; } + + state.mode = last_mode; + } else { + // This is allowed. TODO only in some cases + // if !lhs_param.is_optional { + // crate::utilities::notify!("Expected parameter, for non optional parameter"); + // return SubTypeResult::IsNotSubType(NonEqualityReason::MissingParameter); + // } } } + // TODO optional and rest parameters // `void` return type means anything goes here @@ -1170,7 +1568,7 @@ fn subtype_function( (left_func.return_type, base_type_arguments), (right_func.return_type, right_type_arguments), state, - environment, + information, types, ); @@ -1186,7 +1584,7 @@ fn subtype_properties( (base_type, base_type_arguments): (TypeId, GenericChain), (ty, right_type_arguments): (TypeId, GenericChain), state: &mut State, - environment: &Environment, + information: &impl InformationChain, types: &TypeStore, ) -> SubTypeResult { // TODO this will cause problems @@ -1203,36 +1601,39 @@ fn subtype_properties( // } let mut property_errors = Vec::new(); - let reversed_flattened_properties_on_on = environment + let reversed_flattened_properties_on_base = information .get_chain_of_info() .filter_map(|info| info.current_properties.get(&base_type).map(|v| v.iter().rev())) .flatten(); - for (publicity, key, lhs_property) in reversed_flattened_properties_on_on { + // Note this won't check for conditional stuff being true etc or things being deleted + for (publicity, key, lhs_property) in reversed_flattened_properties_on_base { // crate::utilities::notify!( // "key {:?} with base_type_arguments={:?}", // key, // base_type_arguments // ); - + let holding_key; let key = match key { - PropertyKey::Type(ty) => { + PropertyKey::Type(key_ty) => { if let Some(base_type_arguments) = base_type_arguments { - let ty = base_type_arguments.get_single_argument(*ty).unwrap_or(*ty); - PropertyKey::from_type(ty, types) + let key_ty = + base_type_arguments.get_single_argument(*key_ty).unwrap_or(*key_ty); + holding_key = PropertyKey::from_type(key_ty, types); + &holding_key } else { - key.clone() + key } } - PropertyKey::String(_) => key.clone(), + PropertyKey::String(_) => key, }; let result = check_lhs_property_is_super_type_of_rhs( - (*publicity, &key), - (lhs_property, base_type_arguments), + (*publicity, key), + (lhs_property, base_type_arguments, false), (ty, right_type_arguments), state, - environment, + information, types, ); @@ -1241,169 +1642,259 @@ fn subtype_properties( } } - let result = if property_errors.is_empty() { - // TODO type arguments - if let Some(ref mut object_constraints) = state.object_constraints { - let base_type = - if let Some(GenericChainLink::Link { ref from, parent_link, value: _ }) = - base_type_arguments - { - if parent_link.is_none() { - crate::utilities::notify!("TODO recursive get_from"); - } - *from - } else { - base_type - }; - object_constraints.push((ty, base_type)); - } + state.mode = state.mode.one_shallower(); - if let Some(extends) = types.interface_extends.get(&base_type) { - type_is_subtype_with_generics( + if property_errors.is_empty() { + if let Type::Interface { extends: Some(extends), .. } = types.get_type_by_id(base_type) { + let extends_result = type_is_subtype_with_generics( (*extends, base_type_arguments), (ty, right_type_arguments), state, - environment, + information, types, - ) - } else { - SubTypeResult::IsSubType + ); + if let e @ SubTypeResult::IsNotSubType(_) = extends_result { + return e; + } + } + + // Exclusive check + if base_type_arguments.is_some_and(|base| base.exclusive_mode()) { + use crate::types::properties; + + let get_properties = properties::get_properties_on_single_type( + ty, + types, + information, + false, + TypeId::ANY_TYPE, + ); + + // Assert base_type contains all the keys of the LHS + for (publicity, key, _value) in get_properties { + let result = properties::get_property_unbound( + (base_type, base_type_arguments), + (publicity, &key, None), + true, + information, + types, + ); + + // TODO more + if result.is_err() { + // TODO excess property + return SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch); + } + } + } + + // TODO type arguments + if let Some(ref mut object_constraints) = state.object_constraints { + let base_type = if let Some(GenericChainLink::PartiallyAppliedGenericArgumentsLink { + ref from, + parent_link, + value: _, + }) = base_type_arguments + { + if parent_link.is_some() { + crate::utilities::notify!("TODO recursive get_from"); + } + *from + } else { + base_type + }; + object_constraints.push((ty, base_type)); } + + SubTypeResult::IsSubType } else { SubTypeResult::IsNotSubType(NonEqualityReason::PropertiesInvalid { errors: property_errors, }) - }; - - state.mode = state.mode.one_shallower(); - - result + } } fn check_lhs_property_is_super_type_of_rhs( (publicity, key): (Publicity, &PropertyKey<'_>), - (lhs_property, base_type_arguments): (&PropertyValue, GenericChain), + (lhs_property, base_type_arguments, optional): (&PropertyValue, GenericChain, bool), (ty, right_type_arguments): (TypeId, GenericChain), state: &mut State, - environment: &Environment, + information: &impl InformationChain, types: &TypeStore, ) -> Result<(), PropertyError> { match lhs_property { PropertyValue::Value(lhs_value) => { - // TODO should all values do this or is it only mapped generic ids - let root; - let base_type_arguments = if let Some((id, to)) = key.mapped_generic_id(types) { - // WIP, need to work for ors etc - // let to = base_type_arguments.and_then(|args| args.get_single_argument(to)).unwrap_or(to); - let mut map = crate::Map::default(); - map.insert(id, (to, SpanWithSource::NULL)); - root = GenericArguments::ExplicitRestrictions(map); - Some(GenericChainLink::append_to_link(id, base_type_arguments.as_ref(), &root)) - } else { - base_type_arguments - }; + let right_result = get_property_unbound( + (ty, right_type_arguments), + (publicity, key, base_type_arguments), + false, + information, + types, + ); - // Fix for mapped types and where { [x: string]: ... } etc - if let PropertyKey::Type(TypeId::STRING_TYPE | TypeId::NUMBER_TYPE) = key { - if let Type::Object(..) = types.get_type_by_id(ty) { - let reversed_flattened_properties_on_on = environment - .get_chain_of_info() - .filter_map(|info| info.current_properties.get(&ty).map(|v| v.iter().rev())) - .flatten(); + // { + // crate::utilities::notify!("LHS value is {:?}", lhs_value); + // crate::utilities::notify!( + // "RHS value is {:?} {:?}", + // right_result, + // (key, base_type_arguments) + // ); + // } - for (_rhs_publicity, rhs_key, rhs_property) in - reversed_flattened_properties_on_on - { - if key_matches( - (key, base_type_arguments), - (rhs_key, right_type_arguments), - types, - ) { - let res = check_logical_property( + match right_result { + Ok(LogicalOrValid::Logical(res)) => { + let res = check_logical_property( + (*lhs_value, base_type_arguments, optional), + (res, right_type_arguments), + state, + information, + types, + ); + match res { + SubTypeResult::IsSubType => Ok(()), + SubTypeResult::IsNotSubType(err) => Err(PropertyError::Invalid { + expected: *lhs_value, + // TODO logical -> TypeId + found: TypeId::UNIMPLEMENTED_ERROR_TYPE, + mismatch: err, + }), + } + } + // PROXY HANDLING!! + Ok(LogicalOrValid::NeedsCalculation(NeedsCalculation::Proxy( + objects::Proxy { handler, over }, + _, + ))) => { + crate::utilities::notify!("TODO set as well?"); + let get_handler = get_property_unbound( + (handler, right_type_arguments), + ( + Publicity::Public, + &PropertyKey::String(std::borrow::Cow::Borrowed("get")), + base_type_arguments, + ), + false, + information, + types, + ); + + if let Ok(LogicalOrValid::Logical(Logical::Pure(get_res))) = get_handler { + let function = get_res.as_get_type(types); + if let Type::SpecialObject(SpecialObject::Function(id, _)) = + types.get_type_by_id(function) + { + let function = types.get_function_from_id(*id); + let mut map = crate::Map::new(); + // `Some` weird but accounts for missing parameters + if let Some((first, _)) = + function.parameters.get_parameter_type_at_index(0) + { + map.insert(first, (CovariantContribution::TypeId(over), 0)); + } + if let Some((second, _)) = + function.parameters.get_parameter_type_at_index(1) + { + map.insert( + second, + (CovariantContribution::from(key.clone().into_owned()), 0), + ); + } + if let Some((third, _)) = + function.parameters.get_parameter_type_at_index(2) + { + map.insert(third, (CovariantContribution::TypeId(handler), 0)); + } + + let right_type_arguments = Some(GenericChainLink::MappedPropertyLink { + parent_link: right_type_arguments.as_ref(), + value: &map, + }); + + let result = type_is_subtype_with_generics( (*lhs_value, base_type_arguments), - (Logical::Pure(rhs_property.clone()), right_type_arguments), + (function.return_type, right_type_arguments), state, - environment, + information, types, ); - if let SubTypeResult::IsNotSubType(err) = res { - return Err(PropertyError::Invalid { - expected: TypeId::UNIMPLEMENTED_ERROR_TYPE, - found: TypeId::UNIMPLEMENTED_ERROR_TYPE, - mismatch: err, - }); + if let SubTypeResult::IsSubType = result { + Ok(()) + } else { + // crate::utilities::notify!("One missing"); + Err(PropertyError::Missing) } } else { - crate::utilities::notify!("Here key does not match"); + crate::utilities::notify!("{:?}", get_res); + + check_lhs_property_is_super_type_of_rhs( + (publicity, key), + (lhs_property, base_type_arguments, optional), + (handler, right_type_arguments), + state, + information, + types, + ) } + } else { + check_lhs_property_is_super_type_of_rhs( + (publicity, key), + (lhs_property, base_type_arguments, optional), + (handler, right_type_arguments), + state, + information, + types, + ) } + } + Ok(LogicalOrValid::NeedsCalculation(NeedsCalculation::Infer { .. })) => { + crate::utilities::notify!("TODO add constraint candidate"); Ok(()) - } else { - crate::utilities::notify!("TODO more complex keys"); - Err(PropertyError::Missing) } - } else { - let property = get_property_unbound( - (ty, right_type_arguments), - (publicity, key, base_type_arguments), - environment, - types, - ); - - match property { - Ok(rhs_value) => { - let res = check_logical_property( - (*lhs_value, base_type_arguments), - (rhs_value, right_type_arguments), - state, - environment, - types, - ); - match res { - SubTypeResult::IsSubType => Ok(()), - SubTypeResult::IsNotSubType(err) => Err(PropertyError::Invalid { - expected: *lhs_value, - // TODO logical -> TypeId - found: TypeId::UNIMPLEMENTED_ERROR_TYPE, - mismatch: err, - }), - } + Err(_) => { + if optional { + Ok(()) + } else { + // crate::utilities::notify!("One missing"); + Err(PropertyError::Missing) } - // TODO - Err(..) => Err(PropertyError::Missing), } } } - PropertyValue::Getter(getter) => { - let res = get_property_unbound((ty, None), (publicity, key, None), environment, types); + PropertyValue::GetterAndSetter { getter, setter } => { + todo!("{:?}", (getter, setter)); + } + PropertyValue::Getter(_getter) => { + let res = + get_property_unbound((ty, None), (publicity, key, None), true, information, types); crate::utilities::notify!("looking for {:?} found {:?}", key, res); match res { - Ok(res) => { - let res = check_logical_property( - (getter.return_type, base_type_arguments), - (res, right_type_arguments), - state, - environment, - types, - ); - match res { - SubTypeResult::IsSubType => Ok(()), - SubTypeResult::IsNotSubType(err) => Err(PropertyError::Invalid { - expected: TypeId::UNIMPLEMENTED_ERROR_TYPE, - found: TypeId::UNIMPLEMENTED_ERROR_TYPE, - mismatch: err, - }), - } + Ok(LogicalOrValid::Logical(_res)) => { + todo!("get get return type") } // TODO - Err(..) => Err(PropertyError::Missing), + res => { + crate::utilities::notify!("res={:?}", res); + Err(PropertyError::Missing) + } + } + } + PropertyValue::Setter(_) => { + let rhs = + get_property_unbound((ty, None), (publicity, key, None), true, information, types); + + match rhs { + Ok(ok) => { + crate::utilities::notify!("Set vs {:?}", ok); + Ok(()) + } + Err(_err) => Err(PropertyError::Missing), } } - PropertyValue::Setter(_) => todo!(), PropertyValue::Deleted => { // TODO WIP - let res = get_property_unbound((ty, None), (publicity, key, None), environment, types); + let res = + get_property_unbound((ty, None), (publicity, key, None), true, information, types); if res.is_ok() { // TODO the opposite of missing Err(PropertyError::Missing) @@ -1412,84 +1903,430 @@ fn check_lhs_property_is_super_type_of_rhs( Ok(()) } } - PropertyValue::ConditionallyExists { on: _, truthy } => { - if let PropertyValue::Value(lhs_value) = &**truthy { - let property = get_property_unbound( - (ty, right_type_arguments), - (publicity, key, base_type_arguments), - environment, - types, - ); - if let Ok(property) = property { - // TODO - let found = if let Logical::Pure(PropertyValue::Value(ref found)) = property { - *found - } else { - TypeId::ERROR_TYPE - }; + PropertyValue::ConditionallyExists { condition, truthy } => { + crate::utilities::notify!("Here {:?}", (key, ty, condition, truthy)); - let res = check_logical_property( - (*lhs_value, base_type_arguments), - (property, right_type_arguments), - state, - environment, - types, - ); + // TODO `NON_OPTIONAL_KEY_ARGUMENT` temp + let is_optional = + !matches!(*condition, TypeId::TRUE | TypeId::NON_OPTIONAL_KEY_ARGUMENT); - if let SubTypeResult::IsNotSubType(reason) = res { - Err(PropertyError::Invalid { - expected: *lhs_value, - found, - mismatch: reason, - }) - } else { - Ok(()) - } - } else { - // Okay if missing because of the above - Ok(()) - } - } else { - todo!() - } + check_lhs_property_is_super_type_of_rhs( + (publicity, key), + (truthy, base_type_arguments, is_optional), + (ty, right_type_arguments), + state, + information, + types, + ) + // if let PropertyValue::Value(lhs_value) = &**truthy { + // let property = get_property_unbound( + // (ty, right_type_arguments), + // (publicity, key, base_type_arguments), + // information, + // types, + // ); + // crate::utilities::notify!("property={:?}", property); + + // if let Ok(LogicalOrValid::Logical(property)) = property { + // // TODO for error reporting + // let found = if let Logical::Pure(PropertyValue::Value(ref found)) = property { + // *found + // } else { + // TypeId::ERROR_TYPE + // }; + + // crate::utilities::notify!("{:?}", property); + + // let res = check_logical_property( + // (*lhs_value, base_type_arguments), + // (property, right_type_arguments), + // state, + // information, + // types, + // ); + + // if let SubTypeResult::IsNotSubType(reason) = res { + // Err(PropertyError::Invalid { + // expected: *lhs_value, + // found, + // mismatch: reason, + // }) + // } else { + // Ok(()) + // } + // } else { + // crate::utilities::notify!("Here"); + // // Err(PropertyError::Missing) + // // Okay if missing because of the above + // Ok(()) + // } + // } else { + // crate::utilities::notify!("Here maybe errors needs to continue checking {:?}", truthy); + // Ok(()) + // } + } + PropertyValue::Configured { on, .. } => { + crate::utilities::notify!("TODO check readonly"); + check_lhs_property_is_super_type_of_rhs( + (publicity, key), + (on, base_type_arguments, optional), + (ty, right_type_arguments), + state, + information, + types, + ) } } } fn check_logical_property( - (base, base_type_arguments): (TypeId, GenericChain), + (lhs_property_value, lhs_property_value_type_arguments, optional): (TypeId, GenericChain, bool), (rhs_property, right_type_arguments): (Logical, GenericChain), state: &mut State, - environment: &Environment, + information: &impl InformationChain, types: &TypeStore, ) -> SubTypeResult { match rhs_property { Logical::Pure(rhs_property) => { - let rhs_type = rhs_property.as_set_type(); + let rhs_type = rhs_property.as_get_type(types); // crate::utilities::notify!( // "Checking {} with {}, against {}, left={:?}", - // print_type(key, types, environment, true), - // print_type(property, types, environment, true), - // print_type(rhs_type, types, environment, true), - // base_type_arguments + // print_type(key, types, information, true), + // print_type(property, types, information, true), + // print_type(rhs_type, types, information, true), + // lhs_property_value_type_arguments // ); type_is_subtype_with_generics( - (base, base_type_arguments), + (lhs_property_value, lhs_property_value_type_arguments), (rhs_type, right_type_arguments), state, - environment, + information, types, ) } - Logical::Or { .. } => todo!(), - Logical::Implies { on, antecedent } => check_logical_property( - (base, GenericChainLink::append(base, base_type_arguments.as_ref(), &antecedent)), - (*on, right_type_arguments), - state, - environment, - types, - ), + Logical::Or { condition, left, right } => { + crate::utilities::notify!("{:?}", (condition, &left, &right)); + + if let (LogicalOrValid::Logical(left), LogicalOrValid::Logical(right)) = (*left, *right) + { + let left_result = check_logical_property( + (lhs_property_value, lhs_property_value_type_arguments, optional), + (left, right_type_arguments), + state, + information, + types, + ); + + if let SubTypeResult::IsSubType = left_result { + check_logical_property( + (lhs_property_value, lhs_property_value_type_arguments, optional), + (right, right_type_arguments), + state, + information, + types, + ) + } else { + // else return the failing result + left_result + } + } else if optional { + SubTypeResult::IsSubType + } else { + crate::utilities::notify!("One missing"); + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + } + } + Logical::Implies { on, antecedent } => { + crate::utilities::notify!("{:?}", antecedent); + check_logical_property( + (lhs_property_value, lhs_property_value_type_arguments, optional), + ( + *on, + GenericChainLink::append( + TypeId::ERROR_TYPE, + right_type_arguments.as_ref(), + &antecedent, + ), + ), + state, + information, + types, + ) + } + Logical::BasedOnKey(kind) => match kind { + BasedOnKey::Left { value, key_arguments } => { + let property_generics = Some(GenericChainLink::MappedPropertyLink { + parent_link: right_type_arguments.as_ref(), + value: &key_arguments, + }); + check_logical_property( + (lhs_property_value, lhs_property_value_type_arguments, optional), + (*value, property_generics), + state, + information, + types, + ) + } + BasedOnKey::Right(PropertyOn { on, key }) => { + if let Type::RootPolyType(PolyNature::MappedGeneric { name: _, extends }) = + types.get_type_by_id(key) + { + type_is_subtype_of_property_mapped_key( + MappedKey { value: (*extends).into(), key }, + (lhs_property_value, lhs_property_value_type_arguments, optional), + (on, right_type_arguments), + state, + information, + types, + ) + } else { + let filter = get_constraint(key, types).unwrap_or(key); + + let properties = get_properties_on_single_type2( + (on, right_type_arguments), + types, + information, + filter, + ); + for (_key, rhs_property, _args) in properties { + let result = check_logical_property( + (lhs_property_value, lhs_property_value_type_arguments, optional), + (Logical::Pure(rhs_property), right_type_arguments), + state, + information, + types, + ); + if result.is_mismatch() { + return result; + } + } + SubTypeResult::IsSubType + } + } + }, + } +} + +pub struct MappedKey { + /// covariant contribution allows for slices and `PropertyKey::String` + pub value: CovariantContribution, + /// This points towards the `Type::RootPolyType(PolyNature::MappedGeneric)` + pub key: TypeId, +} + +pub fn type_is_subtype_of_property_mapped_key( + mapped_key: MappedKey, + (base, property_generics, optional): (TypeId, GenericChain, bool), + (ty, right_type_arguments): (TypeId, GenericChain), + state: &mut State, + information: &impl InformationChain, + types: &TypeStore, +) -> SubTypeResult { + // TODO use covariant contribution as property key. Also what about slices on types? + match mapped_key.value { + CovariantContribution::String(ref s) => { + { + crate::utilities::notify!( + "Reading {:?}, with {:?} {:?}", + types.get_type_by_id(ty), + s, + (property_generics.as_ref(), right_type_arguments.as_ref()) + ); + } + let right_property = get_property_unbound( + (ty, right_type_arguments), + ( + Publicity::Public, + &PropertyKey::String(std::borrow::Cow::Owned(s.to_owned())), + None, + ), + false, + information, + types, + ); + + match right_property { + Ok(LogicalOrValid::Logical(right_property)) => { + let map = crate::Map::from_iter([(mapped_key.key, (mapped_key.value, 0))]); + let property_generics = Some(GenericChainLink::MappedPropertyLink { + parent_link: property_generics.as_ref(), + value: &map, + }); + let result = check_logical_property( + (base, property_generics, optional), + (right_property, right_type_arguments), + state, + information, + types, + ); + + crate::utilities::notify!("Got {:?}", result); + + result + } + // TODO + _res => { + crate::utilities::notify!("Missing"); + if optional { + SubTypeResult::IsSubType + } else { + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + } + } + } + } + CovariantContribution::TypeId(key_ty) => { + match types.get_type_by_id(key_ty) { + Type::AliasTo { to, name: _, parameters: _ } => { + type_is_subtype_of_property_mapped_key( + MappedKey { value: (*to).into(), key: mapped_key.key }, + (base, property_generics, optional), + (ty, right_type_arguments), + state, + information, + types, + ) + } + Type::And(left, right) => { + let left = type_is_subtype_of_property_mapped_key( + MappedKey { value: (*left).into(), key: mapped_key.key }, + (base, property_generics, optional), + (ty, right_type_arguments), + state, + information, + types, + ); + if left.is_mismatch() { + type_is_subtype_of_property_mapped_key( + MappedKey { value: (*right).into(), key: mapped_key.key }, + (base, property_generics, optional), + (ty, right_type_arguments), + state, + information, + types, + ) + } else { + left + } + } + Type::Or(left, right) => { + let left = type_is_subtype_of_property_mapped_key( + MappedKey { value: (*left).into(), key: mapped_key.key }, + (base, property_generics, optional), + (ty, right_type_arguments), + state, + information, + types, + ); + if left.is_mismatch() { + left + } else { + type_is_subtype_of_property_mapped_key( + MappedKey { value: (*right).into(), key: mapped_key.key }, + (base, property_generics, optional), + (ty, right_type_arguments), + state, + information, + types, + ) + } + } + Type::RootPolyType(_) => { + // TODO get_covariant contribution + if let Some(value) = + property_generics.and_then(|args| args.get_single_argument(key_ty)) + { + type_is_subtype_of_property_mapped_key( + MappedKey { value: value.into(), key: mapped_key.key }, + (base, property_generics, optional), + (ty, right_type_arguments), + state, + information, + types, + ) + } else { + todo!("no value {:?}", (ty, property_generics)) + } + } + Type::Constructor(Constructor::Property { .. }) => { + todo!() + } + Type::Constructor(Constructor::KeyOf(key_of_ty)) => { + let properties = get_properties_on_single_type2( + (*key_of_ty, property_generics), + types, + information, + TypeId::ANY_TYPE, + ); + for (key, _, _) in properties { + let value = match key { + PropertyKey::Type(ty) => CovariantContribution::TypeId(ty), + PropertyKey::String(str) => { + CovariantContribution::String(str.into_owned()) + } + }; + crate::utilities::notify!("Here {:?}", value); + let result = type_is_subtype_of_property_mapped_key( + MappedKey { value, key: mapped_key.key }, + (base, property_generics, optional), + (ty, right_type_arguments), + state, + information, + types, + ); + + if result.is_mismatch() { + return SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch); + } + } + SubTypeResult::IsSubType + } + Type::Constructor(_) => todo!(), + Type::PartiallyAppliedGenerics(_) => todo!(), + Type::Interface { .. } => todo!(), + Type::Class { .. } => todo!(), + Type::Constant(_) => { + let right_property = get_property_unbound( + (ty, right_type_arguments), + (Publicity::Public, &PropertyKey::Type(key_ty), right_type_arguments), + true, + information, + types, + ); + + match right_property { + Ok(LogicalOrValid::Logical(right_property)) => { + let map = + crate::Map::from_iter([(mapped_key.key, (mapped_key.value, 0))]); + let property_generics = Some(GenericChainLink::MappedPropertyLink { + parent_link: property_generics.as_ref(), + value: &map, + }); + check_logical_property( + (base, property_generics, optional), + (right_property, right_type_arguments), + state, + information, + types, + ) + } + // TODO + _res => { + if optional { + SubTypeResult::IsSubType + } else { + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + } + } + } + } + Type::FunctionReference(_) => todo!(), + Type::Object(_) => todo!(), + Type::SpecialObject(_) => todo!(), + } + } + value => todo!("{:?}", value), } } @@ -1499,59 +2336,78 @@ pub fn type_is_subtype_of_property( (property, property_generics): (&Logical, GenericChain), ty: TypeId, state: &mut State, - environment: &Environment, + information: &impl InformationChain, types: &TypeStore, ) -> SubTypeResult { match property { Logical::Pure(prop) => type_is_subtype_with_generics( - (prop.as_set_type(), property_generics), + (prop.as_set_type(types), property_generics), (ty, GenericChain::None), state, - environment, + information, types, ), - Logical::Or { .. } => { - todo!() - // let left_result = type_is_subtype_of_property( - // left, - // property_generics, - // ty, - // state, - // environment, - // types, - // ); - // if let SubTypeResult::IsSubType = left_result { - // left_result + Logical::Or { condition: _, left, right } => { + let left_result = if let LogicalOrValid::Logical(left) = &**left { + type_is_subtype_of_property( + (left, property_generics), + ty, + state, + information, + types, + ) + } else { + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + }; + if let SubTypeResult::IsSubType = left_result { + left_result + } else if let LogicalOrValid::Logical(right) = &**right { + type_is_subtype_of_property( + (right, property_generics), + ty, + state, + information, + types, + ) + } else { + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + } + } + Logical::Implies { on, antecedent } => { + let property_generics = GenericChainLink::append( + TypeId::UNIMPLEMENTED_ERROR_TYPE, + property_generics.as_ref(), + antecedent, + ); + type_is_subtype_of_property((on, property_generics), ty, state, information, types) + } + Logical::BasedOnKey(on) => { + // if let BasedOnKey::Right { on, key } = on { + // if let Type::RootPolyType(PolyNature::MappedGeneric { name: _, extends }) = + // types.get_type_by_id(*filter) + // { + // type_is_subtype_of_( + // Some(MappedKey { value: *extends, key: *filter }), + // (property, property_generics), + // ty, + // state, + // information, + // types, + // ) + // } else { + // crate::utilities::notify!("TODO, returning IsSubType {:?}", on); + // SubTypeResult::IsSubType + // } // } else { - // type_is_subtype_of_property( - // right, - // property_generics, - // ty, - // state, - // environment, - // types, - // ) + crate::utilities::notify!("TODO, returning IsSubType {:?}", on); + SubTypeResult::IsSubType // } } - Logical::Implies { on, antecedent } => type_is_subtype_of_property( - ( - on, - GenericChainLink::append( - TypeId::UNIMPLEMENTED_ERROR_TYPE, - property_generics.as_ref(), - antecedent, - ), - ), - ty, - state, - environment, - types, - ), } } impl NonEqualityReason { - pub(crate) fn _into_error_message(self, _environment: &GeneralContext) -> Vec { + pub(crate) fn _into_error_message(self, _information: &GeneralContext) -> Vec { match self { NonEqualityReason::GenericParameterMismatch | NonEqualityReason::MissingParameter @@ -1560,6 +2416,516 @@ impl NonEqualityReason { errors.into_iter().map(|error| format!("{error:?}")).collect() } NonEqualityReason::TooStrict => todo!(), + NonEqualityReason::Excess => todo!(), + } + } +} + +pub type SliceArguments = + TriMap; + +// #[derive(Debug, Default)] +// pub struct SliceArguments { +// pub(crate) covariant: TriMap, +// /// WIP for mapped inference +// pub(crate) contravariant: TriMap +// } + +/// `allow_casts=true` is for property keys +pub(crate) fn slice_matches_type( + (base, base_type_arguments): (TypeId, Option), + slice: &str, + mut contributions: Option<&mut SliceArguments>, + information: &impl InformationChain, + types: &TypeStore, + allow_casts: bool, +) -> bool { + let base_ty = types.get_type_by_id(base); + + // { + // crate::utilities::notify!( + // "Slice checking {} ({:?}) :>= '{}'", + // print_type(base, types, information, true), + // base_type_arguments, + // slice + // ); + // } + + // TODO cast string + if allow_casts { + if base == TypeId::ANY_TYPE { + return true; + } else if base == TypeId::BOOLEAN_TYPE { + return slice == "true" || slice == "false"; + } else if base == TypeId::NUMBER_TYPE { + return slice.parse::().is_ok(); + } else if base == TypeId::STRING_TYPE { + // crate::utilities::notify!("Here!"); + // TODO is this okay? + return slice.parse::().is_err(); + } + } + match base_ty { + Type::Constant(Constant::String(base_string)) => { + if let Some(transform) = base_type_arguments.and_then(|a| a.get_string_transform()) { + apply_string_intrinsic(transform, base_string).as_str() == slice + } else if base_type_arguments.is_some_and(|a| a.is_case_insensitive()) { + base_string.to_lowercase() == slice.to_lowercase() + } else { + base_string == slice + } + } + Type::RootPolyType(rpt) => { + // TODO temp fix to set keyof arguments + { + let constraint = rpt.get_constraint(); + if let Type::Constructor(Constructor::KeyOf { .. }) = + types.get_type_by_id(constraint) + { + let mut new_contributions = SliceArguments::default(); + let _ = slice_matches_type( + (constraint, base_type_arguments), + slice, + Some(&mut new_contributions), + information, + types, + allow_casts, + ); + if let Some(ref mut contributions) = contributions { + contributions.extend(new_contributions); + } + } + } + + if let Some(argument) = base_type_arguments.and_then(|v| v.get_single_argument(base)) { + slice_matches_type( + (argument, base_type_arguments), + slice, + contributions, + information, + types, + allow_casts, + ) + } else if let Some(contributions) = contributions { + assert!(rpt.is_substitutable()); + let constraint = rpt.get_constraint(); + let res = slice_matches_type( + (constraint, base_type_arguments), + slice, + Some(contributions), + information, + types, + allow_casts, + ); + if res { + contributions + .insert(base, (CovariantContribution::String(slice.to_owned()), 0)); + } + res + } else { + false + } + } + Type::AliasTo { to, .. } => slice_matches_type( + (*to, base_type_arguments), + slice, + contributions, + information, + types, + allow_casts, + ), + Type::Or(l, r) => { + // TODO temp for + let mut new_contributions = SliceArguments::default(); + let matches = slice_matches_type( + (*l, base_type_arguments), + slice, + Some(&mut new_contributions), + information, + types, + allow_casts, + ); + if matches { + if let Some(ref mut contributions) = contributions { + contributions.extend(new_contributions); + } + true + } else { + // TODO clear contributions + slice_matches_type( + (*r, base_type_arguments), + slice, + contributions, + information, + types, + allow_casts, + ) + } + } + Type::And(l, r) => { + let mut new_contributions = SliceArguments::default(); + let matches = slice_matches_type( + (*l, base_type_arguments), + slice, + Some(&mut new_contributions), + information, + types, + allow_casts, + ); + if matches { + if let Some(ref mut contributions) = contributions { + contributions.extend(new_contributions); + } + slice_matches_type( + (*r, base_type_arguments), + slice, + contributions, + information, + types, + allow_casts, + ) + } else { + false + } + } + Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: + transform @ (TypeId::STRING_CAPITALIZE + | TypeId::STRING_UNCAPITALIZE + | TypeId::STRING_LOWERCASE + | TypeId::STRING_UPPERCASE), + arguments, + }) => { + let matches_constraint = match *transform { + TypeId::STRING_CAPITALIZE => slice.chars().next().map_or(true, char::is_uppercase), + TypeId::STRING_UNCAPITALIZE => { + slice.chars().next().map_or(true, char::is_lowercase) + } + TypeId::STRING_LOWERCASE => slice.chars().all(char::is_lowercase), + TypeId::STRING_UPPERCASE => slice.chars().all(char::is_uppercase), + _ => unreachable!(), + }; + + if matches_constraint { + let generic_chain_link = Some(GenericChainLink::SpecialGenericChainLink { + parent_link: base_type_arguments.as_ref(), + special: SpecialGenericChainLink::CaseTransform { transform: *transform }, + }); + let inner = arguments.get_structure_restriction(TypeId::STRING_GENERIC).unwrap(); + + let mut new_contributions = SliceArguments::default(); + // TODO any contributions in here SHOULD be wrapped in case insensitive + let matches = slice_matches_type( + (inner, generic_chain_link), + slice, + Some(&mut new_contributions), + information, + types, + allow_casts, + ); + if let (true, Some(current)) = (matches, contributions) { + crate::utilities::notify!("{:?}", new_contributions); + for (id, (c, d)) in new_contributions { + current + .insert(id, (CovariantContribution::CaseInsensitive(Box::new(c)), d)); + } + crate::utilities::notify!("{:?}", current); + } + matches + } else { + false + } + } + Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::CASE_INSENSITIVE, + arguments, + }) => { + let base_type_arguments = Some(GenericChainLink::SpecialGenericChainLink { + parent_link: base_type_arguments.as_ref(), + special: SpecialGenericChainLink::CaseInsensitive, + }); + let inner = arguments.get_structure_restriction(TypeId::STRING_GENERIC).unwrap(); + slice_matches_type( + (inner, base_type_arguments), + slice, + contributions, + information, + types, + allow_casts, + ) + } + Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::MULTIPLE_OF | TypeId::LESS_THAN | TypeId::GREATER_THAN, + arguments: _, + }) if allow_casts => { + // Special behavior here to treat numerical property keys (which are strings) as numbers + if let Ok(value) = slice.parse::() { + number_matches_type( + (base, base_type_arguments), + value, + contributions, + information, + types, + ) + } else { + false + } + } + Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::NOT_RESTRICTION, + arguments, + }) => { + // Here don't have to use disjoint + let argument = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap(); + // TODO what to do about contributions :/ + let matches = slice_matches_type( + (argument, base_type_arguments), + slice, + contributions, + information, + types, + allow_casts, + ); + // crate::utilities::notify!("negated slice arguments={:?}", _k); + !matches + } + Type::Constructor(super::Constructor::KeyOf(on)) => { + let argument = + (Publicity::Public, &PropertyKey::String(std::borrow::Cow::Borrowed(slice)), None); + + let arg = base_type_arguments + .as_ref() + .and_then(|link| link.get_single_argument(*on)) + .unwrap_or(*on); + + let property = get_property_unbound( + (arg, base_type_arguments), + argument, + true, + information, + types, + ); + + // crate::utilities::notify!("Here {:?}", property); + + if let Ok(LogicalOrValid::Logical(property)) = property { + // For mapped types + if let Some(contributions) = contributions { + // WIP!! + let is_writable = + if let Logical::Pure(PropertyValue::Configured { on: _, ref descriptor }) = + property + { + descriptor.writable + } else { + // TODO might be missing something here via LogicalOr etc + crate::utilities::notify!("Might be missing {:?}", property); + TypeId::TRUE + }; + + // WIP!! + let is_defined = if let Logical::Pure(PropertyValue::ConditionallyExists { + ref condition, + .. + }) = property + { + *condition + } else { + // TODO might be missing something here via LogicalOr etc + crate::utilities::notify!("Might be missing {:?}", property); + TypeId::TRUE + }; + + contributions.insert( + TypeId::WRITABLE_KEY_ARGUMENT, + (CovariantContribution::TypeId(is_writable), 0), + ); + contributions.insert( + TypeId::NON_OPTIONAL_KEY_ARGUMENT, + (CovariantContribution::TypeId(is_defined), 0), + ); + crate::utilities::notify!( + "For MT set: (is_writable, is_defined)={:?}", + (is_writable, is_defined) + ); + } + + true + } else { + false + } + } + Type::Constructor(super::Constructor::BinaryOperator { + lhs, + rhs, + operator: MathematicalAndBitwise::Add, + }) => { + let lhs = base_type_arguments + .as_ref() + .and_then(|link| link.get_single_argument(*lhs)) + .unwrap_or(*lhs); + + let rhs = base_type_arguments + .as_ref() + .and_then(|link| link.get_single_argument(*rhs)) + .unwrap_or(*rhs); + + if let Type::Constant(Constant::String(prefix)) = types.get_type_by_id(lhs) { + if let Some(after) = slice.strip_prefix(prefix) { + slice_matches_type( + (rhs, base_type_arguments), + after, + contributions, + information, + types, + allow_casts, + ) + } else { + false + } + } else if let Type::Constant(Constant::String(suffix)) = types.get_type_by_id(rhs) { + if let Some(before) = slice.strip_suffix(suffix) { + slice_matches_type( + (lhs, base_type_arguments), + before, + contributions, + information, + types, + allow_casts, + ) + } else { + false + } + } else { + let lhs = types.get_type_by_id(lhs); + let rhs = types.get_type_by_id(rhs); + crate::utilities::notify!( + "More complex type here, returning false. lhs={:?}, rhs={:?}, {:?}", + lhs, + rhs, + base_type_arguments + ); + false + } + } + _ => { + if base == TypeId::STRING_TYPE || base == TypeId::ANY_TYPE { + true + } else { + crate::utilities::notify!("Cannot match key {:?}", base_ty); + false + } + } + } +} + +// TODO keyof +#[allow(clippy::only_used_in_recursion)] +pub(crate) fn number_matches_type( + (base, base_type_arguments): (TypeId, Option), + number: f64, + mut contributions: Option<&mut SliceArguments>, + information: &impl InformationChain, + types: &TypeStore, +) -> bool { + match types.get_type_by_id(base) { + Type::Constant(cst) => { + if let Constant::Number(base_number) = cst { + *base_number == number + } else { + false + } + } + Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: on @ (TypeId::MULTIPLE_OF | TypeId::LESS_THAN | TypeId::GREATER_THAN), + arguments, + }) => { + // Special behavior here to treat numerical property keys (which are strings) as numbers + // TODO unify with the subtyping + let argument = arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(); + + let arg_ty = types.get_type_by_id(argument); + if let Type::Constant(Constant::Number(argument)) = arg_ty { + let number: ordered_float::NotNan = number.try_into().unwrap(); + crate::utilities::notify!("value={:?}, arg={:?}", number, argument); + match *on { + TypeId::LESS_THAN => *argument < number, + TypeId::GREATER_THAN => *argument > number, + TypeId::MULTIPLE_OF => number % *argument == 0f64, + _ => unreachable!(), + } + } else { + crate::utilities::notify!("TODO argument is dependent {:?}", arg_ty); + false + } + } + Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::NOT_RESTRICTION, + arguments, + }) => { + let argument = arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(); + + !number_matches_type( + (argument, base_type_arguments), + number, + contributions, + information, + types, + ) + } + Type::Or(l, r) => { + // TODO temp for + let mut new_contributions = SliceArguments::default(); + let matches = number_matches_type( + (*l, base_type_arguments), + number, + Some(&mut new_contributions), + information, + types, + ); + if matches { + if let Some(ref mut contributions) = contributions { + contributions.extend(new_contributions); + } + true + } else { + // TODO clear contributions + number_matches_type( + (*r, base_type_arguments), + number, + contributions, + information, + types, + ) + } + } + Type::And(l, r) => { + let mut new_contributions = SliceArguments::default(); + let matches = number_matches_type( + (*l, base_type_arguments), + number, + Some(&mut new_contributions), + information, + types, + ); + if matches { + if let Some(ref mut contributions) = contributions { + contributions.extend(new_contributions); + } + number_matches_type( + (*r, base_type_arguments), + number, + contributions, + information, + types, + ) + } else { + false + } + } + ty => { + crate::utilities::notify!("TODO number matches ty={:?}", ty); + true } } } diff --git a/checker/src/types/terms.rs b/checker/src/types/terms.rs index 44fb8cee..04e25147 100644 --- a/checker/src/types/terms.rs +++ b/checker/src/types/terms.rs @@ -4,23 +4,13 @@ use super::TypeId; /// Terms /// TODO: -/// - `IntoProof` /// - `BigInt` () -/// - Separate `NotNull` term, and implement js subtyping -/// -/// TODO unsure about some of these #[derive(Eq, PartialEq, Hash, Debug, Clone, binary_serialize_derive::BinarySerializable)] pub enum Constant { Number(ordered_float::NotNan), String(String), Boolean(bool), - Symbol { - key: String, - }, - /// A unique function type given - /// the `x` reference of `function x() {}` - Undefined, - Null, + Symbol { key: String }, NaN, } @@ -33,8 +23,6 @@ impl Constant { Constant::String(value) => Cow::Borrowed(value), Constant::Boolean(value) => Cow::Borrowed(if *value { "true" } else { "false" }), Constant::Symbol { key } => Cow::Owned(format!("Symbol({key})")), - Constant::Undefined => Cow::Borrowed("undefined"), - Constant::Null => Cow::Borrowed("null"), Constant::NaN => Cow::Borrowed("NaN"), } } @@ -48,8 +36,6 @@ impl Constant { Constant::String(value) => format!("\"{value}\""), Constant::Boolean(value) => if *value { "true" } else { "false" }.to_owned(), Constant::Symbol { key } => format!("Symbol({key})"), - Constant::Undefined => "undefined".to_owned(), - Constant::Null => "null".to_owned(), Constant::NaN => "NaN".to_owned(), } } @@ -60,9 +46,7 @@ impl Constant { Constant::Number(_) | Constant::NaN => TypeId::NUMBER_TYPE, Constant::String(_) => TypeId::STRING_TYPE, Constant::Boolean(_) => TypeId::BOOLEAN_TYPE, - Constant::Undefined => TypeId::UNDEFINED_TYPE, - Constant::Null => TypeId::NULL_TYPE, - Constant::Symbol { .. } => todo!(), + Constant::Symbol { .. } => TypeId::SYMBOL_TYPE, } } } diff --git a/checker/src/utilities.rs b/checker/src/utilities.rs deleted file mode 100644 index 5c252872..00000000 --- a/checker/src/utilities.rs +++ /dev/null @@ -1,42 +0,0 @@ -static IS_DEBUG_MODE: std::sync::Mutex> = std::sync::Mutex::new(None); - -#[cfg(all(debug_assertions, not(target_arch = "wasm32")))] -#[allow(clippy::manual_is_variant_and)] -pub(crate) fn is_debug_mode() -> bool { - *IS_DEBUG_MODE.lock().unwrap().get_or_insert_with(|| { - std::env::var("EZNO_DEBUG").map(|value| !value.is_empty()).unwrap_or_default() - }) -} - -#[cfg(any(not(debug_assertions), target_arch = "wasm32"))] -pub(crate) fn is_debug_mode() -> bool { - false -} - -/// For `notify!` macro below -pub fn shorten(s: &str) -> &str { - &s[s.find("src").expect("file not under 'src' folder")..] -} - -macro_rules! notify { - () => { - if crate::utilities::is_debug_mode() { - eprintln!("[{}:{}]", crate::utilities::shorten(file!()), line!()) - } - }; - - ($content:expr) => { - if crate::utilities::is_debug_mode() { - eprintln!("[{}:{}] {}", crate::utilities::shorten(file!()), line!(), $content) - } - }; - - ($content:literal, $($es:expr),+) => { - if crate::utilities::is_debug_mode() { - eprintln!("[{}:{}] {}", crate::utilities::shorten(file!()), line!(), format_args!($content, $($es),+)) - } - }; -} - -#[doc(hidden)] -pub(crate) use notify; diff --git a/checker/src/utilities/debugging.rs b/checker/src/utilities/debugging.rs new file mode 100644 index 00000000..4d845ba7 --- /dev/null +++ b/checker/src/utilities/debugging.rs @@ -0,0 +1,68 @@ +use std::cell::Cell; + +// For `notify!` macro below. This caches the value looked up by std::env::var(...) +thread_local! { + static IS_DEBUG_MODE: Cell> = Cell::new(None); + static DEBUG_MODE_PAUSED: Cell = Cell::new(false); +} + +#[allow(clippy::manual_is_variant_and)] +pub(crate) fn is_debug_mode() -> bool { + if cfg!(all(debug_assertions, not(target_arch = "wasm32"))) { + if DEBUG_MODE_PAUSED.get() { + return false; + } + let value = IS_DEBUG_MODE.get(); + if let Some(value) = value { + value + } else { + let new_value = std::env::var("EZNO_DEBUG") + .map(|value| !value.is_empty() || value == "0") + .unwrap_or_default(); + IS_DEBUG_MODE.set(Some(new_value)); + new_value + } + } else { + false + } +} + +pub(crate) fn pause_debug_mode() { + if cfg!(all(debug_assertions, not(target_arch = "wasm32"))) { + DEBUG_MODE_PAUSED.set(true); + } +} + +pub(crate) fn unpause_debug_mode() { + if cfg!(all(debug_assertions, not(target_arch = "wasm32"))) { + DEBUG_MODE_PAUSED.set(false); + } +} + +/// For `notify!` macro below +pub fn shorten(s: &str) -> &str { + &s[s.find("src").expect("file not under 'src' folder")..] +} + +macro_rules! notify { + () => { + if crate::utilities::is_debug_mode() { + eprintln!("[{}:{}]", crate::utilities::shorten(file!()), line!()) + } + }; + + ($content:expr) => { + if crate::utilities::is_debug_mode() { + eprintln!("[{}:{}] {}", crate::utilities::shorten(file!()), line!(), $content) + } + }; + + ($content:literal, $($es:expr),+) => { + if crate::utilities::is_debug_mode() { + eprintln!("[{}:{}] {}", crate::utilities::shorten(file!()), line!(), format_args!($content, $($es),+)) + } + }; +} + +#[doc(hidden)] +pub(crate) use notify; diff --git a/checker/src/utilities/map.rs b/checker/src/utilities/map.rs new file mode 100644 index 00000000..3a63a535 --- /dev/null +++ b/checker/src/utilities/map.rs @@ -0,0 +1,103 @@ +/// Small map for 1-5 items +/// Also should be rewindable +#[derive(Debug, Clone, binary_serialize_derive::BinarySerializable)] +pub struct Map(pub Vec<(K, V)>); + +impl Default for Map { + fn default() -> Self { + Self(Default::default()) + } +} + +impl Map +where + K: PartialEq, +{ + pub fn new() -> Self { + Default::default() + } + + pub fn get(&self, want: &Q) -> Option<&V> + where + K: std::borrow::Borrow, + Q: std::hash::Hash + Eq + ?Sized + std::cmp::PartialEq, + { + self.0.iter().rev().find_map(|(key, value)| (want == key).then_some(value)) + } + + pub fn get_mut(&mut self, want: &K) -> Option<&mut V> { + self.0.iter_mut().rev().find_map(|(key, value)| (want == key).then_some(value)) + } + + #[must_use] + pub fn iter(&self) -> impl ExactSizeIterator { + self.0.iter() + } + + pub fn iter_mut(&mut self) -> impl ExactSizeIterator { + self.0.iter_mut() + } + + #[must_use] + pub fn keys(&self) -> impl ExactSizeIterator { + self.0.iter().map(|(k, _)| k) + } + + #[must_use] + pub fn values(&self) -> impl ExactSizeIterator { + self.0.iter().map(|(_, v)| v) + } + + /// *assumes `id` not already inside* + pub fn insert(&mut self, id: K, value: V) { + self.0.push((id, value)); + } + + #[must_use] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + #[must_use] + pub fn into_some(self) -> Option { + if self.0.is_empty() { + None + } else { + Some(self) + } + } + + #[must_use] + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn drop_range( + &mut self, + range: std::ops::RangeFrom, + ) -> impl Iterator + '_ { + self.0.drain(range) + } +} + +impl std::iter::IntoIterator for Map { + type Item = (K, V); + + type IntoIter = as std::iter::IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl std::iter::FromIterator<(K, V)> for Map { + fn from_iter>(iter: T) -> Self { + Self(Vec::from_iter(iter)) + } +} + +impl std::iter::Extend<(K, V)> for Map { + fn extend>(&mut self, iter: T) { + self.0.extend(iter); + } +} diff --git a/checker/src/utilities/mod.rs b/checker/src/utilities/mod.rs new file mode 100644 index 00000000..e8427ee6 --- /dev/null +++ b/checker/src/utilities/mod.rs @@ -0,0 +1,8 @@ +//! These should probably be made into reusable crates at some point + +mod debugging; +pub mod map; +pub mod range_map; +pub mod serialization; + +pub use debugging::*; diff --git a/checker/src/range_map.rs b/checker/src/utilities/range_map.rs similarity index 100% rename from checker/src/range_map.rs rename to checker/src/utilities/range_map.rs diff --git a/checker/src/serialization.rs b/checker/src/utilities/serialization.rs similarity index 100% rename from checker/src/serialization.rs rename to checker/src/utilities/serialization.rs diff --git a/checker/tests/partial_source.rs b/checker/tests/partial_source.rs index c1c48082..101bcc32 100644 --- a/checker/tests/partial_source.rs +++ b/checker/tests/partial_source.rs @@ -2,9 +2,9 @@ #[test] fn type_mappings() { use ezno_checker::{check_project, synthesis, TypeCheckOptions}; - use std::collections::HashSet; // Below source has several issues + let root = "index.ts"; let text = "let x: 2 = 5 + ; const y = const z = 2; @@ -13,13 +13,11 @@ fn type_mappings() { func(b)"; let definition_file = ezno_checker::INTERNAL_DEFINITION_FILE_PATH.into(); - let type_definition_files = HashSet::from_iter([definition_file]); + let type_definition_files = vec![definition_file]; // `lsp_mode` <=> partial syntax let options = TypeCheckOptions { lsp_mode: true, ..Default::default() }; - let root = "index.ts"; - let result = check_project::<_, synthesis::EznoParser>( vec![root.into()], type_definition_files, @@ -31,8 +29,5 @@ fn type_mappings() { let diagnostics: Vec<_> = result.diagnostics.into_iter().collect(); assert_eq!(diagnostics.len(), 1); - assert_eq!( - diagnostics.first().unwrap().reason(), - "Could not find variable 'b' in scope. Did you mean x, y or z?" - ); + assert_eq!(diagnostics.first().unwrap().reason(), "Could not find variable 'b' in scope"); } diff --git a/checker/tests/suggestions.rs b/checker/tests/suggestions.rs new file mode 100644 index 00000000..0621d149 --- /dev/null +++ b/checker/tests/suggestions.rs @@ -0,0 +1,79 @@ +const SIMPLE_DTS: Option<&str> = Some(include_str!("../definitions/simple.d.ts")); + +#[cfg(feature = "ezno-parser")] +#[test] +fn suggestions() { + use ezno_checker::{check_project, Diagnostic, TypeCheckOptions}; + use std::path::{Path, PathBuf}; + + let root = "index.ts"; + let text = " +const something = 2, anything = 4; +console.log(sothing); + +const invalidType: Aray = []; + +const obj = { property: 2, else: 2 }; +console.log(obj.proberly); +const obj2 = { }; +console.log(obj2.proberly); +"; + + let definition_file_name: PathBuf = if SIMPLE_DTS.is_some() { + "./checker/definitions/simple.d.ts".into() + } else { + ezno_checker::INTERNAL_DEFINITION_FILE_PATH.into() + }; + let type_definition_files = vec![definition_file_name.clone()]; + + let resolver = |path: &Path| -> Option> { + if path == definition_file_name.as_path() { + Some(SIMPLE_DTS.unwrap().to_owned().into_bytes()) + } else { + Some(text.into()) + } + }; + + // `store_expression_type_mappings` important + let options = TypeCheckOptions { store_type_mappings: true, ..Default::default() }; + + let result = check_project::<_, ezno_checker::synthesis::EznoParser>( + vec![root.into()], + type_definition_files, + resolver, + options, + (), + None, + ); + + let diagnostics = result.diagnostics.into_iter().collect::>(); + + // Diagnostics doesn't implement PartialEq, so this is fine. Just want to check the [] + labels, not positions + match_deref::match_deref! { + match &diagnostics { + Deref @ [ + Diagnostic::PositionWithAdditionalLabels { + reason: Deref @ "Could not find type 'Aray'", + labels: Deref @ [(Deref @ "Did you mean 'Array'?", _)], + .. + }, + Diagnostic::PositionWithAdditionalLabels { + reason: Deref @ "Could not find variable 'sothing' in scope", + labels: Deref @ [(Deref @ "Did you mean 'something'?", _)], + .. + }, + Diagnostic::PositionWithAdditionalLabels { + reason: Deref @ "No property 'proberly' on { property: 2, else: 2 }", + labels: Deref @ [(Deref @ "Did you mean 'property'?", _)], + .. + }, + Diagnostic::PositionWithAdditionalLabels { + reason: Deref @ "No property 'proberly' on {}", + labels: Deref @ [], + .. + }, + ] => {}, + _ => panic!("{diagnostics:#?} did not match diagnostics"), + } + }; +} diff --git a/checker/tests/type_mappings.rs b/checker/tests/type_mappings.rs index 100f8315..e6eef489 100644 --- a/checker/tests/type_mappings.rs +++ b/checker/tests/type_mappings.rs @@ -2,20 +2,18 @@ #[test] fn type_mappings() { use ezno_checker::{check_project, synthesis, TypeCheckOptions}; - use std::collections::HashSet; + let root = "index.ts"; let text = "const x: number = 2; function y() { return x } y()"; let definition_file = ezno_checker::INTERNAL_DEFINITION_FILE_PATH.into(); - let type_definition_files = HashSet::from_iter([definition_file]); + let type_definition_files = vec![definition_file]; // `store_expression_type_mappings` important let options = TypeCheckOptions { store_type_mappings: true, ..Default::default() }; - let root = "index.ts"; - let result = check_project::<_, synthesis::EznoParser>( vec![root.into()], type_definition_files, diff --git a/src/ast_explorer.rs b/src/ast_explorer.rs index 69a484a7..7879d421 100644 --- a/src/ast_explorer.rs +++ b/src/ast_explorer.rs @@ -8,7 +8,7 @@ use enum_variants_strings::EnumVariantsStrings; use parser::{source_map::FileSystem, ASTNode, Expression, Module, ToStringOptions}; use crate::{ - reporting::emit_diagnostics, + reporting::report_diagnostics_to_cli, utilities::{print_to_cli, print_to_cli_without_newline}, }; @@ -136,7 +136,7 @@ impl ExplorerSubCommand { } } // TODO temp - Err(err) => emit_diagnostics( + Err(err) => report_diagnostics_to_cli( std::iter::once((err, source_id).into()), &fs, false, @@ -162,7 +162,7 @@ impl ExplorerSubCommand { } } // TODO temp - Err(err) => emit_diagnostics( + Err(err) => report_diagnostics_to_cli( std::iter::once((err, source_id).into()), &fs, false, @@ -185,7 +185,7 @@ impl ExplorerSubCommand { }; print_to_cli(format_args!("{}", module.to_string(&options))); } - Err(err) => emit_diagnostics( + Err(err) => report_diagnostics_to_cli( std::iter::once((err, source_id).into()), &fs, false, diff --git a/src/check.rs b/src/check.rs index f07fec29..436e6c28 100644 --- a/src/check.rs +++ b/src/check.rs @@ -1,8 +1,5 @@ use checker::CheckOutput; -use std::{ - collections::HashSet, - path::{Path, PathBuf}, -}; +use std::path::{Path, PathBuf}; pub fn check( entry_points: Vec, @@ -11,9 +8,9 @@ pub fn check( type_check_options: checker::TypeCheckOptions, ) -> CheckOutput { let definitions = if let Some(tdm) = type_definition_module { - HashSet::from_iter(std::iter::once(tdm.into())) + vec![tdm.into()] } else { - HashSet::from_iter(std::iter::once(checker::INTERNAL_DEFINITION_FILE_PATH.into())) + vec![checker::INTERNAL_DEFINITION_FILE_PATH.into()] }; let read_from_fs = diff --git a/src/cli.rs b/src/cli.rs index b0feadb0..26fec7e7 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -11,11 +11,11 @@ use std::{ use crate::{ build::{build, BuildConfig, BuildOutput, EznoParsePostCheckVisitors, FailedBuildOutput}, check::check, - reporting::emit_diagnostics, + reporting::report_diagnostics_to_cli, utilities::{self, print_to_cli, MaxDiagnostics}, }; use argh::FromArgs; -use checker::CheckOutput; +use checker::{CheckOutput, TypeCheckOptions}; use parser::ParseOptions; /// The Ezno type-checker & compiler @@ -200,30 +200,30 @@ pub fn run_cli entry_points, Err(_) => return ExitCode::FAILURE, }; - let CheckOutput { diagnostics, module_contents, .. } = + let CheckOutput { diagnostics, module_contents, chronometer, types, .. } = check(entry_points, read_file, definition_file.as_deref(), type_check_options); - #[cfg(not(target_family = "wasm"))] - if let Some(start) = start { - eprintln!("Checked in {:?}", start.elapsed()); - }; + let diagnostics_count = diagnostics.count(); + let current = timings.then(std::time::Instant::now); - if diagnostics.has_error() { + let result = if diagnostics.has_error() { if let MaxDiagnostics::FixedTo(0) = max_diagnostics { let count = diagnostics.into_iter().count(); print_to_cli(format_args!("Found {count} type errors and warnings 😬")) } else { - emit_diagnostics( + report_diagnostics_to_cli( diagnostics, &module_contents, compact_diagnostics, @@ -234,7 +234,7 @@ pub fn run_cli { - emit_diagnostics( + report_diagnostics_to_cli( diagnostics, &fs, compact_diagnostics, @@ -347,7 +363,7 @@ pub fn run_cli { - emit_diagnostics( + report_diagnostics_to_cli( std::iter::once((err, source_id).into()), &files, false, diff --git a/src/main.rs b/src/main.rs index 6d50911e..db171795 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,3 @@ -#![deny(clippy::all)] -#![deny(clippy::pedantic)] #![allow( clippy::new_without_default, // TODO: Remove when fixed diff --git a/src/repl.rs b/src/repl.rs index 6814b3a8..6abc582e 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -5,7 +5,7 @@ use argh::FromArgs; use parser::{visiting::VisitorsMut, ASTNode}; use parser::{Expression, Module, Statement}; -use crate::reporting::emit_diagnostics; +use crate::reporting::report_diagnostics_to_cli; use crate::utilities::print_to_cli; /// Run project repl using deno. (`deno` command must be in path) @@ -50,8 +50,13 @@ pub(crate) fn run_repl( let mut state = match state { Ok(state) => state, Err((diagnostics, fs)) => { - emit_diagnostics(diagnostics, &fs, false, crate::utilities::MaxDiagnostics::All) - .unwrap(); + report_diagnostics_to_cli( + diagnostics, + &fs, + false, + crate::utilities::MaxDiagnostics::All, + ) + .unwrap(); return; } }; @@ -90,7 +95,7 @@ pub(crate) fn run_repl( let mut item = match result { Ok(item) => item, Err(err) => { - emit_diagnostics( + report_diagnostics_to_cli( std::iter::once((err, source).into()), state.get_fs_ref(), false, @@ -117,7 +122,7 @@ pub(crate) fn run_repl( match result { Ok((last_ty, diagnostics)) => { - emit_diagnostics( + report_diagnostics_to_cli( diagnostics, state.get_fs_ref(), false, @@ -129,7 +134,7 @@ pub(crate) fn run_repl( } } Err(diagnostics) => { - emit_diagnostics( + report_diagnostics_to_cli( diagnostics, state.get_fs_ref(), false, diff --git a/src/reporting.rs b/src/reporting.rs index 9912203d..2747442b 100644 --- a/src/reporting.rs +++ b/src/reporting.rs @@ -80,7 +80,7 @@ fn checker_diagnostic_to_codespan_diagnostic( } } -pub(crate) fn emit_diagnostics( +pub(crate) fn report_diagnostics_to_cli( diagnostics: I, fs: &MapFileStore, compact: bool,