From 662647988138972097eb7557ef33bf63ccd67c4c Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 5 Dec 2023 13:56:51 +0000 Subject: [PATCH] Random fixes across the project (#90) - Up to 114 specification tests passing - Multiple fixes to parsing --- .github/workflows/github-release.yml | 10 +- .github/workflows/lines-of-code.yml | 2 +- .github/workflows/rust.yml | 84 +-- Cargo.lock | 4 +- checker/Cargo.toml | 2 +- checker/definitions/internal.d.ts | 11 + checker/examples/check.rs | 2 - checker/specification/build.rs | 23 +- checker/specification/specification.md | 149 ++++- checker/specification/test.rs | 4 +- checker/specification/to_implement.md | 89 +-- checker/src/behavior/assignments.rs | 2 +- checker/src/behavior/functions.rs | 423 ++++++++++++- checker/src/behavior/loops.rs | 20 + checker/src/behavior/operations.rs | 46 +- checker/src/behavior/template_literal.rs | 31 +- checker/src/behavior/variables.rs | 10 +- checker/src/context/calling.rs | 5 + checker/src/context/environment.rs | 108 ++-- checker/src/context/mod.rs | 488 +++------------ checker/src/context/root.rs | 12 +- checker/src/diagnostics.rs | 32 +- checker/src/events/application.rs | 8 +- checker/src/events/helpers.rs | 17 +- checker/src/events/mod.rs | 4 + checker/src/lib.rs | 145 ++--- checker/src/options.rs | 2 +- checker/src/synthesis/assignments.rs | 13 +- checker/src/synthesis/block.rs | 37 +- checker/src/synthesis/classes.rs | 6 +- checker/src/synthesis/definitions.rs | 21 +- checker/src/synthesis/expressions.rs | 177 +++--- checker/src/synthesis/extensions/jsx.rs | 29 +- checker/src/synthesis/functions.rs | 123 +--- checker/src/synthesis/hoisting.rs | 120 ++-- checker/src/synthesis/interfaces.rs | 42 +- checker/src/synthesis/mod.rs | 87 ++- checker/src/synthesis/statements.rs | 194 +----- checker/src/synthesis/type_annotations.rs | 46 +- checker/src/synthesis/variables.rs | 46 +- checker/src/types/calling.rs | 79 +-- checker/src/types/classes.rs | 18 +- checker/src/types/functions.rs | 8 +- checker/src/types/mod.rs | 26 +- checker/src/types/others.rs | 2 +- checker/src/types/poly_types/substitution.rs | 2 +- checker/src/types/printing.rs | 25 +- checker/src/types/properties.rs | 19 +- checker/src/types/store.rs | 42 +- checker/src/types/subtyping.rs | 23 +- parser/Cargo.toml | 2 +- parser/examples/chain.rs | 4 +- parser/examples/code_blocks_to_script.rs | 115 ++++ parser/examples/depth.rs | 18 + parser/examples/parse.rs | 25 +- parser/src/block.rs | 136 ++-- parser/src/comments.rs | 41 +- .../src/declarations/classes/class_member.rs | 89 ++- parser/src/declarations/classes/mod.rs | 42 +- parser/src/declarations/export.rs | 4 +- parser/src/declarations/import.rs | 4 +- parser/src/declarations/mod.rs | 21 +- parser/src/errors.rs | 7 + parser/src/expressions/arrow_function.rs | 31 +- parser/src/expressions/assignments.rs | 4 +- parser/src/expressions/mod.rs | 169 +++-- parser/src/expressions/object_literal.rs | 74 ++- parser/src/expressions/template_literal.rs | 4 +- parser/src/extensions/decorators.rs | 4 +- parser/src/extensions/jsx.rs | 4 +- parser/src/functions.rs | 208 +++++- parser/src/lexer.rs | 55 +- parser/src/lib.rs | 592 +++++++++--------- parser/src/modules.rs | 8 +- parser/src/operators.rs | 7 +- parser/src/property_key.rs | 105 +++- parser/src/statements/mod.rs | 7 +- parser/src/statements/switch_statement.rs | 12 +- parser/src/statements/while_statement.rs | 12 +- parser/src/tokens.rs | 55 +- parser/src/types/declares.rs | 6 +- parser/src/types/interface.rs | 49 +- parser/src/types/type_annotations.rs | 9 +- parser/src/variable_fields.rs | 64 +- parser/src/visiting.rs | 334 ++++------ parser/tests/statements.rs | 9 +- parser/tests/visiting.rs | 20 +- parser/visitable-derive/macro.rs | 14 +- src/ast_explorer.rs | 4 +- src/{commands.rs => build.rs} | 49 +- src/check.rs | 29 + src/cli.rs | 86 ++- src/js-based-plugin/index.mjs | 2 +- src/js-cli-and-library/src/index.mjs | 4 +- src/js-cli-and-library/src/initialised.js | 4 +- src/js-cli-and-library/test.mjs | 11 +- src/lib.rs | 9 +- src/main.rs | 12 +- src/repl.rs | 2 +- src/transformers/mod.rs | 17 +- src/transformers/optimisations.rs | 156 +++++ src/wasm_bindings.rs | 30 +- 102 files changed, 3304 insertions(+), 2393 deletions(-) create mode 100644 checker/src/behavior/loops.rs create mode 100644 parser/examples/code_blocks_to_script.rs create mode 100644 parser/examples/depth.rs rename src/{commands.rs => build.rs} (63%) create mode 100644 src/check.rs create mode 100644 src/transformers/optimisations.rs diff --git a/.github/workflows/github-release.yml b/.github/workflows/github-release.yml index cded4079..623e3613 100644 --- a/.github/workflows/github-release.yml +++ b/.github/workflows/github-release.yml @@ -21,12 +21,14 @@ jobs: steps: - uses: actions/checkout@v3 + with: + fetch-tags: true - name: Get version id: get-version run: | - version=$(git tag --list 'release/main-*' --sort=-taggerdate | tail -n 1) - echo "Releasing ${version:13}" - echo "new-ezno-version=${version:13}" >> $GITHUB_OUTPUT + tag=$(git for-each-ref --sort=creatordate --format '%(refname:short)' 'refs/tags/release/ezno-[0-9]*' | tail -n 1) + echo "Releasing ${tag:13}" + echo "new-ezno-version=${tag:13}" >> $GITHUB_OUTPUT - id: get-sponsors run: | @@ -101,7 +103,7 @@ jobs: - name: Print artifacts run: | - echo "::group::Print Artifacts" + echo "::group::Build artifacts" ls -R build-artifacts echo "::endgroup::" diff --git a/.github/workflows/lines-of-code.yml b/.github/workflows/lines-of-code.yml index a13760bf..1c21c3f3 100644 --- a/.github/workflows/lines-of-code.yml +++ b/.github/workflows/lines-of-code.yml @@ -37,5 +37,5 @@ jobs: -w "\nUpdated-project: \n" \ https://projects.kaleidawave.workers.dev/update-project; - echo "\`$name\` has $linesOfRustCode lines of code"Y; + echo "\`$name\` has $linesOfRustCode lines of code" >> $GITHUB_STEP_SUMMARY; done \ No newline at end of file diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 0a65919e..a187f547 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -28,7 +28,7 @@ env: jobs: validity: runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 10 steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable @@ -38,27 +38,49 @@ jobs: path: ${{ env.CACHE_PATHS }} key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + - name: Check source is valid + run: cargo check --workspace + + - name: Check binary + run: cargo check --bin ezno + + extras: + runs-on: ubuntu-latest + needs: validity + timeout-minutes: 10 + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + + - uses: actions/cache@v3 + with: + path: ${{ env.CACHE_PATHS }} + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + - uses: dorny/paths-filter@v2 id: changes with: filters: | + src: + - 'src/**' parser: - 'parser/**' checker: - 'checker/**' - src: - - 'src/**' - - - name: Check source is valid - run: cargo check --workspace - - - name: Check binary - run: cargo check --bin ezno - uses: brndnmtthws/rust-action-cargo-binstall@v1 if: steps.changes.outputs.src == 'true' with: packages: wasm-bindgen-cli@0.2.87 + - uses: denoland/setup-deno@v1 + if: steps.changes.outputs.src == 'true' + with: + deno-version: v1.x + - uses: actions/setup-node@v3 + if: steps.changes.outputs.src == 'true' + with: + node-version: 18 + - name: Check WASM if: steps.changes.outputs.src == 'true' run: | @@ -68,6 +90,16 @@ jobs: npm run build working-directory: src/js-cli-and-library + - name: Build and test WASM + if: steps.changes.outputs.src == 'true' + run: | + npm ci + npm run test + node ./dist/cli.cjs info + deno run -A ./dist/cli.mjs info + working-directory: src/js-cli-and-library + shell: bash + - name: Check parser without extras if: steps.changes.outputs.parser == 'true' run: @@ -77,7 +109,7 @@ jobs: if: steps.changes.outputs.checker == 'true' run: cargo check -p ezno-checker --no-default-features - + formating: runs-on: ubuntu-latest steps: @@ -92,13 +124,12 @@ jobs: packages: taplo-cli - name: Check TOML formatting with taplo - run: | - taplo fmt --check **/*/Cargo.toml + run: taplo fmt --check **/*/Cargo.toml tests: needs: validity runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 20 steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable @@ -135,37 +166,18 @@ jobs: - name: Run checker tests if: steps.changes.outputs.checker == 'true' - run: cargo test -F ezno-parser + run: | + # Test checker with the parser features + cargo test -F ezno-parser working-directory: checker - name: Run base tests run: cargo test - - uses: brndnmtthws/rust-action-cargo-binstall@v1 - with: - packages: wasm-bindgen-cli@0.2.87 - - - uses: denoland/setup-deno@v1 - with: - deno-version: v1.x - - uses: actions/setup-node@v3 - with: - node-version: 18 - - - name: Build and test WASM - run: | - rustup target add wasm32-unknown-unknown - npm ci - npm run test - node ./dist/cli.cjs info - deno run -A ./dist/cli.mjs info - working-directory: src/js-cli-and-library - shell: bash - fuzzing: needs: validity runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 15 continue-on-error: true strategy: matrix: diff --git a/Cargo.lock b/Cargo.lock index e0789db5..437f2d1e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -831,9 +831,9 @@ dependencies = [ [[package]] name = "source-map" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0b3ebf1fb4d2927644205814029f7a831ac62a26f1244d0981111b6129cc41" +checksum = "a0919459f4f8f556d70ae8b531e9ecbb07a596ec5d229fe372108c421350d8bd" dependencies = [ "codespan-reporting", "self-rust-tokenize", diff --git a/checker/Cargo.toml b/checker/Cargo.toml index 7abccccf..a69ffb83 100644 --- a/checker/Cargo.toml +++ b/checker/Cargo.toml @@ -19,7 +19,7 @@ default = [] ezno-parser = ["parser"] [dependencies] -source-map = { version = "0.14.6", features = ["serde-serialize"] } +source-map = { version = "0.14.7", features = ["serde-serialize"] } binary-serialize-derive = { path = "./binary-serialize-derive", version = "0.0.1" } diff --git a/checker/definitions/internal.d.ts b/checker/definitions/internal.d.ts index 906e1e4c..549968d1 100644 --- a/checker/definitions/internal.d.ts +++ b/checker/definitions/internal.d.ts @@ -42,11 +42,17 @@ interface nominal Array { } interface Math { + @DoNotIncludeThis sin(x: number): number performs const sin; + @DoNotIncludeThis cos(x: number): number performs const cos; + @DoNotIncludeThis tan(x: number): number performs const tan; + @DoNotIncludeThis floor(x: number): number performs const floor; + @DoNotIncludeThis sqrt(x: number): number performs const sqrt; + @DoNotIncludeThis cbrt(x: number): number performs const cbrt; // TODO newer method @@ -63,14 +69,17 @@ interface nominal string { } interface Console { + @DoNotIncludeThis log(msg: any): void; } interface JSON { // TODO any temp + @DoNotIncludeThis parse(input: string): any; // TODO any temp + @DoNotIncludeThis stringify(input: any): string; } @@ -79,8 +88,10 @@ interface Function { } interface Object { + @DoNotIncludeThis setPrototypeOf(on: object, to: object): object performs const set_prototype; + @DoNotIncludeThis getPrototypeOf(on: object): object | null performs const get_prototype; // create(prototype: object): object performs { diff --git a/checker/examples/check.rs b/checker/examples/check.rs index 857e9982..75661d14 100644 --- a/checker/examples/check.rs +++ b/checker/examples/check.rs @@ -2,7 +2,6 @@ fn main() { use ezno_checker::{check_project, synthesis}; use std::{ - collections::HashSet, env, fs, path::{Path, PathBuf}, }; @@ -21,7 +20,6 @@ fn main() { } }, None, - Default::default(), ); let args: Vec<_> = env::args().collect(); diff --git a/checker/specification/build.rs b/checker/specification/build.rs index ea26cf83..092ff4bf 100644 --- a/checker/specification/build.rs +++ b/checker/specification/build.rs @@ -48,7 +48,7 @@ fn markdown_lines_append_test_to_rust( writeln!(out, "}}").unwrap(); } first_section = false; - let section_heading = heading_to_rust_name(section_heading); + let section_heading = heading_to_rust_identifier(section_heading); writeln!(out, "mod {section_heading} {{").unwrap(); continue; } @@ -58,7 +58,7 @@ fn markdown_lines_append_test_to_rust( } let heading = line.strip_prefix("####").unwrap().trim_start(); - let test_title = heading_to_rust_name(heading); + let test_title = heading_to_rust_identifier(heading); let blocks = { let mut blocks = Vec::new(); @@ -75,7 +75,7 @@ fn markdown_lines_append_test_to_rust( if !code.trim().is_empty() { blocks.push(( current_filename.unwrap_or(DEFAULT_FILE_PATH), - mem::take(&mut code).replace('"', "\\\""), + mem::take(&mut code), )); } current_filename = Some(path); @@ -87,32 +87,31 @@ fn markdown_lines_append_test_to_rust( code.push_str(line); code.push('\n') } - // Escape " - let code = code.replace('"', "\\\""); blocks.push((current_filename.unwrap_or(DEFAULT_FILE_PATH), code)); blocks }; let errors = { let mut errors = Vec::new(); for (_, line) in lines.by_ref() { - if line.is_empty() || !line.starts_with('-') { - if !errors.is_empty() { - break; - } - } else { + if line.starts_with("#") { + panic!("block with no diagnostics or break between") + } else if line.starts_with('-') { let error = line.strip_prefix("- ").unwrap().replace('\\', "").replace('"', "\\\""); errors.push(format!("\"{}\"", error)) + } else if !errors.is_empty() { + break; } } errors }; let errors = errors.join(", "); + let heading_idx = heading_idx + 1; let code = blocks .into_iter() - .map(|(path, content)| format!("(\"{path}\",\"{content}\"),")) + .map(|(path, content)| format!("(\"{path}\",r#\"{content}\"#),")) .fold(String::new(), |mut acc, cur| { acc.push_str(&cur); acc @@ -132,7 +131,7 @@ fn markdown_lines_append_test_to_rust( Ok(()) } -fn heading_to_rust_name(heading: &str) -> String { +fn heading_to_rust_identifier(heading: &str) -> String { heading .replace([' ', '-', '&'], "_") .replace(['*', '\'', '`', '"', '!', '(', ')', ','], "") diff --git a/checker/specification/specification.md b/checker/specification/specification.md index 876045bf..003491f7 100644 --- a/checker/specification/specification.md +++ b/checker/specification/specification.md @@ -229,6 +229,16 @@ function createObject2(a: T, b: U): { a: U, b: U } { - Cannot return { a: T, b: U } because the function is expected to return { a: U, b: U } +#### Expected parameter from variable declaration + +> Technically works with inference but this method should be less overhead + produce better positioned errors + +```ts +const x: (a: string) => number = a => a.to; +``` + +- No property 'to' on string + ### Function calling #### Argument type against parameter @@ -330,6 +340,30 @@ obj.getA() satisfies 6; - Expected 6, found 5 +#### This passed around + +```ts +function getToUpperCase(s: string) { + return s.toUpperCase +} + +(getToUpperCase("hi")() satisfies "HEY") +``` + +- Expected "HEY", found "HI" + +#### This as generic argument + +```ts +function callToUpperCase(s: string) { + return s.toUpperCase() +} + +(callToUpperCase("hi") satisfies "HEY") +``` + +- Expected "HEY", found "HI" + ### Closures #### Reading variable @@ -631,18 +665,39 @@ const my_obj: { b: 3 } = { a: 2 } #### Getters ```ts -const b = { - get c() { - return 2 +let global = 0; +const object = { + get getValue() { + return global++ }, } -b.c satisfies string + +object.getValue satisfies string +object.getValue satisfies boolean ``` -- Expected string, found 2 +> Also test that side effects work here + +- Expected string, found 0 +- Expected boolean, found 1 + +#### Object spread + +```ts +const obj1 = { a: 2, b: 3 }; +const obj2 = { b: 4, ...obj1, a: 6 }; + +obj2.b satisfies 100; +obj2.a satisfies boolean; +``` + +- Expected 100, found 3 +- Expected boolean, found 6 #### Array pushing and pop-ing +> TODO maybe separate + ```ts const x = [1] x.push("hi") @@ -982,16 +1037,35 @@ interface X { b: boolean } -interface X { - c: number +{ + interface X { + c: number + } + + const x: X = { a: "field", b: false, c: false } + const y: X = { a: "field", b: false, c: 2 } } - -const x: X = { a: "field", b: false, c: false } -const y: X = { a: "field", b: false, c: 2 } ``` - Type { a: "field", b: false, c: false } is not assignable to type X +#### Interfaces do not merge with aliases + +```ts +type X = { a: string } + +{ + interface X { + b: number + } + + const x: X = { b: 3 } // Don't require 'a' here <- + const y: X = { b: "NaN" } +} +``` + +- Type { b: "NaN" } is not assignable to type X + ### Classes > TODO privacy @@ -1144,6 +1218,61 @@ getProp satisfies string - Expected string, found (obj: { prop: 3 } | { prop: 2 }) => 3 | 2 +#### Generic extends + +```ts +function getA(p: T) { + return p.a +} + +getA({ p: 2 }) +``` + +- Argument of type { p: 2 } is not assignable to parameter of type T + +> I think reasons contains more information + +#### Function subtyping + +```ts +// Perfectly fine +const x: (a: number) => string = (a: 4) => "hi" +// Bad +const y: (a: 4) => string = (a: number) => "hi" +``` + +- Type (a: number) => "hi" is not assignable to type (a: 4) => string + +> I think reasons contains more information + +#### Indexing into (fixed) type + +```ts +interface ThePrimitives { + a: number, + b: string, + c: boolean +} + +(2 satisfies ThePrimitives["b"]); +``` + +- Expected string, found 2 + +#### Indexing into (generic) type + +```ts +function getProp(t: T): T["prop"] { + return t.other +} + +function getOther(t: T): T["other"] { + return t.other +} +``` + +- Cannot return T["other"] because the function is expected to return T["prop"] + ### Prototypes #### Set prototype diff --git a/checker/specification/test.rs b/checker/specification/test.rs index eba4b68e..72ea11cf 100644 --- a/checker/specification/test.rs +++ b/checker/specification/test.rs @@ -26,7 +26,7 @@ mod specification { /// Called by each test fn check_errors( heading: &'static str, - line: usize, + _line: usize, // (Path, Content) code: &[(&'static str, &'static str)], expected_diagnostics: &[&'static str], @@ -48,7 +48,6 @@ fn check_errors( // TODO could test these let type_check_options = None; - let parse_options = Default::default(); // eprintln!("{:?}", code); @@ -69,7 +68,6 @@ fn check_errors( } }, type_check_options, - parse_options, ); // }); diff --git a/checker/specification/to_implement.md b/checker/specification/to_implement.md index 9fe0adf1..9254adfb 100644 --- a/checker/specification/to_implement.md +++ b/checker/specification/to_implement.md @@ -14,21 +14,6 @@ const b = 2; getB(); ``` -#### Spread arguments - -```ts -function spread(main, ...others) { - return { - main, - others, - } -} -spread(1, 2, 3) satisfies string -``` - -#TODO spread array -#TODO spread object - ### Types #### Resolving value by property on dependent @@ -332,28 +317,6 @@ myArray.push("hi") - Type "hi" is not assignable to type number -#### Index into array - -```ts -function getFirst(a: Array) { - return a[3] -} - -print_type(getFirst) -``` - -#### Array spread - -```ts -const array1 = [1, 2, 3]; -const array2 = [...array1, 4, 5, 6]; - -array2.length satisfies 6; -array2[2] satisfies string; -``` - -- Expected string, found 3 - #### Simple array map ```ts @@ -389,21 +352,6 @@ myTag`Hello ${name}` satisfies "Hi Ben" - Expected "Hi Ben", found "Hello Ben" -#### Object spread - -> parser currently broken - -```ts -const obj1 = { a: 2, b: 3 }; -const obj2 = { b: 4, ...obj1, a: 6 }; - -obj1.b satisfies 100; -obj1.a satisfies bool; -``` - -- Expected 100, found 3 -- Expected bool, found 6 - #### Bad arithmetic operator > This is allowed under non strict casts option (and will return NaN) but the tests run with strict casts on @@ -428,22 +376,41 @@ map(a => a.t) > No property t on string -#### Index - -> Panics specialising property +#### Spread arguments ```ts -function getFirst(array: Array) { - return array[0] +function spread(main, ...others) { + return { + main, + others, + } } -print_type(getFirst) +spread(1, 2, 3) satisfies string ``` -#### Expected +- TODO... + +#### Array spread ```ts -const x: (a: string) => number = (a) => a.to; +const array1 = [1, 2, 3]; +const array2 = [...array1, 4, 5, 6]; + +array2.length satisfies 6; +array2[2] satisfies string; ``` -> No property to on string +- Expected string, found 3 + +#### Index into array + +```ts +function getFirst(a: Array) { + return a[3] +} + +print_type(getFirst) +``` + +- TODO diff --git a/checker/src/behavior/assignments.rs b/checker/src/behavior/assignments.rs index 6fcdb12c..aa8e8fce 100644 --- a/checker/src/behavior/assignments.rs +++ b/checker/src/behavior/assignments.rs @@ -42,7 +42,7 @@ impl Reference { #[must_use] pub fn get_position(&self) -> SpanWithSource { match self { - Reference::Variable(_, span) | Reference::Property { span, .. } => span.clone(), + Reference::Variable(_, span) | Reference::Property { span, .. } => *span, } } } diff --git a/checker/src/behavior/functions.rs b/checker/src/behavior/functions.rs index 111668c2..50a0376f 100644 --- a/checker/src/behavior/functions.rs +++ b/checker/src/behavior/functions.rs @@ -1,22 +1,29 @@ -use std::{collections::HashMap, mem}; +use std::{ + borrow::Cow, + collections::{hash_map::Entry, HashMap}, + mem, +}; use source_map::{SourceId, Span, SpanWithSource}; use super::variables::VariableMutability; use crate::{ context::{ + environment::FunctionScope, facts::{Facts, Publicity}, - Context, ContextType, + get_value_of_variable, CanReferenceThis, Context, ContextType, Syntax, }, - events::Event, + events::{Event, RootReference}, types::{ + self, classes::ClassValue, functions::SynthesisedParameters, poly_types::GenericTypeParameters, properties::{PropertyKey, PropertyValue}, - FunctionType, TypeStore, + Constructor, FunctionType, PolyNature, TypeStore, }, - ASTImplementation, CheckingData, Environment, FunctionId, ReadFromFS, Type, TypeId, VariableId, + ASTImplementation, CheckingData, Environment, FunctionId, ReadFromFS, Scope, Type, TypeId, + VariableId, }; #[derive(Clone, Copy, Debug, Default, binary_serialize_derive::BinarySerializable)] @@ -53,12 +60,12 @@ pub enum GetterSetter { None, } -pub fn register_arrow_function( +pub fn register_arrow_function( expecting: TypeId, is_async: bool, - function: &impl SynthesisableFunction, + function: &impl SynthesisableFunction, environment: &mut Environment, - checking_data: &mut CheckingData, + checking_data: &mut CheckingData, ) -> TypeId { let function_type = environment.new_function( checking_data, @@ -68,14 +75,14 @@ pub fn register_arrow_function( +pub fn register_expression_function( expecting: TypeId, is_async: bool, is_generator: bool, location: Option, - function: &impl SynthesisableFunction, + function: &impl SynthesisableFunction, environment: &mut Environment, - checking_data: &mut CheckingData, + checking_data: &mut CheckingData, ) -> TypeId { let function_type = environment.new_function( checking_data, @@ -90,14 +97,14 @@ pub fn register_expression_function( +pub fn synthesise_hoisted_statement_function( variable_id: crate::VariableId, is_async: bool, is_generator: bool, location: Option, - function: &impl SynthesisableFunction, + function: &impl SynthesisableFunction, environment: &mut Environment, - checking_data: &mut CheckingData, + checking_data: &mut CheckingData, ) { // TODO get existing by variable_id let behavior = crate::behavior::functions::FunctionRegisterBehavior::StatementFunction { @@ -162,7 +169,7 @@ impl FunctionBehavior { } /// Covers both actual functions and -pub trait SynthesisableFunction { +pub trait SynthesisableFunction { fn id(&self, source_id: SourceId) -> FunctionId; // TODO temp @@ -172,46 +179,46 @@ pub trait SynthesisableFunction { fn type_parameters( &self, environment: &mut Environment, - checking_data: &mut CheckingData, + checking_data: &mut CheckingData, ) -> Option; fn this_constraint( &self, environment: &mut Environment, - checking_data: &mut CheckingData, + checking_data: &mut CheckingData, ) -> Option; /// For object literals fn super_constraint( &self, environment: &mut Environment, - checking_data: &mut CheckingData, + checking_data: &mut CheckingData, ) -> Option; /// **THIS FUNCTION IS EXPECTED TO PUT THE PARAMETERS INTO THE ENVIRONMENT WHILE SYNTHESISING THEM** fn parameters( &self, environment: &mut Environment, - checking_data: &mut CheckingData, - expected_parameters: Option, + checking_data: &mut CheckingData, + expected_parameters: Option<&SynthesisedParameters>, ) -> SynthesisedParameters; fn return_type_annotation( &self, environment: &mut Environment, - checking_data: &mut CheckingData, + checking_data: &mut CheckingData, ) -> Option<(TypeId, SpanWithSource)>; /// Returned type is extracted from events, thus doesn't expect anything in return fn body( &self, environment: &mut Environment, - checking_data: &mut CheckingData, + checking_data: &mut CheckingData, ); } -/// TODO might be generic if `FunctionBehavior` becomes generic -pub enum FunctionRegisterBehavior<'a, M: crate::ASTImplementation> { +/// TODO might be generic if [`FunctionBehavior`] becomes generic +pub enum FunctionRegisterBehavior<'a, A: crate::ASTImplementation> { ArrowFunction { expecting: TypeId, is_async: bool, @@ -245,13 +252,13 @@ pub enum FunctionRegisterBehavior<'a, M: crate::ASTImplementation> { prototype: TypeId, /// Is this is_some then can use `super()` super_type: Option, - properties: ClassPropertiesToRegister<'a, M>, + properties: ClassPropertiesToRegister<'a, A>, }, } -pub struct ClassPropertiesToRegister<'a, M: ASTImplementation>(pub Vec>); +pub struct ClassPropertiesToRegister<'a, A: crate::ASTImplementation>(pub Vec>); -impl<'a, M: crate::ASTImplementation> FunctionRegisterBehavior<'a, M> { +impl<'a, A: crate::ASTImplementation> FunctionRegisterBehavior<'a, A> { #[must_use] pub fn is_async(&self) -> bool { match self { @@ -288,3 +295,365 @@ pub trait ClosureChain { where T: Fn(ClosureId) -> Option; } + +pub(crate) fn register_function( + context: &mut Environment, + behavior: FunctionRegisterBehavior, + function: &F, + checking_data: &mut CheckingData, +) -> FunctionType +where + T: crate::ReadFromFS, + A: crate::ASTImplementation, + F: SynthesisableFunction, +{ + let is_async = behavior.is_async(); + let is_generator = behavior.is_generator(); + + let (mut behavior, scope, constructor, location, expected_parameters, expected_return) = + match behavior { + FunctionRegisterBehavior::Constructor { super_type, prototype, properties } => ( + FunctionBehavior::Constructor { + non_super_prototype: super_type.is_some().then_some(prototype), + this_object_type: TypeId::ERROR_TYPE, + }, + FunctionScope::Constructor { + extends: super_type.is_some(), + type_of_super: super_type, + this_object_type: TypeId::ERROR_TYPE, + }, + Some((prototype, properties)), + None, + None, + None, + ), + FunctionRegisterBehavior::ArrowFunction { expecting, is_async } => { + crate::utils::notify!("expecting {:?}", expecting); + let (expecting_parameters, expected_return) = + if let Type::FunctionReference(func_id, _) = + checking_data.types.get_type_by_id(expecting) + { + let f = checking_data.types.get_function_from_id(*func_id); + (Some(f.parameters.clone()), Some(f.return_type)) + } else { + (None, None) + }; + + ( + FunctionBehavior::ArrowFunction { is_async }, + // to set + FunctionScope::ArrowFunction { free_this_type: TypeId::ERROR_TYPE, is_async }, + None, + None, + expecting_parameters, + expected_return, + ) + } + FunctionRegisterBehavior::ExpressionFunction { + expecting, + is_async, + is_generator, + location, + } => ( + FunctionBehavior::Function { + is_async, + is_generator, + free_this_id: TypeId::ERROR_TYPE, + }, + FunctionScope::Function { + is_generator, + is_async, + // to set + this_type: TypeId::ERROR_TYPE, + type_of_super: TypeId::ANY_TYPE, + }, + None, + location, + None, + None, + ), + FunctionRegisterBehavior::StatementFunction { + hoisted, + is_async, + is_generator, + location, + } => ( + FunctionBehavior::Function { + is_async, + is_generator, + free_this_id: TypeId::ERROR_TYPE, + }, + FunctionScope::Function { + is_generator, + is_async, + this_type: TypeId::ERROR_TYPE, + type_of_super: TypeId::ERROR_TYPE, + }, + None, + location, + None, + None, + ), + FunctionRegisterBehavior::ObjectMethod { is_async, is_generator } => ( + FunctionBehavior::Method { + is_async, + is_generator, + free_this_id: TypeId::ERROR_TYPE, + }, + FunctionScope::MethodFunction { + free_this_type: TypeId::ERROR_TYPE, + is_async, + is_generator, + }, + None, + None, + None, + None, + ), + FunctionRegisterBehavior::ClassMethod { is_async, is_generator, super_type } => ( + FunctionBehavior::Method { + is_async, + is_generator, + free_this_id: TypeId::ERROR_TYPE, + }, + // TODO eager super + FunctionScope::MethodFunction { + free_this_type: TypeId::ERROR_TYPE, + is_async, + is_generator, + }, + None, + None, + None, + None, + ), + }; + + let mut function_environment = context.new_lexical_environment(Scope::Function(scope)); + + let type_parameters = function.type_parameters(&mut function_environment, checking_data); + + // TODO should be in function, but then requires mutable environment :( + let this_constraint = function.this_constraint(&mut function_environment, checking_data); + + // `this` changes stuff + if let Scope::Function(ref mut scope) = function_environment.context_type.scope { + match scope { + FunctionScope::ArrowFunction { ref mut free_this_type, .. } + | FunctionScope::MethodFunction { ref mut free_this_type, .. } => { + let type_id = if let Some(tc) = this_constraint { + checking_data.types.register_type(Type::RootPolyType( + PolyNature::FreeVariable { reference: RootReference::This, based_on: tc }, + )) + } else { + TypeId::ANY_INFERRED_FREE_THIS + }; + if let FunctionBehavior::Method { ref mut free_this_id, .. } = behavior { + *free_this_id = type_id; + } + *free_this_type = type_id; + } + FunctionScope::Function { ref mut this_type, .. } => { + // TODO this could be done conditionally to create less objects, but also doesn't introduce any bad side effects so + // TODO prototype needs to be a poly based on this.prototype. This also fixes inference + + let (this_free_variable, this_constructed_object) = if let Some(this_constraint) = + this_constraint + { + // TODO I don't whether NEW_TARGET_ARG should have a backer + let prototype = checking_data.types.register_type(Type::Constructor( + Constructor::Property { + on: TypeId::NEW_TARGET_ARG, + under: PropertyKey::String(Cow::Owned("value".to_owned())), + result: this_constraint, + }, + )); + + let this_constructed_object = function_environment.facts.new_object( + Some(prototype), + &mut checking_data.types, + true, + ); + + let this_free_variable = checking_data.types.register_type(Type::RootPolyType( + PolyNature::FreeVariable { + reference: RootReference::This, + based_on: this_constraint, + }, + )); + + (this_free_variable, this_constructed_object) + } else { + // TODO inferred prototype + let this_constructed_object = + function_environment.facts.new_object(None, &mut checking_data.types, true); + (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 what is the union, shouldn't it be the this_constraint? + *this_type = new_conditional_type; + } + FunctionScope::Constructor { extends, type_of_super, ref mut this_object_type } => { + crate::utils::notify!("Setting 'this' type here"); + if let Some((prototype, properties)) = constructor { + let new_this_object_type = types::create_this_before_function_synthesis( + &mut checking_data.types, + &mut function_environment.facts, + prototype, + ); + + *this_object_type = new_this_object_type; + // TODO super/derived behavior + types::classes::register_properties_into_environment( + &mut function_environment, + new_this_object_type, + checking_data, + properties, + ); + function_environment.can_reference_this = CanReferenceThis::ConstructorCalled; + + if let FunctionBehavior::Constructor { ref mut this_object_type, .. } = behavior + { + crate::utils::notify!("Set this object type"); + *this_object_type = new_this_object_type; + } else { + unreachable!() + } + } else { + unreachable!() + } + } + } + } else { + unreachable!() + } + + // TODO reuse existing if hoisted or can be sent down + let synthesised_parameters = + function.parameters(&mut function_environment, checking_data, expected_parameters.as_ref()); + + let return_type_annotation = + function.return_type_annotation(&mut function_environment, checking_data); + + // let _expected_return_type: Option = expected_return; + function_environment.context_type.location = location; + + let returned = if function.has_body() { + function.body(&mut function_environment, checking_data); + // Temporary move events to satisfy borrow checker + let mut events = mem::take(&mut function_environment.facts.events); + + let returned = crate::events::helpers::get_return_from_events( + &mut events.iter(), + checking_data, + // TODO environment should be good enough, but needs environment not context + &mut function_environment, + return_type_annotation, + ); + function_environment.facts.events = events; + + match returned { + crate::events::helpers::ReturnedTypeFromBlock::ContinuedExecution => { + TypeId::UNDEFINED_TYPE + } + crate::events::helpers::ReturnedTypeFromBlock::ReturnedIf { when, returns } => { + checking_data.types.new_conditional_type(when, returns, TypeId::UNDEFINED_TYPE) + } + crate::events::helpers::ReturnedTypeFromBlock::Returned(ty) => ty, + } + } else { + return_type_annotation.map_or(TypeId::UNDEFINED_TYPE, |(left, _)| left) + }; + + let closes_over = function_environment + .context_type + .closed_over_references + .iter() + .map(|reference| { + let ty = match reference { + RootReference::Variable(on) => { + let get_value_of_variable = get_value_of_variable( + function_environment.facts_chain(), + *on, + None::<&crate::types::poly_types::FunctionTypeArguments>, + ); + get_value_of_variable.expect("value not assigned?") + } + // TODO not sure + RootReference::This => TypeId::ANY_INFERRED_FREE_THIS, + }; + + (reference.clone(), ty) + }) + .collect(); + + let Syntax { free_variables, closed_over_references: function_closes_over, .. } = + function_environment.context_type; + + let facts = function_environment.facts; + + context.variable_names.extend(function_environment.variable_names); + + // TODO temp ... + for (on, mut properties) in facts.current_properties { + match context.facts.current_properties.entry(on) { + Entry::Occupied(mut occupied) => {} + Entry::Vacant(vacant) => { + vacant.insert(properties); + } + } + } + + for (on, mut properties) in facts.closure_current_values { + match context.facts.closure_current_values.entry(on) { + Entry::Occupied(mut occupied) => {} + Entry::Vacant(vacant) => { + vacant.insert(properties); + } + } + } + + if let Some(closed_over_variables) = context.context_type.get_closed_over_references() { + closed_over_variables.extend(free_variables.iter().cloned()); + // TODO not sure, but fixes nesting + closed_over_variables.extend(function_closes_over.iter().cloned()); + } + + // TODO should references used in the function be counted in this scope + // might break the checking though + + let free_variables = free_variables + .into_iter() + .map(|reference| { + // TODO get the restriction from the context type + (reference, TypeId::ANY_TYPE) + }) + .collect(); + + let id = function.id(context.get_source()); + + FunctionType { + id, + constant_function: None, + behavior, + type_parameters, + parameters: synthesised_parameters, + return_type: returned, + effects: facts.events, + free_variables, + closed_over_variables: closes_over, + } +} diff --git a/checker/src/behavior/loops.rs b/checker/src/behavior/loops.rs new file mode 100644 index 00000000..f84625de --- /dev/null +++ b/checker/src/behavior/loops.rs @@ -0,0 +1,20 @@ +pub struct ValueWithEvents { + events: Vec, + value: TypeId, +} + +pub enum LoopBehavior { + While(TypeId), + For { + initializer: TypeId, + condition: ValueWithEvents, + afterthought: ValueWithEvents + } +} + +// TODO +// events -> get diff, start, end. (end - start).abs() / diff < options.loop_max + +fn evaluate() { + +} \ No newline at end of file diff --git a/checker/src/behavior/operations.rs b/checker/src/behavior/operations.rs index 05a34cbb..d70a0d57 100644 --- a/checker/src/behavior/operations.rs +++ b/checker/src/behavior/operations.rs @@ -36,11 +36,14 @@ pub enum PureBinaryOperation { } /// TODO report errors better here -pub fn evaluate_pure_binary_operation_handle_errors( +pub fn evaluate_pure_binary_operation_handle_errors< + T: crate::ReadFromFS, + A: crate::ASTImplementation, +>( (lhs, lhs_pos): (TypeId, SpanWithSource), operator: PureBinaryOperation, (rhs, rhs_pos): (TypeId, SpanWithSource), - checking_data: &mut CheckingData, + checking_data: &mut CheckingData, environment: &mut Environment, ) -> TypeId { match operator { @@ -326,7 +329,7 @@ pub fn evaluate_equality_inequality_operation( } } -#[derive(Clone, Debug)] +#[derive(Copy, Clone, Debug)] pub enum Logical { And, Or, @@ -335,32 +338,33 @@ pub enum Logical { /// TODO strict casts! pub fn evaluate_logical_operation_with_expression< + 'a, T: crate::ReadFromFS, - M: crate::ASTImplementation, + A: crate::ASTImplementation, >( lhs: TypeId, - operator: &Logical, - rhs: &M::Expression, - checking_data: &mut CheckingData, + operator: Logical, + rhs: &'a A::Expression<'a>, + checking_data: &mut CheckingData, environment: &mut Environment, ) -> Result { - enum TypeOrSynthesisable<'a, M: crate::ASTImplementation> { + enum TypeOrSynthesisable<'a, A: crate::ASTImplementation> { Type(TypeId), - Expression(&'a M::Expression), + Expression(&'a A::Expression<'a>), } - // impl<'a, M: crate::ASTImplementation> SynthesisableConditional for TypeOrSynthesisable<'a, M> { + // impl<'a, A: crate::ASTImplementation> SynthesisableConditional for TypeOrSynthesisable<'a, M> { // type ExpressionResult = TypeId; // fn synthesise_condition( // self, // environment: &mut Environment, - // checking_data: &mut CheckingData, + // checking_data: &mut CheckingData, // ) -> Self::ExpressionResult { // match self { // TypeOrSynthesisable::Type(ty) => ty, // TypeOrSynthesisable::Expression(expr, _) => { - // M::synthesise_expression(expr, TypeId::ANY_TYPE, environment, checking_data) + // A::synthesise_expression(expr, TypeId::ANY_TYPE, environment, checking_data) // } // } // } @@ -382,17 +386,17 @@ pub fn evaluate_logical_operation_with_expression< match operator { Logical::And => Ok(environment.new_conditional_context( lhs, - |env: &mut Environment, data: &mut CheckingData| { - M::synthesise_expression(rhs, TypeId::ANY_TYPE, env, data) + |env: &mut Environment, data: &mut CheckingData| { + A::synthesise_expression(rhs, TypeId::ANY_TYPE, env, data) }, - Some(|_env: &mut Environment, _data: &mut CheckingData| lhs), + Some(|_env: &mut Environment, _data: &mut CheckingData| lhs), checking_data, )), Logical::Or => Ok(environment.new_conditional_context( lhs, - |_env: &mut Environment, _data: &mut CheckingData| lhs, - Some(|env: &mut Environment, data: &mut CheckingData| { - M::synthesise_expression(rhs, TypeId::ANY_TYPE, env, data) + |_env: &mut Environment, _data: &mut CheckingData| lhs, + Some(|env: &mut Environment, data: &mut CheckingData| { + A::synthesise_expression(rhs, TypeId::ANY_TYPE, env, data) }), checking_data, )), @@ -406,10 +410,10 @@ pub fn evaluate_logical_operation_with_expression< )?; Ok(environment.new_conditional_context( is_lhs_null, - |env: &mut Environment, data: &mut CheckingData| { - M::synthesise_expression(rhs, TypeId::ANY_TYPE, env, data) + |env: &mut Environment, data: &mut CheckingData| { + A::synthesise_expression(rhs, TypeId::ANY_TYPE, env, data) }, - Some(|_env: &mut Environment, _data: &mut CheckingData| lhs), + Some(|_env: &mut Environment, _data: &mut CheckingData| lhs), checking_data, )) } diff --git a/checker/src/behavior/template_literal.rs b/checker/src/behavior/template_literal.rs index eaf32c78..455d1828 100644 --- a/checker/src/behavior/template_literal.rs +++ b/checker/src/behavior/template_literal.rs @@ -8,27 +8,30 @@ use crate::{ CheckingData, Constant, Environment, Instance, Type, TypeId, }; +#[derive(Copy, Clone)] pub enum TemplateLiteralPart<'a, T> { Static(&'a str), Dynamic(&'a T), } -pub fn synthesise_template_literal<'a, T, M>( +#[allow(clippy::needless_pass_by_value)] +pub fn synthesise_template_literal<'a, T, A>( tag: Option, - mut parts_iter: impl Iterator> + 'a, + mut parts_iter: impl Iterator>> + 'a, position: &Span, environment: &mut Environment, - checking_data: &mut CheckingData, + checking_data: &mut CheckingData, ) -> TypeId where T: crate::ReadFromFS, - M: crate::ASTImplementation, - M::Expression: 'a, + A: crate::ASTImplementation, + A::Expression<'a>: 'a, { - fn part_to_type( - first: &TemplateLiteralPart, + #[allow(clippy::needless_pass_by_value)] + fn part_to_type<'a, T: crate::ReadFromFS, A: crate::ASTImplementation>( + first: TemplateLiteralPart<'a, A::Expression<'a>>, environment: &mut Environment, - checking_data: &mut CheckingData, + checking_data: &mut CheckingData, ) -> crate::TypeId { match first { TemplateLiteralPart::Static(static_part) => { @@ -36,7 +39,7 @@ where } TemplateLiteralPart::Dynamic(expression) => { // TODO tidy - let value = M::synthesise_expression( + let value = A::synthesise_expression( expression, TypeId::ANY_TYPE, environment, @@ -67,7 +70,7 @@ where for part in parts_iter { match part { p @ TemplateLiteralPart::Static(_) => { - let value = part_to_type(&p, environment, checking_data); + let value = part_to_type(p, environment, checking_data); static_parts.append( environment, crate::context::facts::Publicity::Public, @@ -79,7 +82,7 @@ where static_part_count += 1; } p @ TemplateLiteralPart::Dynamic(_) => { - let ty = part_to_type(&p, environment, checking_data); + let ty = part_to_type(p, environment, checking_data); arguments.push(SynthesisedArgument::NonSpread { ty, // TODO position @@ -98,7 +101,7 @@ where }, ); - let call_site = position.clone().with_source(environment.get_source()); + let call_site = position.with_source(environment.get_source()); crate::types::calling::call_type_handle_errors( tag, CallingInput { @@ -115,9 +118,9 @@ where } else { // Bit weird but makes Rust happy if let Some(first) = parts_iter.next() { - let mut acc = part_to_type(&first, environment, checking_data); + let mut acc = part_to_type(first, environment, checking_data); for rest in parts_iter { - let other = part_to_type(&rest, environment, checking_data); + let other = part_to_type(rest, environment, checking_data); let result = super::operations::evaluate_mathematical_operation( acc, crate::behavior::operations::MathematicalAndBitwise::Add, diff --git a/checker/src/behavior/variables.rs b/checker/src/behavior/variables.rs index a467e94e..395f452f 100644 --- a/checker/src/behavior/variables.rs +++ b/checker/src/behavior/variables.rs @@ -74,16 +74,16 @@ pub enum VariableMutability { #[derive(Clone, Debug)] pub struct VariableWithValue(pub VariableOrImport, pub TypeId); -pub fn check_variable_initialization( +pub fn check_variable_initialization( (variable_declared_type, variable_declared_pos): (TypeId, SpanWithSource), (expression_type, expression_declared_pos): (TypeId, SpanWithSource), environment: &mut crate::context::Environment, - checking_data: &mut CheckingData, + checking_data: &mut CheckingData, ) { use crate::types::subtyping::{type_is_subtype, BasicEquality, SubTypeResult}; - let position = variable_declared_pos.clone(); - let mut basic_subtyping = BasicEquality { add_property_restrictions: true, position }; + let mut basic_subtyping = + BasicEquality { add_property_restrictions: true, position: variable_declared_pos }; let type_is_subtype = type_is_subtype( variable_declared_type, @@ -109,7 +109,7 @@ pub fn check_variable_initialization Self { + Target(Vec::new()) + } + pub(crate) fn new_conditional_target( &mut self, cb: impl for<'a> FnOnce(&'a mut Target) -> EarlyReturn, diff --git a/checker/src/context/environment.rs b/checker/src/context/environment.rs index 676ebb27..ff371bef 100644 --- a/checker/src/context/environment.rs +++ b/checker/src/context/environment.rs @@ -3,7 +3,9 @@ use std::collections::HashSet; use crate::{ behavior::{ + self, assignments::{Assignable, AssignmentKind, Reference}, + functions, modules::{Exported, ImportKind, NamePair}, objects::ObjectBuilder, operations::{ @@ -151,29 +153,31 @@ impl<'a> Environment<'a> { /// /// 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( + /// 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, + >( &mut self, lhs: Assignable, operator: AssignmentKind, // Can be `None` for increment and decrement - expression: Option<&M::Expression>, + expression: Option<&'b A::Expression<'b>>, assignment_span: SpanWithSource, - checking_data: &mut CheckingData, + checking_data: &mut CheckingData, ) -> TypeId { match lhs { Assignable::Reference(reference) => { /// Returns - fn get_reference( + fn get_reference( env: &mut Environment, reference: Reference, - checking_data: &mut CheckingData, + checking_data: &mut CheckingData, ) -> TypeId { match reference { Reference::Variable(name, position) => { - env.get_variable_handle_error(&name, position.clone(), checking_data) - .unwrap() - .1 + env.get_variable_handle_error(&name, position, checking_data).unwrap().1 } Reference::Property { on, with, publicity, span } => { let get_property_handle_errors = env.get_property_handle_errors( @@ -181,7 +185,7 @@ impl<'a> Environment<'a> { publicity, with, checking_data, - span.clone(), + span, ); match get_property_handle_errors { Ok(i) => i.get_value(), @@ -191,11 +195,11 @@ impl<'a> Environment<'a> { } } - fn set_reference( + fn set_reference( env: &mut Environment, reference: Reference, new: TypeId, - checking_data: &mut CheckingData, + checking_data: &mut CheckingData, ) -> Result { match reference { Reference::Variable(name, position) => Ok(env @@ -248,7 +252,7 @@ impl<'a> Environment<'a> { match operator { AssignmentKind::Assign => { - let new = M::synthesise_expression( + let new = A::synthesise_expression( expression.unwrap(), TypeId::ANY_TYPE, self, @@ -276,10 +280,9 @@ impl<'a> Environment<'a> { let existing = get_reference(self, reference.clone(), checking_data); let expression = expression.unwrap(); - let expression_pos = M::expression_position(expression) - .clone() - .with_source(self.get_source()); - let rhs = M::synthesise_expression( + let expression_pos = + A::expression_position(expression).with_source(self.get_source()); + let rhs = A::synthesise_expression( expression, TypeId::ANY_TYPE, self, @@ -361,7 +364,7 @@ impl<'a> Environment<'a> { let expression = expression.unwrap(); let new = evaluate_logical_operation_with_expression( existing, - &operator, + operator, expression, checking_data, self, @@ -392,12 +395,26 @@ impl<'a> Environment<'a> { } } - pub fn assign_to_variable_handle_errors( + pub fn new_function( + &mut self, + checking_data: &mut CheckingData, + function: &F, + behavior: functions::FunctionRegisterBehavior, + ) -> crate::types::FunctionType + where + U: crate::ReadFromFS, + A: crate::ASTImplementation, + F: functions::SynthesisableFunction, + { + functions::register_function(self, behavior, function, checking_data) + } + + pub fn assign_to_variable_handle_errors( &mut self, variable_name: &str, assignment_position: SpanWithSource, new_type: TypeId, - checking_data: &mut CheckingData, + checking_data: &mut CheckingData, ) -> TypeId { let result = self.assign_to_variable( variable_name, @@ -432,7 +449,7 @@ impl<'a> Environment<'a> { VariableOrImport::Variable { mutability, declared_at, context } => { match mutability { VariableMutability::Constant => { - Err(AssignmentError::Constant(declared_at.clone())) + Err(AssignmentError::Constant(*declared_at)) } VariableMutability::Mutable { reassignment_constraint } => { let variable = variable.clone(); @@ -441,7 +458,7 @@ impl<'a> Environment<'a> { // TODO tuple with position: let mut basic_subtyping = BasicEquality { add_property_restrictions: false, - position: declared_at.clone(), + position: *declared_at, }; let result = type_is_subtype( reassignment_constraint, @@ -466,8 +483,8 @@ impl<'a> Environment<'a> { false, ), // TODO split - variable_site: assignment_position.clone(), - value_site: assignment_position.clone(), + variable_site: assignment_position, + value_site: assignment_position, }); } } @@ -506,11 +523,6 @@ impl<'a> Environment<'a> { } } - #[must_use] - pub fn get_environment_id(&self) -> super::ContextId { - self.context_id - } - pub(crate) fn get_environment_type(&self) -> &Scope { &self.context_type.scope } @@ -589,22 +601,16 @@ impl<'a> Environment<'a> { ) } - pub fn get_property_handle_errors( + pub fn get_property_handle_errors( &mut self, on: TypeId, publicity: Publicity, key: PropertyKey, - checking_data: &mut CheckingData, + checking_data: &mut CheckingData, site: SpanWithSource, ) -> Result { - let get_property = self.get_property( - on, - publicity, - key.clone(), - &mut checking_data.types, - None, - site.clone(), - ); + let get_property = + self.get_property(on, publicity, key.clone(), &mut checking_data.types, None, site); if let Some((kind, result)) = get_property { Ok(match kind { PropertyKind::Getter => Instance::GValue(result), @@ -638,11 +644,11 @@ impl<'a> Environment<'a> { } } - pub fn get_variable_handle_error( + pub fn get_variable_handle_error( &mut self, name: &str, position: SpanWithSource, - checking_data: &mut CheckingData, + checking_data: &mut CheckingData, ) -> Result { let (in_root, crossed_boundary, og_var) = { let this = self.get_variable_unbound(name); @@ -809,16 +815,17 @@ impl<'a> Environment<'a> { } } - pub(crate) fn new_conditional_context( + pub(crate) fn new_conditional_context( &mut self, condition: TypeId, - then_evaluate: impl FnOnce(&mut Environment, &mut CheckingData) -> R, - else_evaluate: Option) -> R>, - checking_data: &mut CheckingData, + then_evaluate: impl FnOnce(&mut Environment, &mut CheckingData) -> R, + else_evaluate: Option) -> R>, + checking_data: &mut CheckingData, ) -> R where - M: crate::ASTImplementation, + A: crate::ASTImplementation, R: TypeCombinable, + T: crate::ReadFromFS, { if let Decidable::Known(result) = is_type_truthy_falsy(condition, &checking_data.types) { // TODO emit warning @@ -914,14 +921,14 @@ impl<'a> Environment<'a> { 'b, P: Iterator>, T: crate::ReadFromFS, - M: ASTImplementation, + A: crate::ASTImplementation, >( &mut self, partial_import_path: &str, import_position: Span, default_import: Option<(&str, Span)>, kind: ImportKind<'b, P>, - checking_data: &mut CheckingData, + checking_data: &mut CheckingData, also_export: bool, ) { let current_source = self.get_source(); @@ -947,7 +954,7 @@ impl<'a> Environment<'a> { let id = crate::VariableId(current_source, position.start); let v = VariableOrImport::ConstantImport { to: None, - import_specified_at: position.clone().with_source(current_source), + import_specified_at: position.with_source(current_source), }; self.facts.variable_current_value.insert(id, *item); let existing = self.variables.insert(default_name.to_owned(), v); @@ -1002,7 +1009,6 @@ impl<'a> Environment<'a> { constant, import_specified_at: part .position - .clone() .with_source(self.get_source()), }; let existing = self.variables.insert(part.r#as.to_owned(), v); @@ -1031,7 +1037,7 @@ impl<'a> Environment<'a> { checking_data.diagnostics_container.add_error( TypeCheckError::FieldNotExported { file: partial_import_path, - position: position.clone(), + position, importing: part.value, }, ); diff --git a/checker/src/context/mod.rs b/checker/src/context/mod.rs index a288a809..743afba6 100644 --- a/checker/src/context/mod.rs +++ b/checker/src/context/mod.rs @@ -24,7 +24,9 @@ use crate::{ operations::MathematicalAndBitwise, variables::{VariableMutability, VariableOrImport}, }, - diagnostics::{CannotRedeclareVariable, TypeCheckError, TypeStringRepresentation}, + diagnostics::{ + CannotRedeclareVariable, TypeCheckError, TypeCheckWarning, TypeStringRepresentation, + }, events::{Event, RootReference}, subtyping::{type_is_subtype, BasicEquality}, types::{ @@ -403,15 +405,15 @@ impl Context { } } - pub fn register_variable_handle_error( + pub fn register_variable_handle_error( &mut self, name: &str, declared_at: SpanWithSource, behavior: VariableRegisterBehavior, - checking_data: &mut CheckingData, + checking_data: &mut CheckingData, ) -> TypeId { if let Ok(ty) = - self.register_variable(name, declared_at.clone(), behavior, &mut checking_data.types) + self.register_variable(name, declared_at, behavior, &mut checking_data.types) { ty } else { @@ -936,367 +938,10 @@ impl Context { } } - pub fn new_function( + pub fn new_try_context( &mut self, - checking_data: &mut CheckingData, - function: &F, - behavior: FunctionRegisterBehavior, - ) -> FunctionType - where - U: crate::ReadFromFS, - M: crate::ASTImplementation, - F: behavior::functions::SynthesisableFunction, - { - let is_async = behavior.is_async(); - let is_generator = behavior.is_generator(); - - let (mut behavior, scope, constructor, location, expected_parameters, expected_return) = - match behavior { - FunctionRegisterBehavior::Constructor { super_type, prototype, properties } => ( - FunctionBehavior::Constructor { - non_super_prototype: super_type.is_some().then_some(prototype), - this_object_type: TypeId::ERROR_TYPE, - }, - FunctionScope::Constructor { - extends: super_type.is_some(), - type_of_super: super_type, - this_object_type: TypeId::ERROR_TYPE, - }, - Some((prototype, properties)), - None, - None, - None, - ), - FunctionRegisterBehavior::ArrowFunction { expecting, is_async } => ( - FunctionBehavior::ArrowFunction { is_async }, - // to set - FunctionScope::ArrowFunction { free_this_type: TypeId::ERROR_TYPE, is_async }, - None, - None, - None, - None, - ), - FunctionRegisterBehavior::ExpressionFunction { - expecting, - is_async, - is_generator, - location, - } => ( - FunctionBehavior::Function { - is_async, - is_generator, - free_this_id: TypeId::ERROR_TYPE, - }, - FunctionScope::Function { - is_generator, - is_async, - // to set - this_type: TypeId::ERROR_TYPE, - type_of_super: TypeId::ANY_TYPE, - }, - None, - location, - None, - None, - ), - FunctionRegisterBehavior::StatementFunction { - hoisted, - is_async, - is_generator, - location, - } => ( - FunctionBehavior::Function { - is_async, - is_generator, - free_this_id: TypeId::ERROR_TYPE, - }, - FunctionScope::Function { - is_generator, - is_async, - this_type: TypeId::ERROR_TYPE, - type_of_super: TypeId::ERROR_TYPE, - }, - None, - location, - None, - None, - ), - FunctionRegisterBehavior::ObjectMethod { is_async, is_generator } => ( - FunctionBehavior::Method { - is_async, - is_generator, - free_this_id: TypeId::ERROR_TYPE, - }, - FunctionScope::MethodFunction { - free_this_type: TypeId::ERROR_TYPE, - is_async, - is_generator, - }, - None, - None, - None, - None, - ), - FunctionRegisterBehavior::ClassMethod { is_async, is_generator, super_type } => ( - FunctionBehavior::Method { - is_async, - is_generator, - free_this_id: TypeId::ERROR_TYPE, - }, - // TODO eager super - FunctionScope::MethodFunction { - free_this_type: TypeId::ERROR_TYPE, - is_async, - is_generator, - }, - None, - None, - None, - None, - ), - }; - - let mut function_environment = self.new_lexical_environment(Scope::Function(scope)); - - let type_parameters = function.type_parameters(&mut function_environment, checking_data); - - // TODO should be in function, but then requires mutable environment :( - let this_constraint = function.this_constraint(&mut function_environment, checking_data); - if let Scope::Function(ref mut scope) = function_environment.context_type.scope { - match scope { - FunctionScope::ArrowFunction { ref mut free_this_type, .. } - | FunctionScope::MethodFunction { ref mut free_this_type, .. } => { - let type_id = if let Some(tc) = this_constraint { - checking_data.types.register_type(Type::RootPolyType( - PolyNature::FreeVariable { - reference: RootReference::This, - based_on: tc, - }, - )) - } else { - TypeId::ANY_INFERRED_FREE_THIS - }; - if let FunctionBehavior::Method { ref mut free_this_id, .. } = behavior { - *free_this_id = type_id; - } - *free_this_type = type_id; - } - FunctionScope::Function { ref mut this_type, .. } => { - // TODO this could be done conditionally to create less objects, but also doesn't introduce any bad side effects so - // TODO prototype needs to be a poly based on this.prototype. This also fixes inference - - let (this_free_variable, this_constructed_object) = - if let Some(this_constraint) = this_constraint { - // TODO I don't whether NEW_TARGET_ARG should have a backer - let prototype = checking_data.types.register_type(Type::Constructor( - Constructor::Property { - on: TypeId::NEW_TARGET_ARG, - under: PropertyKey::String(Cow::Owned("value".to_owned())), - result: this_constraint, - }, - )); - - let this_constructed_object = function_environment.facts.new_object( - Some(prototype), - &mut checking_data.types, - true, - ); - - let this_free_variable = checking_data.types.register_type( - Type::RootPolyType(PolyNature::FreeVariable { - reference: RootReference::This, - based_on: this_constraint, - }), - ); - - (this_free_variable, this_constructed_object) - } else { - // TODO inferred prototype - let this_constructed_object = function_environment.facts.new_object( - None, - &mut checking_data.types, - true, - ); - (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 what is the union, shouldn't it be the this_constraint? - *this_type = new_conditional_type; - } - FunctionScope::Constructor { extends, type_of_super, ref mut this_object_type } => { - crate::utils::notify!("Setting 'this' type here"); - if let Some((prototype, properties)) = constructor { - let new_this_object_type = create_this_before_function_synthesis( - &mut checking_data.types, - &mut function_environment.facts, - prototype, - ); - - *this_object_type = new_this_object_type; - // TODO super/derived behavior - types::classes::register_properties_into_environment( - &mut function_environment, - new_this_object_type, - checking_data, - properties, - ); - function_environment.can_reference_this = - CanReferenceThis::ConstructorCalled; - - if let FunctionBehavior::Constructor { ref mut this_object_type, .. } = - behavior - { - crate::utils::notify!("Set this object type"); - *this_object_type = new_this_object_type; - } else { - unreachable!() - } - } else { - unreachable!() - } - } - } - } else { - unreachable!() - } - - // TODO reuse existing if hoisted or can be sent down - let synthesised_parameters = - function.parameters(&mut function_environment, checking_data, expected_parameters); - - let return_type_annotation = - function.return_type_annotation(&mut function_environment, checking_data); - - // TODO: - #[allow(clippy::no_effect_underscore_binding)] - let _expected_return_type: Option = expected_return; - function_environment.context_type.location = location; - - let returned = if function.has_body() { - function.body(&mut function_environment, checking_data); - // Temporary move events to satisfy borrow checker - let mut events = mem::take(&mut function_environment.facts.events); - - let returned = crate::events::helpers::get_return_from_events( - &mut events.iter(), - checking_data, - // TODO environment should be good enough, but needs environment not context - &mut function_environment, - return_type_annotation, - ); - function_environment.facts.events = events; - - match returned { - crate::events::helpers::ReturnedTypeFromBlock::ContinuedExecution => { - TypeId::UNDEFINED_TYPE - } - crate::events::helpers::ReturnedTypeFromBlock::ReturnedIf { when, returns } => { - checking_data.types.new_conditional_type(when, returns, TypeId::UNDEFINED_TYPE) - } - crate::events::helpers::ReturnedTypeFromBlock::Returned(ty) => ty, - } - } else { - return_type_annotation.map_or(TypeId::UNDEFINED_TYPE, |(left, _)| left) - }; - - let closes_over = function_environment - .context_type - .closed_over_references - .iter() - .map(|reference| { - let ty = match reference { - RootReference::Variable(on) => { - let get_value_of_variable = get_value_of_variable( - function_environment.facts_chain(), - *on, - None::<&crate::types::poly_types::FunctionTypeArguments>, - ); - get_value_of_variable.expect("value not assigned?") - } - // TODO not sure - RootReference::This => TypeId::ANY_INFERRED_FREE_THIS, - }; - - (reference.clone(), ty) - }) - .collect(); - - let Syntax { free_variables, closed_over_references: function_closes_over, .. } = - function_environment.context_type; - - let facts = function_environment.facts; - - self.variable_names.extend(function_environment.variable_names); - - // TODO temp ... - for (on, mut properties) in facts.current_properties { - match self.facts.current_properties.entry(on) { - hash_map::Entry::Occupied(mut occupied) => {} - hash_map::Entry::Vacant(vacant) => { - vacant.insert(properties); - } - } - } - - for (on, mut properties) in facts.closure_current_values { - match self.facts.closure_current_values.entry(on) { - hash_map::Entry::Occupied(mut occupied) => {} - hash_map::Entry::Vacant(vacant) => { - vacant.insert(properties); - } - } - } - - if let Some(closed_over_variables) = self.context_type.get_closed_over_references() { - closed_over_variables.extend(free_variables.iter().cloned()); - // TODO not sure, but fixes nesting - closed_over_variables.extend(function_closes_over.iter().cloned()); - } - - // TODO should references used in the function be counted in this scope - // might break the checking though - - let free_variables = free_variables - .into_iter() - .map(|reference| { - // TODO get the restriction from the context type - (reference, TypeId::ANY_TYPE) - }) - .collect(); - - let id = function.id(self.get_source()); - FunctionType { - id, - constant_function: None, - behavior, - type_parameters, - parameters: synthesised_parameters, - return_type: returned, - effects: facts.events, - free_variables, - closed_over_variables: closes_over, - } - - // register_behavior.afterwards(function, func_ty, self, &mut checking_data.types) - } - - pub fn new_try_context( - &mut self, - checking_data: &mut CheckingData, - func: impl for<'a> FnOnce(&'a mut Environment, &'a mut CheckingData), + checking_data: &mut CheckingData, + func: impl for<'a> FnOnce(&'a mut Environment, &'a mut CheckingData), ) -> TypeId { let (thrown, ..) = self.new_lexical_environment_fold_into_parent( Scope::TryBlock {}, @@ -1330,12 +975,12 @@ impl Context { pub fn new_lexical_environment_fold_into_parent< U: crate::ReadFromFS, Res, - M: crate::ASTImplementation, + A: crate::ASTImplementation, >( &mut self, scope: Scope, - checking_data: &mut CheckingData, - cb: impl for<'a> FnOnce(&'a mut Environment, &'a mut CheckingData) -> Res, + checking_data: &mut CheckingData, + cb: impl for<'a> FnOnce(&'a mut Environment, &'a mut CheckingData) -> Res, ) -> (Res, Option<(Vec, ClosedOverReferencesInScope)>, ContextId) { if matches!(scope, Scope::Conditional { .. }) { unreachable!("Use Environment::new_conditional_context") @@ -1382,24 +1027,18 @@ impl Context { // Run any truths through subtyping let additional = match scope { - Scope::Conditional { .. } => { - crate::utils::notify!("TODO scoping stuff"); - crate::utils::notify!("What about deferred function constraints"); - - todo!("events should be substituted"); - None - // Some((events, closed_over_references)) - } - Scope::Looping { .. } => todo!(), - Scope::Function { .. } | Scope::FunctionAnnotation {} => { - // self.proofs.merge(proofs); - - // crate::utils::notify!( - // "Function properties settings temp, breaks interfaces nesting, otherwise fine" - // ); - self.facts.current_properties.extend(facts.current_properties); + // TODO these might go + Scope::FunctionAnnotation {} => None, + // TODO temp + Scope::Function(FunctionScope::Constructor { .. }) | Scope::Looping { .. } => { Some((facts.events, used_parent_references)) } + Scope::Function { .. } => { + unreachable!("use new_function") + } + Scope::Conditional { .. } => { + unreachable!("use new_conditional") + } // TODO Scope::Module ?? Scope::InterfaceEnvironment { .. } | Scope::TypeAlias @@ -1491,11 +1130,11 @@ impl Context { } } - pub fn get_type_by_name_handle_errors( + pub fn get_type_by_name_handle_errors( &self, name: &str, pos: SpanWithSource, - checking_data: &mut CheckingData, + checking_data: &mut CheckingData, ) -> TypeId { if let Some(val) = self.get_type_from_name(name) { val @@ -1509,25 +1148,51 @@ impl Context { } /// TODO extends - pub fn new_interface( + pub fn new_interface<'a, U: crate::ReadFromFS, A: crate::ASTImplementation>( &mut self, name: &str, nominal: bool, - parameters: Option<&[M::TypeParameter]>, - extends: Option<&[M::TypeAnnotation]>, - position: &SpanWithSource, - types: &mut TypeStore, + parameters: Option<&'a [A::TypeParameter<'a>]>, + extends: Option<&'a [A::TypeAnnotation<'a>]>, + position: SpanWithSource, + checking_data: &mut CheckingData, ) -> TypeId { + 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; + }; + // TODO declare here let parameters = parameters.map(|parameters| { parameters .iter() .map(|parameter| { let ty = Type::RootPolyType(PolyNature::Generic { - name: M::type_parameter_name(parameter).to_owned(), + name: A::type_parameter_name(parameter).to_owned(), eager_fixed: TypeId::ANY_TYPE, }); - types.register_type(ty) + checking_data.types.register_type(ty) }) .collect() }); @@ -1536,35 +1201,26 @@ impl Context { todo!("synthesise, fold into Type::And and create alias type") } - let ty = Type::NamedRooted { nominal, name: name.to_owned(), parameters }; - - let interface_ty = types.register_type(ty); - - // Interface merging! - let existing = - self.parents_iter().find_map(|env| get_on_ctx!(env.named_types.get(name))).copied(); - - if let Some(existing) = existing { - existing - } else { - let existing_type = self.named_types.insert(name.to_owned(), interface_ty); - interface_ty - } + let ty = Type::Interface { nominal, name: name.to_owned(), parameters }; + let interface_ty = checking_data.types.register_type(ty); + self.named_types.insert(name.to_owned(), interface_ty); + interface_ty } - pub fn new_alias( + pub fn new_alias<'a, U: crate::ReadFromFS, A: crate::ASTImplementation>( &mut self, name: &str, - parameters: Option<&[M::TypeParameter]>, - to: &M::TypeAnnotation, - checking_data: &mut CheckingData, + parameters: Option<&'a [A::TypeParameter<'a>]>, + to: &'a A::TypeAnnotation<'a>, + position: Span, + checking_data: &mut CheckingData, ) -> TypeId { 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 = M::type_parameter_name(parameter).to_owned(); + let name = A::type_parameter_name(parameter).to_owned(); let ty = Type::RootPolyType(PolyNature::Generic { name: name.clone(), eager_fixed: TypeId::ANY_TYPE, @@ -1576,11 +1232,11 @@ impl Context { }) .collect(); - let to = M::synthesise_type_annotation(to, &mut env, checking_data); + let to = A::synthesise_type_annotation(to, &mut env, checking_data); (Some(parameters), to) } else { // TODO should just use self - let to = M::synthesise_type_annotation(to, &mut env, checking_data); + let to = A::synthesise_type_annotation(to, &mut env, checking_data); (None, to) }; @@ -1590,7 +1246,11 @@ impl Context { let existing_type = self.named_types.insert(name.to_owned(), alias_ty); if existing_type.is_some() { - panic!() + 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 } diff --git a/checker/src/context/root.rs b/checker/src/context/root.rs index c08be30d..d77ce78b 100644 --- a/checker/src/context/root.rs +++ b/checker/src/context/root.rs @@ -78,24 +78,24 @@ impl RootContext { } } - pub fn new_module_context<'a, T: crate::ReadFromFS, M: crate::ASTImplementation>( + pub fn new_module_context<'a, T: crate::ReadFromFS, A: crate::ASTImplementation>( &self, source: SourceId, - module: M::Module, - checking_data: &'a mut CheckingData, - ) -> &'a SynthesisedModule { + module: A::Module<'static>, + checking_data: &'a mut CheckingData, + ) -> &'a SynthesisedModule { let mut environment = self.new_lexical_environment(crate::Scope::Module { source, exported: Exported::default(), }); - M::synthesise_module(&module, source, &mut environment, checking_data); + A::synthesise_module(&module, source, &mut environment, checking_data); let crate::Scope::Module { exported, .. } = environment.context_type.scope else { unreachable!() }; let module = SynthesisedModule { - content: M::owned_module_from_module(module), + content: A::owned_module_from_module(module), exported, facts: environment.facts, }; diff --git a/checker/src/diagnostics.rs b/checker/src/diagnostics.rs index 6d1265ec..bdcc82d8 100644 --- a/checker/src/diagnostics.rs +++ b/checker/src/diagnostics.rs @@ -10,7 +10,6 @@ use std::{ }; #[derive(Serialize, Debug)] -#[serde(tag = "type")] pub enum DiagnosticKind { Error, Warning, @@ -19,7 +18,7 @@ pub enum DiagnosticKind { /// Contains information #[derive(Serialize, Debug)] -#[serde(tag = "type")] +#[serde(untagged)] pub enum Diagnostic { /// Does not have positional information Global { @@ -292,6 +291,10 @@ mod defined_errors_and_warnings { }, TypeNeedsTypeArguments(&'a str, SpanWithSource), CannotFindType(&'a str, SpanWithSource), + TypeAlreadyDeclared { + name: String, + position: SpanWithSource, + }, } impl From> for Diagnostic { @@ -471,7 +474,7 @@ mod defined_errors_and_warnings { ), labels: vec![( format!("Function annotated to return {expected_return_type} here"), - Some(annotation_position.clone()), + Some(annotation_position), )], position: returned_position, kind, @@ -488,7 +491,11 @@ mod defined_errors_and_warnings { TypeCheckError::InvalidUnaryOperation(_, _) => todo!(), TypeCheckError::TypeIsNotIndexable(_) => todo!(), TypeCheckError::TypeIsNotIterable(_) => todo!(), - TypeCheckError::NonTopLevelExport(pos) => todo!(), + TypeCheckError::NonTopLevelExport(position) => Diagnostic::Position { + reason: "Cannot export at not top level".to_owned(), + position, + kind, + }, TypeCheckError::FieldNotExported { file, importing, position } => { Diagnostic::Position { reason: format!("{importing} not exported from {file}"), @@ -597,7 +604,12 @@ mod defined_errors_and_warnings { reason: format!("Cannot find type {ty}"), position, kind, - } + }, + TypeCheckError::TypeAlreadyDeclared { name, position } => Diagnostic::Position { + reason: format!("Type {name} already declared"), + position, + kind, + }, } } } @@ -618,6 +630,9 @@ mod defined_errors_and_warnings { expression_span: SpanWithSource, // TODO other branch information }, + MergingInterfaceInSameContext { + position: SpanWithSource, + }, } impl From for Diagnostic { @@ -652,6 +667,13 @@ mod defined_errors_and_warnings { position: expression_span, kind, }, + TypeCheckWarning::MergingInterfaceInSameContext { position } => { + Diagnostic::Position { + reason: "Merging interfaces in the same context".to_owned(), + position, + kind, + } + } } } } diff --git a/checker/src/events/application.rs b/checker/src/events/application.rs index 3387eb84..33251e61 100644 --- a/checker/src/events/application.rs +++ b/checker/src/events/application.rs @@ -163,9 +163,9 @@ pub(crate) fn apply_event( let with = with .iter() .map(|argument| match argument { - SynthesisedArgument::NonSpread { ty, position: pos } => { + SynthesisedArgument::NonSpread { ty, position } => { let ty = substitute(*ty, type_arguments, environment, types); - SynthesisedArgument::NonSpread { ty, position: pos.clone() } + SynthesisedArgument::NonSpread { ty, position: *position } } }) .collect::>(); @@ -348,6 +348,10 @@ pub(crate) fn apply_event( types, ); } + // TODO + Event::Break { position } => {} + // TODO + Event::Continue { position } => {} } None } diff --git a/checker/src/events/helpers.rs b/checker/src/events/helpers.rs index 216bf298..5c52c3de 100644 --- a/checker/src/events/helpers.rs +++ b/checker/src/events/helpers.rs @@ -14,20 +14,19 @@ pub(crate) enum ReturnedTypeFromBlock { } /// TODO will cover move, like yield events and stuff -pub(crate) fn get_return_from_events<'a, T: crate::ReadFromFS, M: crate::ASTImplementation>( +pub(crate) fn get_return_from_events<'a, T: crate::ReadFromFS, A: crate::ASTImplementation>( iter: &mut (impl Iterator + ExactSizeIterator), - checking_data: &mut CheckingData, + checking_data: &mut CheckingData, environment: &mut Environment, expected_return_type: Option<(TypeId, source_map::SpanWithSource)>, ) -> ReturnedTypeFromBlock { while let Some(event) = iter.next() { match event { Event::Return { returned, returned_position } => { - if let Some((expected_return_type, annotation_span)) = expected_return_type.clone() - { + if let Some((expected_return_type, annotation_span)) = expected_return_type { let mut behavior = crate::subtyping::BasicEquality { add_property_restrictions: true, - position: annotation_span.clone(), + position: annotation_span, }; let result = crate::subtyping::type_is_subtype( @@ -55,8 +54,8 @@ pub(crate) fn get_return_from_events<'a, T: crate::ReadFromFS, M: crate::ASTImpl &checking_data.types, false, ), - annotation_position: annotation_span.clone(), - returned_position: returned_position.clone(), + annotation_position: annotation_span, + returned_position: *returned_position, }, ); } @@ -68,13 +67,13 @@ pub(crate) fn get_return_from_events<'a, T: crate::ReadFromFS, M: crate::ASTImpl &mut events_if_truthy.iter(), checking_data, environment, - expected_return_type.clone(), + expected_return_type, ); let else_return = get_return_from_events( &mut else_events.iter(), checking_data, environment, - expected_return_type.clone(), + expected_return_type, ); return match (return_if_truthy, else_return) { diff --git a/checker/src/events/mod.rs b/checker/src/events/mod.rs index d083f5ba..c0797c03 100644 --- a/checker/src/events/mod.rs +++ b/checker/src/events/mod.rs @@ -124,6 +124,10 @@ pub enum Event { referenced_in_scope_as: TypeId, position: Option, }, + /// TODO label + Break { position: Option }, + /// TODO label + Continue { position: Option }, } #[derive(Debug, Clone, binary_serialize_derive::BinarySerializable)] diff --git a/checker/src/lib.rs b/checker/src/lib.rs index b1e69d75..994ccae2 100644 --- a/checker/src/lib.rs +++ b/checker/src/lib.rs @@ -81,71 +81,75 @@ pub struct ModuleData<'a, FileReader, ModuleAST: ASTImplementation> { pub(crate) currently_checking_modules: HashSet, /// The result of checking. Includes exported variables and facts pub(crate) synthesised_modules: HashMap>, - pub(crate) parsing_options: ModuleAST::ParseOptions, } pub trait ASTImplementation: Sized { type ParseOptions; type ParseError: Into; - type Module; - /// TODO fix for allowing modules to reference + type Module<'a>; + /// TODO temp item. Some modules can have references type OwnedModule; - type DefinitionFile; - type TypeAnnotation; - type TypeParameter; - type Expression; + type DefinitionFile<'a>; - type ClassMethod: SynthesisableFunction; + type TypeAnnotation<'a>; + type TypeParameter<'a>; + type Expression<'a>; + + type ClassMethod<'a>: SynthesisableFunction; /// # Errors /// TODO fn module_from_string( source_id: SourceId, string: String, - options: &Self::ParseOptions, - ) -> Result; + options: Self::ParseOptions, + ) -> Result, Self::ParseError>; /// # Errors /// TODO fn definition_module_from_string( source_id: SourceId, string: String, - ) -> Result; + ) -> Result, Self::ParseError>; - fn synthesise_module( - module: &Self::Module, + #[allow(clippy::needless_lifetimes)] + fn synthesise_module<'a, T: crate::ReadFromFS>( + module: &Self::Module<'a>, source_id: SourceId, - root: &mut Environment, + module_context: &mut Environment, checking_data: &mut crate::CheckingData, ); + #[allow(clippy::needless_lifetimes)] + fn synthesise_definition_file<'a, T: crate::ReadFromFS>( + file: Self::DefinitionFile<'a>, + root: &RootContext, + checking_data: &mut CheckingData, + ) -> (Names, Facts); + /// Expected is used for eagerly setting function parameters - fn synthesise_expression( - expression: &Self::Expression, + fn synthesise_expression<'a, T: crate::ReadFromFS>( + expression: &'a Self::Expression<'a>, expected_type: TypeId, environment: &mut Environment, checking_data: &mut crate::CheckingData, ) -> TypeId; - fn synthesise_type_annotation( - annotation: &Self::TypeAnnotation, + fn synthesise_type_annotation<'a, T: crate::ReadFromFS>( + annotation: &'a Self::TypeAnnotation<'a>, environment: &mut Environment, checking_data: &mut crate::CheckingData, ) -> TypeId; - fn expression_position(expression: &Self::Expression) -> Span; + fn expression_position<'a>(expression: &'a Self::Expression<'a>) -> Span; - fn type_definition_file( - file: Self::DefinitionFile, - root: &RootContext, - checking_data: &mut CheckingData, - ) -> (Names, Facts); + fn type_parameter_name<'a>(parameter: &'a Self::TypeParameter<'a>) -> &'a str; - fn type_parameter_name(parameter: &Self::TypeParameter) -> &str; + fn parse_options(is_js: bool) -> Self::ParseOptions; - fn owned_module_from_module(module: Self::Module) -> Self::OwnedModule; + fn owned_module_from_module(m: Self::Module<'static>) -> Self::OwnedModule; } impl<'a, T: crate::ReadFromFS, ModuleAST: ASTImplementation> ModuleData<'a, T, ModuleAST> { @@ -153,7 +157,6 @@ impl<'a, T: crate::ReadFromFS, ModuleAST: ASTImplementation> ModuleData<'a, T, M mut file_resolver: &'a T, current_working_directory: PathBuf, files: Option>, - parsing_options: ModuleAST::ParseOptions, ) -> Self { Self { files: files.unwrap_or_default(), @@ -163,7 +166,6 @@ impl<'a, T: crate::ReadFromFS, ModuleAST: ASTImplementation> ModuleData<'a, T, M // custom_module_resolvers, file_reader: file_resolver, current_working_directory, - parsing_options, } } @@ -218,7 +220,7 @@ pub struct CheckingData<'a, FSResolver, ModuleAST: ASTImplementation> { pub(crate) modules: ModuleData<'a, FSResolver, ModuleAST>, /// Options for checking pub(crate) options: TypeCheckOptions, - // pub(crate) parse_settings: parser::ParseSettings, + // pub(crate) parse_options: parser::ParseOptions, // pub(crate) events: EventsStore, pub types: TypeStore, @@ -230,20 +232,19 @@ pub struct CheckingData<'a, FSResolver, ModuleAST: ASTImplementation> { #[derive(Debug, Clone)] pub struct CouldNotOpenFile(pub PathBuf); -impl<'a, T: crate::ReadFromFS, M: ASTImplementation> CheckingData<'a, T, M> { +impl<'a, T: crate::ReadFromFS, A: crate::ASTImplementation> CheckingData<'a, T, A> { // TODO improve on this function pub fn new( - settings: TypeCheckOptions, + options: TypeCheckOptions, resolver: &'a T, - parse_options: M::ParseOptions, existing_files: Option>, ) -> Self { // let custom_file_resolvers = HashMap::default(); let cwd = Default::default(); - let modules = ModuleData::new(resolver, cwd, existing_files, parse_options); + let modules = ModuleData::new(resolver, cwd, existing_files); Self { - options: settings, + options, type_mappings: Default::default(), diagnostics_container: Default::default(), modules, @@ -258,11 +259,11 @@ impl<'a, T: crate::ReadFromFS, M: ASTImplementation> CheckingData<'a, T, M> { importing_path: &str, environment: &mut Environment, ) -> Result, CouldNotOpenFile> { - fn get_module<'a, T: crate::ReadFromFS, M: ASTImplementation>( - full_importer: &PathBuf, + fn get_module<'a, T: crate::ReadFromFS, A: crate::ASTImplementation>( + full_importer: &Path, environment: &mut Environment, - checking_data: &'a mut CheckingData, - ) -> Option, M::ParseError>> { + checking_data: &'a mut CheckingData, + ) -> Option, A::ParseError>> { let existing = checking_data.modules.files.get_source_at_path(full_importer); if let Some(existing) = existing { Some(Ok(checking_data @@ -271,18 +272,21 @@ impl<'a, T: crate::ReadFromFS, M: ASTImplementation> CheckingData<'a, T, M> { .get(&existing) .expect("existing file, but not synthesised"))) } else { - let content = (checking_data.modules.file_reader)(full_importer.as_ref()); + let content = (checking_data.modules.file_reader)(full_importer); if let Some(content) = content { let source = checking_data .modules .files - .new_source_id(full_importer.clone(), content.clone()); + .new_source_id(full_importer.to_path_buf(), content.clone()); + + let parse_options = A::parse_options( + full_importer + .extension() + .and_then(|s| s.to_str()) + .map_or(false, |s| s.ends_with("ts")), + ); - match M::module_from_string( - source, - content, - &checking_data.modules.parsing_options, - ) { + match A::module_from_string(source, content, parse_options) { Ok(module) => { let new_module_context = environment.get_root().new_module_context( source, @@ -393,23 +397,29 @@ impl<'a, T: crate::ReadFromFS, M: ASTImplementation> CheckingData<'a, T, M> { } /// Used for transformers and other things after checking!!!! -pub struct PostCheckData { +pub struct PostCheckData { pub type_mappings: crate::TypeMappings, pub types: crate::types::TypeStore, pub module_contents: MapFileStore, - pub modules: HashMap>, + pub modules: HashMap>, pub entry_source: SourceId, } -pub fn check_project( +pub fn check_project( entry_point: PathBuf, type_definition_files: HashSet, resolver: T, options: Option, - parse_options: M::ParseOptions, -) -> (crate::DiagnosticsContainer, Result, MapFileStore>) { - let mut checking_data = - CheckingData::::new(options.unwrap_or_default(), &resolver, parse_options, None); +) -> (crate::DiagnosticsContainer, Result, MapFileStore>) { + let mut checking_data = CheckingData::::new(options.unwrap_or_default(), &resolver, None); + + let mut root = crate::context::RootContext::new_with_primitive_references(); + + add_definition_files_to_root(type_definition_files, &mut root, &mut checking_data); + + if checking_data.diagnostics_container.has_error() { + return (checking_data.diagnostics_container, Err(checking_data.modules.files)); + } let entry_content = (checking_data.modules.file_reader)(entry_point.as_ref()); let module = if let Some(content) = entry_content { @@ -418,11 +428,12 @@ pub fn check_project( checking_data.modules.entry_point = Some(source); - match M::module_from_string(source, content, &checking_data.modules.parsing_options) { - Ok(module) => { - module - // Some(Ok(environment.get_root().new_module_context(source, module, checking_data))) - } + let is_js = false; + let parse_options = A::parse_options(is_js); + + let module = A::module_from_string(source, content, parse_options); + match module { + Ok(module) => Some(root.new_module_context(source, module, &mut checking_data)), Err(err) => { checking_data.diagnostics_container.add_error(err); return (checking_data.diagnostics_container, Err(checking_data.modules.files)); @@ -436,21 +447,11 @@ pub fn check_project( return (checking_data.diagnostics_container, Err(checking_data.modules.files)); }; - let mut root = crate::context::RootContext::new_with_primitive_references(); - - add_definition_files_to_root(type_definition_files, &mut root, &mut checking_data); - - if checking_data.diagnostics_container.has_error() { - return (checking_data.diagnostics_container, Err(checking_data.modules.files)); - } - - root.new_module_context(checking_data.modules.entry_point.unwrap(), module, &mut checking_data); - let CheckingData { diagnostics_container, type_mappings, modules, - options: settings, + options, types, unimplemented_items, } = checking_data; @@ -469,10 +470,10 @@ pub fn check_project( } } -pub(crate) fn add_definition_files_to_root( +pub(crate) fn add_definition_files_to_root( type_definition_files: HashSet, root: &mut RootContext, - checking_data: &mut CheckingData, + checking_data: &mut CheckingData, ) { for path in type_definition_files { let Some((source_id, content)) = checking_data.modules.get_file(&path) else { @@ -485,11 +486,11 @@ pub(crate) fn add_definition_files_to_root { - let (names, facts) = M::type_definition_file(tdm, root, checking_data); + let (names, facts) = A::synthesise_definition_file(tdm, root, checking_data); root.variables.extend(names.variables); root.named_types.extend(names.named_types); root.variable_names.extend(names.variable_names); diff --git a/checker/src/options.rs b/checker/src/options.rs index 55327071..5e090931 100644 --- a/checker/src/options.rs +++ b/checker/src/options.rs @@ -1,6 +1,6 @@ use std::any::Any; -/// Settings for type checking +/// Options for type checking /// TODO reach compat with tsc #[derive(serde::Deserialize)] // TODO: Can be refactored with bit to reduce memory diff --git a/checker/src/synthesis/assignments.rs b/checker/src/synthesis/assignments.rs index d807b478..339aff6c 100644 --- a/checker/src/synthesis/assignments.rs +++ b/checker/src/synthesis/assignments.rs @@ -105,7 +105,7 @@ fn synthesise_object_shorthand_assignable( ) -> Assignable { match name { parser::VariableIdentifier::Standard(name, pos) => Assignable::Reference( - Reference::Variable(name.clone(), pos.clone().with_source(environment.get_source())), + Reference::Variable(name.clone(), pos.with_source(environment.get_source())), ), parser::VariableIdentifier::Cursor(..) => todo!(), } @@ -117,10 +117,9 @@ pub(crate) fn synthesise_access_to_reference( checking_data: &mut CheckingData, ) -> Reference { match variable_or_property_access { - VariableOrPropertyAccess::Variable(ident, position) => Reference::Variable( - ident.clone(), - position.clone().with_source(environment.get_source()), - ), + VariableOrPropertyAccess::Variable(ident, position) => { + Reference::Variable(ident.clone(), position.with_source(environment.get_source())) + } VariableOrPropertyAccess::PropertyAccess { parent, property, position } => { let parent_ty = synthesise_expression(parent, environment, checking_data, TypeId::ANY_TYPE); @@ -133,7 +132,7 @@ pub(crate) fn synthesise_access_to_reference( with: crate::types::properties::PropertyKey::String(Cow::Owned( property.clone(), )), - span: position.clone().with_source(environment.get_source()), + span: position.with_source(environment.get_source()), publicity, } } @@ -155,7 +154,7 @@ pub(crate) fn synthesise_access_to_reference( key_ty, &checking_data.types, ), - span: position.clone().with_source(environment.get_source()), + span: position.with_source(environment.get_source()), publicity: crate::context::facts::Publicity::Public, } } diff --git a/checker/src/synthesis/block.rs b/checker/src/synthesis/block.rs index 71101630..79f562ef 100644 --- a/checker/src/synthesis/block.rs +++ b/checker/src/synthesis/block.rs @@ -1,4 +1,7 @@ -use parser::{declarations::VariableDeclaration, Declaration, Statement, StatementOrDeclaration}; +use parser::{ + declarations::VariableDeclaration, Declaration, ExpressionOrStatementPosition, Statement, + StatementOrDeclaration, VariableIdentifier, +}; use crate::{ behavior::modules::Exported, context::Environment, diagnostics::TypeCheckError, CheckingData, @@ -66,19 +69,23 @@ pub(crate) fn synthesise_declaration( } Declaration::Class(class) => { let constructor = synthesise_class_declaration(&class.on, environment, checking_data); - let position = class.on.position.clone().with_source(environment.get_source()); - let result = environment.declare_variable( - class.on.name.as_str(), - position.clone(), - constructor, - &mut checking_data.types, - None, - ); - if let Err(err) = result { - checking_data.diagnostics_container.add_error(TypeCheckError::ReDeclaredVariable { - name: class.on.name.as_str(), + let position = class.on.position.with_source(environment.get_source()); + if let Some(VariableIdentifier::Standard(name, ..)) = + class.on.name.as_option_variable_identifier() + { + let result = environment.declare_variable( + name, position, - }); + constructor, + &mut checking_data.types, + None, + ); + if let Err(err) = result { + // TODO is this an issue? + checking_data + .diagnostics_container + .add_error(TypeCheckError::ReDeclaredVariable { name, position }); + } } } Declaration::DeclareVariable(_) @@ -138,7 +145,7 @@ pub(crate) fn synthesise_declaration( if exported.default.is_some() { checking_data.diagnostics_container.add_error( TypeCheckError::DoubleDefaultExport( - position.clone().with_source(environment.get_source()), + position.with_source(environment.get_source()), ), ); } else { @@ -147,7 +154,7 @@ pub(crate) fn synthesise_declaration( } else { checking_data.diagnostics_container.add_error( TypeCheckError::NonTopLevelExport( - position.clone().with_source(environment.get_source()), + position.with_source(environment.get_source()), ), ); } diff --git a/checker/src/synthesis/classes.rs b/checker/src/synthesis/classes.rs index b0447fbe..23aa4541 100644 --- a/checker/src/synthesis/classes.rs +++ b/checker/src/synthesis/classes.rs @@ -2,9 +2,9 @@ use std::iter; use parser::{ declarations::{classes::ClassMember, ClassDeclaration}, + functions::MethodHeader, property_key::PublicOrPrivate, - Decorated, Expression, GenericTypeConstraint, MethodHeader, PropertyKey as ParserPropertyKey, - TypeAnnotation, + Decorated, Expression, GenericTypeConstraint, PropertyKey as ParserPropertyKey, TypeAnnotation, }; use crate::{ @@ -110,7 +110,7 @@ pub(super) fn synthesise_class_declaration< let property = function_to_property(&getter_setter, method_ty, &mut checking_data.types); - let position = Some(method.position.clone().with_source(environment.get_source())); + let position = Some(method.position.with_source(environment.get_source())); environment.facts.register_property( class_prototype, publicity, diff --git a/checker/src/synthesis/definitions.rs b/checker/src/synthesis/definitions.rs index 405dc0ef..c215f851 100644 --- a/checker/src/synthesis/definitions.rs +++ b/checker/src/synthesis/definitions.rs @@ -35,13 +35,13 @@ pub(super) fn type_definition_file( for statement in &definition.declarations { match statement { TypeDefinitionModuleDeclaration::Interface(interface) => { - let ty = env.new_interface::( + let ty = env.new_interface( &interface.on.name, interface.on.nominal_keyword.is_some(), interface.on.type_parameters.as_deref(), interface.on.extends.as_deref(), - &interface.on.position.clone().with_source(source), - &mut checking_data.types, + interface.on.position.with_source(source), + checking_data, ); idx_to_types.insert(interface.on.position.start, ty); } @@ -53,11 +53,12 @@ pub(super) fn type_definition_file( // env.register_type(Type::NamedRooted { name class.name.clone())), // ), } - TypeDefinitionModuleDeclaration::TypeAlias(type_alias) => { + TypeDefinitionModuleDeclaration::TypeAlias(alias) => { env.new_alias( - &type_alias.type_name.name, - type_alias.type_name.type_parameters.as_deref(), - &type_alias.type_expression, + &alias.type_name.name, + alias.type_name.type_parameters.as_deref(), + &alias.type_expression, + *alias.get_position(), checking_data, ); } @@ -69,7 +70,7 @@ pub(super) fn type_definition_file( match declaration { TypeDefinitionModuleDeclaration::Function(func) => { // TODO abstract - let declared_at = func.get_position().clone().with_source(source); + let declared_at = func.get_position().with_source(source); let base = synthesise_function_annotation( &func.type_parameters, &func.parameters, @@ -100,7 +101,7 @@ pub(super) fn type_definition_file( let res = env.register_variable_handle_error( func.name.as_str(), // TODO - func.get_position().clone().with_source(source), + func.get_position().with_source(source), behavior, checking_data, ); @@ -195,7 +196,7 @@ pub(super) fn type_definition_file( // .get_type( // extends_type, // checking_data, - // &crate::root::GetTypeFromReferenceSettings::Default, + // &crate::root::GetTypeFromReferenceOptions::Default, // ) // .expect("Class should have been initialised"); // todo!(); diff --git a/checker/src/synthesis/expressions.rs b/checker/src/synthesis/expressions.rs index 6fbe0960..af702ce8 100644 --- a/checker/src/synthesis/expressions.rs +++ b/checker/src/synthesis/expressions.rs @@ -6,8 +6,11 @@ use parser::{ object_literal::{ObjectLiteral, ObjectLiteralMember}, MultipleExpression, SpecialOperators, SpreadExpression, SuperReference, TemplateLiteral, }, - operators::{BinaryAssignmentOperator, UnaryOperator, UnaryPrefixAssignmentOperator}, - ASTNode, Expression, MethodHeader, + functions::MethodHeader, + operators::{ + BinaryAssignmentOperator, BinaryOperator, UnaryOperator, UnaryPrefixAssignmentOperator, + }, + ASTNode, Expression, }; use crate::{ @@ -83,7 +86,12 @@ pub(super) fn synthesise_expression( return checking_data.types.new_constant_type(Constant::Regexp(pattern.clone())); } Expression::NumberLiteral(value, ..) => { - let not_nan = f64::from(value.clone()).try_into().unwrap(); + let not_nan = if let Ok(v) = f64::try_from(value.clone()) { + v.try_into().unwrap() + } else { + crate::utils::notify!("TODO big int"); + return TypeId::ERROR_TYPE; + }; return checking_data.types.new_constant_type(Constant::Number(not_nan)); } Expression::BooleanLiteral(value, ..) => { @@ -114,7 +122,7 @@ pub(super) fn synthesise_expression( { checking_data.raise_unimplemented_error( "Spread elements", - position.clone().with_source(environment.get_source()), + position.with_source(environment.get_source()), ); } crate::utils::notify!("Skipping spread"); @@ -148,7 +156,7 @@ pub(super) fn synthesise_expression( // TODO remove enumerate, add add function and more for (idx, value) in elements.iter().enumerate() { let spread_expression_position = - value.get_position().clone().with_source(environment.get_source()); + value.get_position().with_source(environment.get_source()); let (key, value) = synthesise_array_item( &Decidable::Known(idx), @@ -207,25 +215,24 @@ pub(super) fn synthesise_expression( checking_data, )) } - Expression::BinaryOperation { lhs, operator, rhs, .. } => { - use parser::operators::BinaryOperator; - + Expression::BinaryOperation { lhs, operator, rhs, position } => { let lhs_ty = synthesise_expression(lhs, environment, checking_data, TypeId::ANY_TYPE); if let BinaryOperator::LogicalAnd | BinaryOperator::LogicalOr | BinaryOperator::NullCoalescing = operator { + let operator = match operator { + BinaryOperator::LogicalAnd => crate::behavior::operations::Logical::And, + BinaryOperator::LogicalOr => crate::behavior::operations::Logical::Or, + BinaryOperator::NullCoalescing => { + crate::behavior::operations::Logical::NullCoalescing + } + _ => unreachable!(), + }; return evaluate_logical_operation_with_expression( lhs_ty, - match operator { - BinaryOperator::LogicalAnd => &crate::behavior::operations::Logical::And, - BinaryOperator::LogicalOr => &crate::behavior::operations::Logical::Or, - BinaryOperator::NullCoalescing => { - &crate::behavior::operations::Logical::NullCoalescing - } - _ => unreachable!(), - }, + operator, &**rhs, checking_data, environment, @@ -240,10 +247,8 @@ pub(super) fn synthesise_expression( return TypeId::ERROR_TYPE; } - let lhs_pos = - ASTNode::get_position(&**lhs).clone().with_source(environment.get_source()); - let rhs_pos = - ASTNode::get_position(&**rhs).clone().with_source(environment.get_source()); + let lhs_pos = ASTNode::get_position(&**lhs).with_source(environment.get_source()); + let rhs_pos = ASTNode::get_position(&**rhs).with_source(environment.get_source()); let operator = match operator { BinaryOperator::Add => MathematicalAndBitwise::Add.into(), @@ -275,9 +280,13 @@ pub(super) fn synthesise_expression( | BinaryOperator::NullCoalescing => { unreachable!() } - BinaryOperator::Divides => todo!(), - BinaryOperator::Pipe => todo!(), - BinaryOperator::Compose => todo!(), + BinaryOperator::Divides | BinaryOperator::Pipe | BinaryOperator::Compose => { + checking_data.raise_unimplemented_error( + "special operations", + position.with_source(environment.get_source()), + ); + return TypeId::ERROR_TYPE; + } }; Instance::RValue(evaluate_pure_binary_operation_handle_errors( (lhs_ty, lhs_pos), @@ -315,8 +324,20 @@ pub(super) fn synthesise_expression( .unwrap(), ) } - UnaryOperator::Await => todo!(), - UnaryOperator::TypeOf => todo!(), + UnaryOperator::Await => { + checking_data.raise_unimplemented_error( + "await", + position.with_source(environment.get_source()), + ); + return TypeId::ERROR_TYPE; + } + UnaryOperator::TypeOf => { + checking_data.raise_unimplemented_error( + "TypeOf", + position.with_source(environment.get_source()), + ); + return TypeId::ERROR_TYPE; + } UnaryOperator::Void => { let _operand_type = synthesise_expression( operand, @@ -381,15 +402,20 @@ pub(super) fn synthesise_expression( } } } - UnaryOperator::Yield => todo!(), - UnaryOperator::DelegatedYield => todo!(), + UnaryOperator::Yield | UnaryOperator::DelegatedYield => { + checking_data.raise_unimplemented_error( + "yield expression", + position.with_source(environment.get_source()), + ); + return TypeId::ERROR_TYPE; + } } } Expression::Assignment { lhs, rhs, position } => { let lhs: Assignable = synthesise_lhs_of_assignment_to_reference(lhs, environment, checking_data); - let assignment_span = position.clone().with_source(environment.get_source()); + let assignment_span = position.with_source(environment.get_source()); return environment.assign_to_assignable_handle_errors( lhs, crate::behavior::assignments::AssignmentKind::Assign, @@ -408,7 +434,7 @@ pub(super) fn synthesise_expression( checking_data, )); - let assignment_span = position.clone().with_source(environment.get_source()); + let assignment_span = position.with_source(environment.get_source()); return environment.assign_to_assignable_handle_errors( lhs, operator_to_assignment_kind(*operator), @@ -427,7 +453,7 @@ pub(super) fn synthesise_expression( match operator { UnaryPrefixAssignmentOperator::Invert => todo!(), UnaryPrefixAssignmentOperator::IncrementOrDecrement(direction) => { - let assignment_span = position.clone().with_source(environment.get_source()); + let assignment_span = position.with_source(environment.get_source()); return environment.assign_to_assignable_handle_errors( lhs, crate::behavior::assignments::AssignmentKind::IncrementOrDecrement( @@ -470,7 +496,7 @@ pub(super) fn synthesise_expression( crate::behavior::assignments::AssignmentReturnStatus::Previous, ); - let assignment_span = position.clone().with_source(environment.get_source()); + let assignment_span = position.with_source(environment.get_source()); return environment.assign_to_assignable_handle_errors( lhs, operator, @@ -484,7 +510,7 @@ pub(super) fn synthesise_expression( Expression::VariableReference(name, position) => { let get_variable_or_alternatives = environment.get_variable_handle_error( name.as_str(), - position.clone().with_source(environment.get_source()), + position.with_source(environment.get_source()), checking_data, ); @@ -502,7 +528,7 @@ pub(super) fn synthesise_expression( todo!() }; - let access_position = position.clone().with_source(environment.get_source()); + let access_position = position.with_source(environment.get_source()); // TODO let publicity = Publicity::Public; @@ -529,7 +555,7 @@ pub(super) fn synthesise_expression( TypeId::ANY_TYPE, ); - let index_position = position.clone().with_source(environment.get_source()); + let index_position = position.with_source(environment.get_source()); // TODO handle differently? let result = environment.get_property_handle_errors( being_indexed, @@ -545,7 +571,7 @@ pub(super) fn synthesise_expression( } } Expression::ThisReference(pos) => { - let position = pos.clone().with_source(environment.get_source()); + let position = pos.with_source(environment.get_source()); Instance::RValue(environment.get_value_of_this(&checking_data.types, &position)) } Expression::SuperExpression(reference, position) => match reference { @@ -614,10 +640,10 @@ pub(super) fn synthesise_expression( Some(arguments), environment, checking_data, - position, + *position, ); if let Some(special) = special { - checking_data.type_mappings.special_expressions.push(position.clone(), special); + checking_data.type_mappings.special_expressions.push(*position, special); } Instance::RValue(result) } @@ -632,13 +658,11 @@ pub(super) fn synthesise_expression( arguments.as_ref(), environment, checking_data, - position, + *position, ); Instance::RValue(result) } - Expression::ConditionalTernaryExpression { - condition, truthy_result, falsy_result, .. - } => { + Expression::ConditionalTernary { condition, truthy_result, falsy_result, .. } => { let condition = synthesise_expression(condition, environment, checking_data, TypeId::ANY_TYPE); @@ -665,7 +689,7 @@ pub(super) fn synthesise_expression( 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::Module(_) => "module".to_owned(), + parser::functions::FunctionLocationModifier::Worker(_) => "worker".to_owned(), }); Instance::RValue(register_expression_function( expecting, @@ -697,7 +721,7 @@ pub(super) fn synthesise_expression( SpecialOperators::AsExpression { value, type_annotation, .. } => { checking_data.diagnostics_container.add_warning( TypeCheckWarning::IgnoringAsExpression( - position.clone().with_source(environment.get_source()), + position.with_source(environment.get_source()), ), ); @@ -712,7 +736,7 @@ pub(super) fn synthesise_expression( checking_data.check_satisfies( value, satisfying, - ASTNode::get_position(expression).clone().with_source(environment.get_source()), + ASTNode::get_position(expression).with_source(environment.get_source()), environment, ); @@ -720,7 +744,13 @@ pub(super) fn synthesise_expression( } SpecialOperators::InExpression { lhs, rhs } => { let lhs = match lhs { - parser::expressions::InExpressionLHS::PrivateProperty(_) => todo!(), + parser::expressions::InExpressionLHS::PrivateProperty(_) => { + checking_data.raise_unimplemented_error( + "in on private", + position.with_source(environment.get_source()), + ); + return TypeId::ERROR_TYPE; + } parser::expressions::InExpressionLHS::Expression(lhs) => { synthesise_expression(lhs, environment, checking_data, TypeId::ANY_TYPE) } @@ -731,12 +761,18 @@ pub(super) fn synthesise_expression( Instance::RValue(if result { TypeId::TRUE } else { TypeId::FALSE }) } - SpecialOperators::InstanceOfExpression { .. } => todo!(), + SpecialOperators::InstanceOfExpression { .. } => { + checking_data.raise_unimplemented_error( + "instanceof expression", + position.with_source(environment.get_source()), + ); + return TypeId::ERROR_TYPE; + } }, Expression::DynamicImport { position, .. } => { checking_data.raise_unimplemented_error( "dynamic import", - position.clone().with_source(environment.get_source()), + position.with_source(environment.get_source()), ); return TypeId::ERROR_TYPE; } @@ -745,7 +781,7 @@ pub(super) fn synthesise_expression( } }; - let position = ASTNode::get_position(expression).clone().with_source(environment.get_source()); + let position = ASTNode::get_position(expression).with_source(environment.get_source()); // TODO should be copy checking_data.add_expression_mapping(position, instance.clone()); @@ -816,7 +852,7 @@ fn call_function( mut arguments: Option<&Vec>, environment: &mut Environment, checking_data: &mut CheckingData, - call_site: &parser::Span, + call_site: parser::Span, ) -> (TypeId, Option) { let generic_type_arguments = type_arguments.as_ref().map(|type_arguments| { type_arguments @@ -824,10 +860,7 @@ fn call_function( .map(|generic_type_argument| { ( synthesise_type_annotation(generic_type_argument, environment, checking_data), - generic_type_argument - .get_position() - .clone() - .with_source(environment.get_source()), + generic_type_argument.get_position().with_source(environment.get_source()), ) }) .collect::>() @@ -843,7 +876,7 @@ fn call_function( CallingInput { called_with_new, this_value: Default::default(), - call_site: call_site.clone().with_source(environment.get_source()), + call_site: call_site.with_source(environment.get_source()), call_site_type_arguments: generic_type_arguments, }, environment, @@ -884,9 +917,7 @@ fn synthesise_arguments( let ty = synthesise_expression(expr, environment, checking_data, TypeId::ANY_TYPE); Some(SynthesisedArgument::NonSpread { ty, - position: ASTNode::get_position(expr) - .clone() - .with_source(environment.get_source()), + position: ASTNode::get_position(expr).with_source(environment.get_source()), }) } SpreadExpression::Empty => None, @@ -904,19 +935,28 @@ pub(super) fn synthesise_object_literal( ObjectBuilder::new(None, &mut checking_data.types, &mut environment.facts); for member in members { - let member_position = member.get_position().clone().with_source(environment.get_source()); - match member { + let member_position = member.get_position().with_source(environment.get_source()); + match member.get_ast_ref() { ObjectLiteralMember::Spread(spread, pos) => { - checking_data.raise_unimplemented_error( - "spread in object literal", - pos.clone().with_source(environment.get_source()), - ); + let spread = synthesise_expression(spread, environment, checking_data, expected); + + // TODO use what about string, what about enumerable ... + for (_, key, value) in environment.get_properties_on_type(spread) { + object_builder.append( + environment, + Publicity::Public, + key, + // TODO what about getters + crate::PropertyValue::Value(value), + Some(pos.with_source(environment.get_source())), + ); + } } ObjectLiteralMember::Shorthand(name, position) => { let key = PropertyKey::String(Cow::Owned(name.clone())); let get_variable = environment.get_variable_handle_error( name, - position.clone().with_source(environment.get_source()), + position.with_source(environment.get_source()), checking_data, ); let value = match get_variable { @@ -942,11 +982,8 @@ pub(super) fn synthesise_object_literal( ); } ObjectLiteralMember::Property(key, expression, _) => { - let key = parser_property_key_to_checker_property_key( - key.get_ast_ref(), - environment, - checking_data, - ); + let key = + parser_property_key_to_checker_property_key(key, environment, checking_data); // TODO base of above let expecting = TypeId::ANY_TYPE; @@ -982,7 +1019,7 @@ pub(super) fn synthesise_object_literal( } ObjectLiteralMember::Method(method) => { let key = parser_property_key_to_checker_property_key( - method.name.get_ast_ref(), + &method.name, environment, checking_data, ); diff --git a/checker/src/synthesis/extensions/jsx.rs b/checker/src/synthesis/extensions/jsx.rs index 69b6c5f0..9e17be20 100644 --- a/checker/src/synthesis/extensions/jsx.rs +++ b/checker/src/synthesis/extensions/jsx.rs @@ -52,8 +52,7 @@ pub(crate) fn synthesise_jsx_element( for attribute in &element.attributes { let (name, attribute_value) = synthesise_attribute(attribute, environment, checking_data); - let attribute_position = - attribute.get_position().clone().with_source(environment.get_source()); + let attribute_position = attribute.get_position().with_source(environment.get_source()); attributes_object.append( environment, crate::context::facts::Publicity::Public, @@ -104,13 +103,13 @@ pub(crate) fn synthesise_jsx_element( // attr_restriction, // &environment.as_general_context(), // &checking_data.types, - // checking_data.settings.debug_types, + // checking_data.options.debug_types, // ), // value_type: TypeStringRepresentation::from_type_id( // attr_value, // &environment.as_general_context(), // &checking_data.types, - // checking_data.settings.debug_types, + // checking_data.options.debug_types, // ), // attribute_type_site: (), // value_site: parser::ASTNode::get_position(attribute) @@ -148,7 +147,7 @@ pub(crate) fn synthesise_jsx_element( // TODO idx bad! and should override item let property = PropertyKey::from_usize(idx); - let child_position = child.get_position().clone().with_source(environment.get_source()); + 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, @@ -164,9 +163,9 @@ pub(crate) fn synthesise_jsx_element( None }; - let position = element.get_position().clone().with_source(environment.get_source()); + let position = element.get_position().with_source(environment.get_source()); let jsx_function = - match environment.get_variable_handle_error(JSX_NAME, position.clone(), checking_data) { + match environment.get_variable_handle_error(JSX_NAME, position, checking_data) { Ok(ty) => ty.1, Err(_) => { todo!() @@ -176,18 +175,18 @@ pub(crate) fn synthesise_jsx_element( let tag_name_argument = SynthesisedArgument::NonSpread { ty: tag_name_as_cst_ty, // TODO use tag name position - position: position.clone(), + position, }; let attributes_argument = SynthesisedArgument::NonSpread { ty: attributes_object.build_object(), // TODO use arguments position - position: position.clone(), + position, }; let mut args = vec![tag_name_argument, attributes_argument]; if let Some(child_nodes) = child_nodes { // TODO position here - args.push(SynthesisedArgument::NonSpread { ty: child_nodes, position: position.clone() }); + args.push(SynthesisedArgument::NonSpread { ty: child_nodes, position }); } call_type_handle_errors( @@ -195,7 +194,7 @@ pub(crate) fn synthesise_jsx_element( CallingInput { called_with_new: crate::types::calling::CalledWithNew::None, this_value: environment.facts.value_of_this, - call_site: position.clone(), + call_site: position, call_site_type_arguments: None, }, environment, @@ -285,12 +284,12 @@ pub(crate) fn synthesise_jsx_element( // // attribute_type: TypeStringRepresentation::from_type_id( // // attr_restriction, // // &environment.into_general_context(), - // // checking_data.settings.debug_types, + // // checking_data.options.debug_types, // // ), // // value_type: TypeStringRepresentation::from_type_id( // // attr_value, // // &environment.into_general_context(), - // // checking_data.settings.debug_types, + // // checking_data.options.debug_types, // // ), // // attribute_type_site: (), // // value_site: parser::ASTNode::get_position(attribute) @@ -399,12 +398,12 @@ fn synthesise_jsx_child( // expected: TypeStringRepresentation::from_type( // &expected_type, // &checking_data.memory, - // checking_data.settings.debug_types, + // checking_data.options.debug_types, // ), // found: TypeStringRepresentation::from_type( // &expression.as_type(), // &checking_data.memory, - // checking_data.settings.debug_types, + // checking_data.options.debug_types, // ), // }, // ); diff --git a/checker/src/synthesis/functions.rs b/checker/src/synthesis/functions.rs index 3f61c7a2..5c4f7ab1 100644 --- a/checker/src/synthesis/functions.rs +++ b/checker/src/synthesis/functions.rs @@ -23,101 +23,13 @@ use crate::{ }; use super::{ - expressions::synthesise_expression, hoisting::string_comment_to_type, synthesise_block, + expressions::synthesise_expression, hoisting::comment_as_type_annotation, synthesise_block, type_annotations::synthesise_type_annotation, variables::register_variable, Performs, }; -trait FunctionBasedItem: FunctionBased { - type ObjectTypeId; - - // fn get_function_kind(func: &FunctionBase) -> FunctionKind; - - fn location(func: &FunctionBase) -> Option { - None - } -} - -// TODO generic for these two -impl FunctionBasedItem for parser::functions::bases::StatementFunctionBase { - type ObjectTypeId = (); - - // fn get_function_kind(func: &FunctionBase) -> FunctionKind { - // FunctionKind::StatementFunction { - // is_async: func.header.is_async(), - // generator: func.header.is_generator(), - // } - // } -} - -impl FunctionBasedItem for parser::functions::bases::ExpressionFunctionBase { - type ObjectTypeId = (); - - // fn get_function_kind(func: &FunctionBase) -> FunctionKind { - // FunctionKind::StatementFunction { - // is_async: func.header.is_async(), - // generator: func.header.is_generator(), - // } - // } - - fn location(func: &FunctionBase) -> Option { - match &func.header { - parser::FunctionHeader::VirginFunctionHeader { location, .. } - | parser::FunctionHeader::ChadFunctionHeader { location, .. } => { - if let Some(parser::functions::FunctionLocationModifier::Server(_)) = location { - Some("server".to_owned()) - } else { - // if let Some(StatementOrDeclaration::Statement(Statement::Expression(expr))) = - // func.body.0.first() - // { - // if matches!(expr, parser::expressions::MultipleExpression::Single(parser::Expression::StringLiteral(s, _, _)) if s == "use server") - // { - // return Some("server".to_owned()); - // } - // } - None - } - } - } - } -} - -impl FunctionBasedItem for parser::functions::bases::ArrowFunctionBase { - type ObjectTypeId = (); - - // fn get_function_kind(func: &FunctionBase) -> FunctionKind { - // let is_async = func.header.is_some(); - // FunctionKind::ArrowFunction { is_async } - // } -} - -impl FunctionBasedItem for parser::functions::bases::ObjectLiteralMethodBase { - type ObjectTypeId = Option; - - // fn get_function_kind(func: &FunctionBase) -> FunctionKind { - // FunctionKind::Method(From::from(&func.header)) - // } -} - -impl FunctionBasedItem for parser::functions::bases::ClassFunctionBase { - type ObjectTypeId = Option; - - // fn get_function_kind(func: &FunctionBase) -> FunctionKind { - // FunctionKind::Method(From::from(&func.header)) - // } -} - -impl FunctionBasedItem for parser::functions::bases::ClassConstructorBase { - type ObjectTypeId = Option; - - // fn get_function_kind(func: &FunctionBase) -> FunctionKind { - // FunctionKind::ClassConstructor - // } -} - impl SynthesisableFunction for parser::FunctionBase where - U: FunctionBasedItem, U::Body: SynthesisableFunctionBody, { fn id(&self, source_id: SourceId) -> FunctionId { @@ -167,9 +79,14 @@ where &self, environment: &mut Environment, checking_data: &mut CheckingData, - expected_parameters: Option, + expected_parameters: Option<&SynthesisedParameters>, ) -> SynthesisedParameters { - synthesise_function_parameters(&self.parameters, environment, checking_data) + synthesise_function_parameters( + &self.parameters, + expected_parameters, + environment, + checking_data, + ) } fn return_type_annotation( @@ -180,7 +97,7 @@ where self.return_type.as_ref().map(|reference| { ( synthesise_type_annotation(reference, environment, checking_data), - reference.get_position().clone().with_source(environment.get_source()), + reference.get_position().with_source(environment.get_source()), ) }) } @@ -225,8 +142,7 @@ impl SynthesisableFunctionBody for ExpressionOrBlock { // TODO expecting let returned = synthesise_expression(expression, environment, checking_data, TypeId::ANY_TYPE); - let position = - expression.get_position().clone().with_source(environment.get_source()); + let position = expression.get_position().with_source(environment.get_source()); environment.return_value(returned, position); } ExpressionOrBlock::Block(block) => { @@ -319,7 +235,7 @@ pub(super) fn synthesise_type_annotation_function_parameters( ast_parameters: &parser::FunctionParameters, + expected_parameters: Option<&SynthesisedParameters>, environment: &mut Environment, checking_data: &mut CheckingData, ) -> SynthesisedParameters { let parameters: Vec<_> = ast_parameters .parameters .iter() - .map(|parameter| { + .enumerate() + .map(|(idx, parameter)| { let annotation = parameter .type_annotation .as_ref() .map(|reference| synthesise_type_annotation(reference, environment, checking_data)) .or_else(|| { + // See comments-as-type-annotation if let WithComment::PostfixComment(item, possible_declaration, position) = ¶meter.name { - string_comment_to_type( + comment_as_type_annotation( possible_declaration, - &position.clone().with_source(environment.get_source()), + &position.with_source(environment.get_source()), environment, checking_data, ) @@ -383,6 +302,10 @@ fn synthesise_function_parameters( } else { None } + }) + .or_else(|| { + // Try use expected type + expected_parameters.as_ref().and_then(|p| p.get_type_constraint_at_index(idx)) }); let param_type = register_variable( @@ -403,7 +326,7 @@ fn synthesise_function_parameters( SynthesisedParameter { name, ty: param_type, - position: parameter.position.clone().with_source(environment.get_source()), + position: parameter.position.with_source(environment.get_source()), missing_value, } }) diff --git a/checker/src/synthesis/hoisting.rs b/checker/src/synthesis/hoisting.rs index ee4ae9a4..ca1e3d5c 100644 --- a/checker/src/synthesis/hoisting.rs +++ b/checker/src/synthesis/hoisting.rs @@ -2,8 +2,9 @@ use std::{collections::HashMap, iter}; use parser::{ declarations::{export::Exportable, DeclareVariableDeclaration, ExportDeclaration}, - ASTNode, Declaration, Decorated, Statement, StatementOrDeclaration, VariableIdentifier, - WithComment, + tsx_keywords::Var, + ASTNode, Declaration, Decorated, ExpressionOrStatementPosition, Statement, + StatementOrDeclaration, VariableIdentifier, WithComment, }; use source_map::{Span, SpanWithSource}; @@ -41,28 +42,28 @@ pub(crate) fn hoist_statements( | parser::Declaration::Function(_) => {} parser::Declaration::Enum(r#enum) => checking_data.raise_unimplemented_error( "enum", - r#enum.on.position.clone().with_source(environment.get_source()), + r#enum.on.position.with_source(environment.get_source()), ), parser::Declaration::DeclareInterface(interface) => { // TODO any difference bc declare? - let ty = environment.new_interface::( + let ty = environment.new_interface( &interface.name, interface.nominal_keyword.is_some(), interface.type_parameters.as_deref(), interface.extends.as_deref(), - &interface.position.clone().with_source(environment.get_source()), - &mut checking_data.types, + interface.position.with_source(environment.get_source()), + checking_data, ); idx_to_types.insert(interface.position.start, ty); } parser::Declaration::Interface(interface) => { - let ty = environment.new_interface::( + let ty = environment.new_interface( &interface.on.name, interface.on.nominal_keyword.is_some(), interface.on.type_parameters.as_deref(), interface.on.extends.as_deref(), - &interface.on.position.clone().with_source(environment.get_source()), - &mut checking_data.types, + interface.on.position.with_source(environment.get_source()), + checking_data, ); idx_to_types.insert(interface.on.position.start, ty); } @@ -71,6 +72,7 @@ pub(crate) fn hoist_statements( &alias.type_name.name, alias.type_name.type_parameters.as_deref(), &alias.type_expression, + *alias.get_position(), checking_data, ); } @@ -85,7 +87,7 @@ pub(crate) fn hoist_statements( VariableIdentifier::Standard(under, position) => { crate::behavior::modules::ImportKind::All { under, - position: position.clone(), + position: *position, } } VariableIdentifier::Cursor(_, _) => todo!(), @@ -93,15 +95,15 @@ pub(crate) fn hoist_statements( }; let default_import = import.default.as_ref().and_then(|default_identifier| { match default_identifier { - VariableIdentifier::Standard(name, pos) => { - Some((name.as_str(), pos.clone())) + VariableIdentifier::Standard(name, position) => { + Some((name.as_str(), *position)) } VariableIdentifier::Cursor(..) => None, } }); environment.import_items( import.from.get_path().unwrap(), - import.position.clone(), + import.position, default_import, items, checking_data, @@ -114,8 +116,8 @@ pub(crate) fn hoist_statements( match exported { Exportable::ImportAll { r#as, from } => { let kind = match r#as { - Some(VariableIdentifier::Standard(name, pos)) => { - ImportKind::All { under: name, position: pos.clone() } + Some(VariableIdentifier::Standard(name, position)) => { + ImportKind::All { under: name, position: *position } } Some(VariableIdentifier::Cursor(_, _)) => todo!(), None => ImportKind::Everything, @@ -123,7 +125,7 @@ pub(crate) fn hoist_statements( environment.import_items::, _, _>( from.get_path().unwrap(), - position.clone(), + *position, None, kind, checking_data, @@ -135,7 +137,7 @@ pub(crate) fn hoist_statements( environment.import_items( from.get_path().unwrap(), - position.clone(), + *position, None, crate::behavior::modules::ImportKind::Parts(parts), checking_data, @@ -147,6 +149,7 @@ pub(crate) fn hoist_statements( &alias.type_name.name, alias.type_name.type_parameters.as_deref(), &alias.type_expression, + *alias.get_position(), checking_data, ); @@ -173,7 +176,7 @@ pub(crate) fn hoist_statements( if let Statement::VarVariable(_) = stmt { checking_data.raise_unimplemented_error( "var statement hoisting", - stmt.get_position().clone().with_source(environment.get_source()), + stmt.get_position().with_source(environment.get_source()), ); } } @@ -187,16 +190,20 @@ pub(crate) fn hoist_statements( // TODO mutability: crate::behavior::variables::VariableMutability::Constant, }; - environment.register_variable_handle_error( - func.on.name.as_str(), - func.get_position().clone().with_source(environment.get_source()), - behavior, - checking_data, - ); + if let Some(VariableIdentifier::Standard(name, ..)) = + func.on.name.as_option_variable_identifier() + { + environment.register_variable_handle_error( + name, + func.get_position().with_source(environment.get_source()), + behavior, + checking_data, + ); + } } parser::Declaration::DeclareFunction(func) => { // TODO abstract - let declared_at = func.position.clone().with_source(environment.get_source()); + let declared_at = func.position.with_source(environment.get_source()); let base = synthesise_function_annotation( &func.type_parameters, &func.parameters, @@ -224,7 +231,7 @@ pub(crate) fn hoist_statements( crate::context::VariableRegisterBehavior::Declare { base, context: None }; environment.register_variable_handle_error( func.name.as_str(), - func.get_position().clone().with_source(environment.get_source()), + func.get_position().with_source(environment.get_source()), behavior, checking_data, ); @@ -232,7 +239,7 @@ pub(crate) fn hoist_statements( parser::Declaration::Enum(r#enum) => { checking_data.raise_unimplemented_error( "enum", - r#enum.position.clone().with_source(environment.get_source()), + r#enum.position.with_source(environment.get_source()), ); } parser::Declaration::Interface(interface) => { @@ -284,17 +291,19 @@ pub(crate) fn hoist_statements( let behavior = crate::context::VariableRegisterBehavior::Register { mutability, }; - let declared_at = func - .get_position() - .clone() - .with_source(environment.get_source()); - - environment.register_variable_handle_error( - func.name.as_str(), - declared_at, - behavior, - checking_data, - ); + let declared_at = + func.get_position().with_source(environment.get_source()); + + if let Some(VariableIdentifier::Standard(name, ..)) = + func.name.as_option_variable_identifier() + { + environment.register_variable_handle_error( + name, + declared_at, + behavior, + checking_data, + ); + } } Exportable::Variable(declaration) => { // TODO mark exported @@ -337,7 +346,7 @@ pub(crate) fn hoist_statements( 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::Module(_) => "module".to_owned(), + parser::functions::FunctionLocationModifier::Worker(_) => "worker".to_owned(), }); synthesise_hoisted_statement_function( @@ -365,7 +374,7 @@ pub(crate) fn hoist_statements( 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::Module(_) => "module".to_owned(), + parser::functions::FunctionLocationModifier::Worker(_) => "worker".to_owned(), }); synthesise_hoisted_statement_function( @@ -382,10 +391,13 @@ pub(crate) fn hoist_statements( environment.context_type.scope { // TODO check existing? - exported.named.push(( - function.name.as_str().to_owned(), - (variable_id, VariableMutability::Constant), - )); + if let Some(VariableIdentifier::Standard(name, ..)) = + function.name.as_option_variable_identifier() + { + exported + .named + .push((name.clone(), (variable_id, VariableMutability::Constant))); + } } } _ => (), @@ -397,7 +409,7 @@ fn import_part_to_name_pair(item: &parser::declarations::ImportPart) -> Option { if let VariableIdentifier::Standard(name, position) = name { - Some(NamePair { value: name, r#as: name, position: position.clone() }) + Some(NamePair { value: name, r#as: name, position: *position }) } else { None } @@ -410,7 +422,7 @@ fn import_part_to_name_pair(item: &parser::declarations::ImportPart) -> Option todo!(), }, r#as: name, - position: position.clone(), + position: *position, }) } parser::declarations::ImportPart::PrefixComment(_, item, _) => { @@ -428,7 +440,7 @@ pub(super) fn export_part_to_name_pair( match item { parser::declarations::export::ExportPart::Name(name) => { if let VariableIdentifier::Standard(name, position) = name { - Some(NamePair { value: name, r#as: name, position: position.clone() }) + Some(NamePair { value: name, r#as: name, position: *position }) } else { None } @@ -441,7 +453,7 @@ pub(super) fn export_part_to_name_pair( | parser::declarations::ImportExportName::Quoted(item, _) => item, parser::declarations::ImportExportName::Cursor(_) => todo!(), }, - position: position.clone(), + position: *position, }) } parser::declarations::export::ExportPart::PrefixComment(_, item, _) => { @@ -517,16 +529,16 @@ fn get_annotation_from_declaration< let result = if let Some(annotation) = declaration.type_annotation.as_ref() { Some(( synthesise_type_annotation(annotation, environment, checking_data), - annotation.get_position().clone().with_source(environment.get_source()), + annotation.get_position().with_source(environment.get_source()), )) } // TODO only under config else if let WithComment::PostfixComment(item, possible_declaration, position) = &declaration.name { - string_comment_to_type( + comment_as_type_annotation( possible_declaration, - &position.clone().with_source(environment.get_source()), + &position.with_source(environment.get_source()), environment, checking_data, ) @@ -534,7 +546,7 @@ fn get_annotation_from_declaration< None }; - if let Some((ty, span)) = result.clone() { + if let Some((ty, span)) = result { let get_position = declaration.get_position(); checking_data .type_mappings @@ -545,7 +557,7 @@ fn get_annotation_from_declaration< result.map(|(value, _span)| value) } -pub(crate) fn string_comment_to_type( +pub(crate) fn comment_as_type_annotation( possible_declaration: &String, position: &source_map::SpanWithSource, environment: &mut crate::context::Context>, @@ -564,7 +576,7 @@ pub(crate) fn string_comment_to_type( if let Ok(annotation) = annotation { Some(( synthesise_type_annotation(&annotation, environment, checking_data), - annotation.get_position().clone().with_source(source), + annotation.get_position().with_source(source), )) } else { // TODO warning diff --git a/checker/src/synthesis/interfaces.rs b/checker/src/synthesis/interfaces.rs index 905a5abd..03f23398 100644 --- a/checker/src/synthesis/interfaces.rs +++ b/checker/src/synthesis/interfaces.rs @@ -1,6 +1,6 @@ use parser::{ types::interface::{InterfaceDeclaration, InterfaceMember}, - Decorated, PropertyKey as ParserPropertyKey, + Decorated, PropertyKey as ParserPropertyKey, WithComment, }; use crate::{ @@ -112,19 +112,20 @@ impl SynthesiseInterfaceBehavior for OnToType { pub(super) fn synthesise_signatures( type_parameters: Option<&[parser::GenericTypeConstraint]>, - signatures: &[Decorated], + signatures: &[WithComment>], mut behavior: B, environment: &mut Environment, checking_data: &mut CheckingData, ) -> B { /// TODO check members declared before fn synthesise_members( - members: &[Decorated], + members: &[WithComment>], environment: &mut Context>, checking_data: &mut CheckingData, interface_register_behavior: &mut B, ) { for member in members { + let member = member.get_ast_ref(); match &member.on { InterfaceMember::Method { header, @@ -136,16 +137,25 @@ pub(super) fn synthesise_signatures { - let behavior = functions::FunctionBehavior::Method { - is_async: header.is_async(), - is_generator: header.is_generator(), - // TODO ... - free_this_id: TypeId::ERROR_TYPE, + // Fix for performing const annotations. TODO want to do better + let behavior = if member + .decorators + .iter() + .any(|a| a.name.first().cloned().as_deref() == Some("DoNotIncludeThis")) + { + functions::FunctionBehavior::ArrowFunction { is_async: header.is_async() } + } else { + functions::FunctionBehavior::Method { + is_async: header.is_async(), + is_generator: header.is_generator(), + // TODO ... + free_this_id: TypeId::ERROR_TYPE, + } }; let getter = match header { - parser::MethodHeader::Get(_) => GetterSetter::Getter, - parser::MethodHeader::Set(_) => GetterSetter::Setter, - parser::MethodHeader::Regular { .. } => GetterSetter::None, + parser::functions::MethodHeader::Get(_) => GetterSetter::Getter, + parser::functions::MethodHeader::Set(_) => GetterSetter::Setter, + parser::functions::MethodHeader::Regular { .. } => GetterSetter::None, }; let function = synthesise_function_annotation( type_parameters, @@ -154,7 +164,7 @@ pub(super) fn synthesise_signatures checking_data.raise_unimplemented_error( "interface constructor", - position.clone().with_source(environment.get_source()), + position.with_source(environment.get_source()), ), InterfaceMember::Caller { parameters, @@ -217,7 +227,7 @@ pub(super) fn synthesise_signatures checking_data.raise_unimplemented_error( "interface caller", - position.clone().with_source(environment.get_source()), + position.with_source(environment.get_source()), ), InterfaceMember::Rule { parameter, @@ -229,9 +239,9 @@ pub(super) fn synthesise_signatures checking_data.raise_unimplemented_error( "interface rule", - position.clone().with_source(environment.get_source()), + position.with_source(environment.get_source()), ), - InterfaceMember::Comment(..) => {} + InterfaceMember::Comment(_, _) => {} } } } diff --git a/checker/src/synthesis/mod.rs b/checker/src/synthesis/mod.rs index 3f5ee9a4..49143721 100644 --- a/checker/src/synthesis/mod.rs +++ b/checker/src/synthesis/mod.rs @@ -43,8 +43,21 @@ pub(super) fn parser_property_key_to_checker_property_key< PropertyKey::String(std::borrow::Cow::Owned(value.clone())) } ParserPropertyKey::NumberLiteral(number, _) => { - // TODO - PropertyKey::from_usize(f64::from(number.clone()) as usize) + 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())) + } + } + // TODO + Err(()) => todo!(), + } } ParserPropertyKey::Computed(expression, _) => { let key_type = @@ -94,37 +107,40 @@ impl<'a> From> for Performs<'a> { pub struct EznoParser; +// Clippy suggests a fix that breaks the code +#[allow(clippy::needless_lifetimes)] impl crate::ASTImplementation for EznoParser { type ParseOptions = parser::ParseOptions; type ParseError = (parser::ParseError, SourceId); - type Module = parser::Module; + + type Module<'a> = parser::Module; type OwnedModule = parser::Module; - type DefinitionFile = parser::TypeDefinitionModule; - type TypeAnnotation = parser::TypeAnnotation; - type TypeParameter = parser::GenericTypeConstraint; - type Expression = parser::Expression; - type ClassMethod = parser::FunctionBase; + + type TypeAnnotation<'a> = parser::TypeAnnotation; + type TypeParameter<'a> = parser::GenericTypeConstraint; + type Expression<'a> = parser::Expression; + type ClassMethod<'a> = parser::FunctionBase; fn module_from_string( source_id: SourceId, string: String, - options: &Self::ParseOptions, - ) -> Result { - ::from_string(string, *options, source_id, None) + options: Self::ParseOptions, + ) -> Result, Self::ParseError> { + ::from_string(string, options, source_id, None) .map_err(|err| (err, source_id)) } fn definition_module_from_string( source_id: SourceId, string: String, - ) -> Result { + ) -> Result, Self::ParseError> { let options = Default::default(); parser::TypeDefinitionModule::from_string(&string, options, source_id) .map_err(|err| (err, source_id)) } - fn synthesise_module( - module: &Self::Module, + fn synthesise_module<'a, T: crate::ReadFromFS>( + module: &Self::Module<'a>, source_id: SourceId, module_environment: &mut Environment, checking_data: &mut crate::CheckingData, @@ -132,8 +148,8 @@ impl crate::ASTImplementation for EznoParser { synthesise_block(&module.items, module_environment, checking_data); } - fn synthesise_expression( - expression: &Self::Expression, + fn synthesise_expression<'a, U: crate::ReadFromFS>( + expression: &Self::Expression<'a>, expecting: TypeId, environment: &mut Environment, checking_data: &mut CheckingData, @@ -141,32 +157,39 @@ impl crate::ASTImplementation for EznoParser { synthesise_expression(expression, environment, checking_data, expecting) } - fn expression_position(expression: &Self::Expression) -> source_map::Span { - ASTNode::get_position(expression).clone() + fn expression_position<'a>(expression: &'a Self::Expression<'a>) -> source_map::Span { + *ASTNode::get_position(expression) } - fn type_definition_file( - tdm: parser::TypeDefinitionModule, - root: &crate::RootContext, - checking_data: &mut crate::CheckingData, - ) -> (Names, Facts) { - definitions::type_definition_file(tdm, checking_data, root) - } - - fn type_parameter_name(parameter: &Self::TypeParameter) -> &str { + fn type_parameter_name<'a>(parameter: &'a Self::TypeParameter<'a>) -> &'a str { parameter.name() } - fn synthesise_type_annotation( - annotation: &Self::TypeAnnotation, + fn synthesise_type_annotation<'a, T: crate::ReadFromFS>( + annotation: &Self::TypeAnnotation<'a>, environment: &mut Environment, checking_data: &mut crate::CheckingData, ) -> TypeId { synthesise_type_annotation(annotation, environment, checking_data) } - fn owned_module_from_module(module: Self::Module) -> Self::OwnedModule { - module + type DefinitionFile<'a> = parser::TypeDefinitionModule; + + fn synthesise_definition_file<'a, T: crate::ReadFromFS>( + file: Self::DefinitionFile<'a>, + root: &RootContext, + checking_data: &mut CheckingData, + ) -> (Names, Facts) { + definitions::type_definition_file(file, checking_data, root) + } + + fn parse_options(_is_js: bool) -> Self::ParseOptions { + // TODO + parser::ParseOptions::default() + } + + fn owned_module_from_module(m: Self::Module<'static>) -> Self::OwnedModule { + m } } @@ -199,7 +222,7 @@ pub mod interactive { let mut root = RootContext::new_with_primitive_references(); let entry_point = PathBuf::from("CLI"); let mut checking_data = - CheckingData::new(Default::default(), resolver, Default::default(), None); + CheckingData::new(Default::default(), resolver, Default::default()); add_definition_files_to_root(type_definition_files, &mut root, &mut checking_data); diff --git a/checker/src/synthesis/statements.rs b/checker/src/synthesis/statements.rs index 78b4c37d..f7404848 100644 --- a/checker/src/synthesis/statements.rs +++ b/checker/src/synthesis/statements.rs @@ -2,12 +2,15 @@ use super::{ expressions::synthesise_multiple_expression, synthesise_block, variables::register_variable, }; use crate::{ - context::{ClosedOverReferencesInScope, ContextId, Scope}, + behavior::{assignments::Reference, operations::CanonicalEqualityAndInequality}, + context::{calling::Target, ClosedOverReferencesInScope, ContextId, Scope}, diagnostics::TypeCheckError, - events::Event, + events::{apply_event, Event, RootReference}, synthesis::EznoParser, - CheckingData, Environment, TypeId, + types::{poly_types::FunctionTypeArguments, Constructor, PolyNature}, + CheckingData, Constant, Environment, TypeId, }; +use map_vec::Map; use parser::{ expressions::MultipleExpression, statements::{ConditionalElseStatement, UnconditionalElseStatement}, @@ -32,46 +35,6 @@ pub(super) fn synthesise_statement( TypeId::ANY_TYPE, ); } - // Statement::ExportStatement(_export_statement) => { - // if let Some(out_exports) = out_exports { - // match export_statement { - // parser::ExportStatement::Variable { exported, is_default, .. } => { - // if *is_default { - // todo!(); - // } - // match exported { - // parser::Exportable::ClassDeclaration(_) - // | parser::Exportable::FunctionDeclaration(_) => panic!(), - // parser::Exportable::VariableDeclaration(variable_decl_stmt) => { - // let is_constant = variable_decl_stmt.is_constant(); - // let position = variable_decl_stmt.get_position().cloned(); - // for variable_declaration in - // variable_decl_stmt.declarations.iter() - // { - // synthesise_variable_declaration( - // variable_declaration, - // is_constant, - // position.as_ref(), - // &Some(out_exports), - // environment, - // checking_data, - // - // module_information, - // )?; - // } - // } - // parser::Exportable::InterfaceDeclaration(_) => todo!(), - // parser::Exportable::TypeAlias(_) => todo!(), - // } - // } - // } - // } else { - // checking_data.add_error( - // TypeCheckError::NonTopLevelExport, - // export_statement.get_position().into(), - // ) - // } - // } Statement::Return(return_statement) => { let returned = if let Some(ref expression) = return_statement.1 { // TODO expecting based of expected return type @@ -85,7 +48,7 @@ pub(super) fn synthesise_statement( TypeId::UNDEFINED_TYPE }; - let position = return_statement.2.clone().with_source(environment.get_source()); + let position = return_statement.2.with_source(environment.get_source()); environment.return_value(returned, position); } @@ -139,156 +102,29 @@ pub(super) fn synthesise_statement( environment, checking_data, ); - - // environment.new_conditional_context( - // condition, - // |environment, checking_data| , - // if !if_statement.else_conditions.is_empty() || if_statement.trailing_else.is_some() - // { - // Some(|environment, checking_data| { - // if let [current, other @ ..] = &if_statement.else_conditions { - // let condition = synthesise_multiple_expression( - // ¤t.condition, - // environment, - // checking_data, - // ); - // environment.new_conditional_context( - // condition, - // |environment, checking_data| { - // synthesise_block_or_single_statement( - // ¤t, - // environment, - // checking_data, - // ) - // }, - // if !other.is_empty() || unconditional_else.is_some() { - // Some(IfStatementBranch::NestedConditional { - // conditional_elses: other, - // unconditional_else: unconditional_else.clone(), - // }) - // } else { - // None - // }, - // checking_data, - // ); - // } else { - // synthesise_block_or_single_statement( - // if_statement.trailing_else.unwrap(), - // environment, - // checking_data, - // ) - // } - // }) - // // Some(IfStatementBranch::NestedConditional { - // // conditional_elses: &if_statement.else_conditions, - // // unconditional_else: if_statement.trailing_else.as_ref(), - // // }) - // } else { - // None - // }, - // checking_data, - // ); - - // Necessary because the parser treats it as a list rather than a tree - // enum IfStatementBranch<'a> { - // Branch(&'a BlockOrSingleStatement), - // NestedConditional { - // conditional_elses: &'a [ConditionalElseStatement], - // unconditional_else: Option<&'a UnconditionalElseStatement>, - // }, - // } - - // // TODO tidy - // impl<'a> SynthesisableConditional for IfStatementBranch<'a> { - // type ExpressionResult = (); - - // fn synthesise_condition( - // self, - // environment: &mut Environment, - // checking_data: &mut CheckingData, - // ) -> Self::ExpressionResult { - // match self { - // IfStatementBranch::Branch(branch) => match branch { - // BlockOrSingleStatement::Braced(statements) => { - // synthesise_block(&statements.0, environment, checking_data) - // } - // BlockOrSingleStatement::SingleStatement(statement) => { - // synthesise_statement(statement, environment, checking_data) - // } - // }, - // IfStatementBranch::NestedConditional { - // conditional_elses, - // unconditional_else, - // } => { - // if let [current, other @ ..] = conditional_elses { - // let condition = synthesise_multiple_expression( - // ¤t.condition, - // environment, - // checking_data, - // ); - // environment.new_conditional_context( - // condition, - // IfStatementBranch::Branch(¤t.inner), - // if !other.is_empty() || unconditional_else.is_some() { - // Some(IfStatementBranch::NestedConditional { - // conditional_elses: other, - // unconditional_else: unconditional_else.clone(), - // }) - // } else { - // None - // }, - // checking_data, - // ); - // } else { - // match &unconditional_else.unwrap().inner { - // BlockOrSingleStatement::Braced(statements) => { - // synthesise_block(&statements.0, environment, checking_data) - // } - // BlockOrSingleStatement::SingleStatement(statement) => { - // synthesise_statement(statement, environment, checking_data) - // } - // } - // } - // } - // } - // } - - // fn conditional_expression_result( - // _: TypeId, - // _: Self::ExpressionResult, - // _: Self::ExpressionResult, - // _: &mut crate::types::TypeStore, - // ) -> Self::ExpressionResult { - // () - // } - - // fn default_result() -> Self::ExpressionResult { - // () - // } - // } } Statement::SwitchStatement(stmt) => { checking_data.diagnostics_container.add_error(TypeCheckError::Unsupported { thing: "Switch statement", - at: stmt.get_position().clone().with_source(environment.get_source()), + at: stmt.get_position().with_source(environment.get_source()), }); } Statement::WhileStatement(stmt) => { checking_data.diagnostics_container.add_error(TypeCheckError::Unsupported { thing: "While statement", - at: stmt.get_position().clone().with_source(environment.get_source()), + at: stmt.get_position().with_source(environment.get_source()), }); } Statement::DoWhileStatement(stmt) => { checking_data.diagnostics_container.add_error(TypeCheckError::Unsupported { thing: "Do while statement", - at: stmt.get_position().clone().with_source(environment.get_source()), + at: stmt.get_position().with_source(environment.get_source()), }); } Statement::ForLoopStatement(stmt) => { checking_data.diagnostics_container.add_error(TypeCheckError::Unsupported { thing: "For statement", - at: stmt.get_position().clone().with_source(environment.get_source()), + at: stmt.get_position().with_source(environment.get_source()), }); // let mut environment = environment.new_lexical_environment(ScopeType::Conditional {}); // match &for_statement.condition { @@ -357,7 +193,7 @@ pub(super) fn synthesise_statement( Statement::Continue(..) | Statement::Break(..) => { checking_data.raise_unimplemented_error( "continue and break statements", - statement.get_position().clone().with_source(environment.get_source()), + statement.get_position().with_source(environment.get_source()), ); } Statement::Throw(stmt) => { @@ -367,20 +203,20 @@ pub(super) fn synthesise_statement( checking_data, TypeId::ANY_TYPE, ); - let thrown_position = stmt.2.clone().with_source(environment.get_source()); + let thrown_position = stmt.2.with_source(environment.get_source()); environment.throw_value(thrown_value, thrown_position); } Statement::Labelled { position, name, statement } => { checking_data.raise_unimplemented_error( "labelled statements", - statement.get_position().clone().with_source(environment.get_source()), + statement.get_position().with_source(environment.get_source()), ); synthesise_statement(statement, environment, checking_data); } Statement::VarVariable(_) => { checking_data.raise_unimplemented_error( "var variables statements", - statement.get_position().clone().with_source(environment.get_source()), + statement.get_position().with_source(environment.get_source()), ); } Statement::TryCatchStatement(stmt) => { diff --git a/checker/src/synthesis/type_annotations.rs b/checker/src/synthesis/type_annotations.rs index 78372f4c..7002fa32 100644 --- a/checker/src/synthesis/type_annotations.rs +++ b/checker/src/synthesis/type_annotations.rs @@ -71,7 +71,9 @@ pub(super) fn synthesise_type_annotation( checking_data.types.new_constant_type(Constant::String(value.clone())) } TypeAnnotation::NumberLiteral(value, _) => { - let constant = Constant::Number(f64::from(value.clone()).try_into().unwrap()); + let constant = Constant::Number( + f64::try_from(value.clone()).expect("big int number type").try_into().unwrap(), + ); checking_data.types.new_constant_type(constant) } TypeAnnotation::BooleanLiteral(value, _) => { @@ -85,13 +87,13 @@ pub(super) fn synthesise_type_annotation( if let Some(ty) = environment.get_type_from_name(name) { // Warn if it requires parameters. e.g. Array if let Type::AliasTo { parameters: Some(_), .. } - | Type::NamedRooted { parameters: Some(_), .. } = checking_data.types.get_type_by_id(ty) + | Type::Interface { parameters: Some(_), .. } = checking_data.types.get_type_by_id(ty) { // TODO check defaults... checking_data.diagnostics_container.add_error( TypeCheckError::TypeNeedsTypeArguments( name, - pos.clone().with_source(environment.get_source()), + pos.with_source(environment.get_source()), ), ); TypeId::ANY_TYPE @@ -101,7 +103,7 @@ pub(super) fn synthesise_type_annotation( } else { checking_data.diagnostics_container.add_error(TypeCheckError::CannotFindType( name, - pos.clone().with_source(environment.get_source()), + pos.with_source(environment.get_source()), )); TypeId::ERROR_TYPE } @@ -164,7 +166,6 @@ pub(super) fn synthesise_type_annotation( add_property_restrictions: true, position: argument_type_annotation .get_position() - .clone() .with_source(environment.get_source()), }; @@ -200,7 +201,7 @@ pub(super) fn synthesise_type_annotation( &checking_data.types, checking_data.options.debug_types, ), - position: argument_type_annotation.get_position().clone().with_source(environment.get_source()), + position: argument_type_annotation.get_position().with_source(environment.get_source()), }; checking_data.diagnostics_container.add_error(error); @@ -208,7 +209,6 @@ pub(super) fn synthesise_type_annotation( let with_source = argument_type_annotation .get_position() - .clone() .with_source(environment.get_source()); type_arguments.insert(parameter, (argument, with_source)); @@ -232,7 +232,7 @@ pub(super) fn synthesise_type_annotation( checking_data.diagnostics_container.add_error( TypeCheckError::TypeHasNoGenericParameters( name.clone(), - position.clone().with_source(environment.get_source()), + position.with_source(environment.get_source()), ), ); TypeId::ERROR_TYPE @@ -245,7 +245,7 @@ pub(super) fn synthesise_type_annotation( position, .. } => { - let position = position.clone().with_source(environment.get_source()); + let position = position.with_source(environment.get_source()); let function_type = synthesise_function_annotation( type_parameters, parameters, @@ -288,8 +288,7 @@ pub(super) fn synthesise_type_annotation( TypeAnnotation::NamespacedName(_, _, _) => unimplemented!(), TypeAnnotation::ArrayLiteral(item_annotation, _) => { let item_type = synthesise_type_annotation(item_annotation, environment, checking_data); - let with_source = - item_annotation.get_position().clone().with_source(environment.get_source()); + let with_source = item_annotation.get_position().with_source(environment.get_source()); let ty = Type::Constructor(Constructor::StructureGenerics(StructureGenerics { on: TypeId::ARRAY_TYPE, arguments: StructureGenericArguments { @@ -346,10 +345,8 @@ pub(super) fn synthesise_type_annotation( let item_ty = synthesise_type_annotation(type_annotation, environment, checking_data); - let ty_position = type_annotation - .get_position() - .clone() - .with_source(environment.get_source()); + let ty_position = + type_annotation.get_position().with_source(environment.get_source()); obj.append( environment, @@ -386,10 +383,15 @@ pub(super) fn synthesise_type_annotation( let being_indexed = synthesise_type_annotation(being_indexed, environment, checking_data); let indexer = synthesise_type_annotation(indexer, environment, checking_data); - if let Some(prop) = environment.get_property_unbound( + let under = + crate::types::properties::PropertyKey::from_type(indexer, &checking_data.types); + + if let Some(base) = environment.get_poly_base(being_indexed, &checking_data.types) { + checking_data.types.new_property_constructor(being_indexed, indexer, base) + } else if let Some(prop) = environment.get_property_unbound( being_indexed, Publicity::Public, - crate::types::properties::PropertyKey::Type(indexer), + under, &checking_data.types, ) { match prop { @@ -398,13 +400,9 @@ pub(super) fn synthesise_type_annotation( crate::context::Logical::Implies { .. } => todo!(), } } else { - todo!("Error") + crate::utils::notify!("Error: no index on type annotation"); + TypeId::ERROR_TYPE } - // indexee.get_property_using_type_annotation( - // &indexer, - // checking_data, - // crate::types::GetPropertySettings::HowAboutNo, - // )) } TypeAnnotation::KeyOf(_, _) => unimplemented!(), TypeAnnotation::Conditional { condition, resolve_true, resolve_false, position } => { @@ -451,7 +449,7 @@ pub(super) fn synthesise_type_annotation( checking_data .type_mappings .types_to_types - .push(annotation.get_position().clone().with_source(environment.get_source()), ty); + .push(annotation.get_position().with_source(environment.get_source()), ty); ty } diff --git a/checker/src/synthesis/variables.rs b/checker/src/synthesis/variables.rs index 01aa0264..0f2dc256 100644 --- a/checker/src/synthesis/variables.rs +++ b/checker/src/synthesis/variables.rs @@ -37,7 +37,7 @@ pub(crate) fn register_variable { let ty = environment.register_variable_handle_error( name, - pos.clone().with_source(environment.get_source()), + pos.with_source(environment.get_source()), behavior, checking_data, ); @@ -117,10 +117,7 @@ pub(crate) fn register_variable( { exported.named.push((name.as_str().to_owned(), (id, mutability))); } else { - todo!("emit error here") + checking_data.diagnostics_container.add_error( + TypeCheckError::NonTopLevelExport( + name.get_position().with_source(environment.get_source()), + ), + ); } } } @@ -303,10 +303,8 @@ fn assign_to_fields( ArrayDestructuringField::Name(variable_field, _) => { let idx = PropertyKey::from_usize(idx); - let field_position = variable_field - .get_position() - .clone() - .with_source(environment.get_source()); + let field_position = + variable_field.get_position().with_source(environment.get_source()); let value = environment.get_property( value, @@ -350,7 +348,7 @@ fn assign_to_fields( }; let get_position_with_source = - get_position.clone().with_source(environment.get_source()); + get_position.with_source(environment.get_source()); // TODO if LHS = undefined ...? conditional // TODO record information @@ -398,7 +396,7 @@ fn assign_to_fields( key_ty, &mut checking_data.types, None, - position.clone().with_source(environment.get_source()), + position.with_source(environment.get_source()), ); let value = match property_value { diff --git a/checker/src/types/calling.rs b/checker/src/types/calling.rs index 352523ea..a5edcbf2 100644 --- a/checker/src/types/calling.rs +++ b/checker/src/types/calling.rs @@ -14,7 +14,7 @@ use crate::{ subtyping::{type_is_subtype, BasicEquality, NonEqualityReason, SubTypeResult}, types::{ functions::SynthesisedArgument, poly_types::generic_type_arguments::TypeArgumentStore, - substitute, + printing::print_type, substitute, }, types::{FunctionType, Type}, FunctionId, SpecialExpressions, TypeId, @@ -49,12 +49,7 @@ pub fn call_type_handle_errors (TypeId, Option) { let result = call_type( ty, - CallingInput { - called_with_new, - this_value, - call_site_type_arguments, - call_site: call_site.clone(), - }, + CallingInput { called_with_new, this_value, call_site_type_arguments, call_site }, arguments, environment, &mut CheckThings, @@ -66,7 +61,7 @@ pub fn call_type_handle_errors( } if let Some(constraint) = environment.get_poly_base(on, types) { + crate::utils::notify!("Evaluating generic call"); create_generic_function_call( constraint, CallingInput { called_with_new, this_value, call_site_type_arguments, call_site }, @@ -160,6 +156,7 @@ fn call_logical( environment: &mut Environment, behavior: &mut E, ) -> Result> { + crate::utils::notify!("Calling logical"); match logical { Logical::Pure((func, this_value)) => { if let Some(function_type) = types.functions.get(&func) { @@ -224,7 +221,7 @@ fn get_logical_callable_from_type( } get_logical_callable_from_type(*to, types) } - Type::NamedRooted { .. } | Type::Constant(_) | Type::Object(_) => None, + Type::Interface { .. } | Type::Constant(_) | Type::Object(_) => None, Type::FunctionReference(f, t) | Type::Function(f, t) => Some(Logical::Pure((*f, *t))), Type::SpecialObject(_) => todo!(), } @@ -239,15 +236,9 @@ fn create_generic_function_call( behavior: &mut E, types: &mut TypeStore, ) -> Result> { - // TODO don't like how it is mixed let result = call_type( constraint, - CallingInput { - called_with_new, - this_value, - call_site_type_arguments, - call_site: call_site.clone(), - }, + CallingInput { called_with_new, this_value, call_site_type_arguments, call_site }, // TODO clone arguments.clone(), environment, @@ -278,7 +269,7 @@ fn create_generic_function_call( timing: crate::events::CallingTiming::Synchronous, called_with_new, // Don't care about output. reflects_dependency: None, - position: call_site.clone(), + position: call_site, }); return Ok(result); @@ -304,7 +295,7 @@ fn create_generic_function_call( timing: crate::events::CallingTiming::Synchronous, called_with_new, reflects_dependency, - position: call_site.clone(), + position: call_site, }); // TODO should wrap result in open poly @@ -405,7 +396,7 @@ impl FunctionType { if let (Some(const_fn_ident), true) = (self.constant_function.as_deref(), call_constant) { let has_dependent_argument = arguments.iter().any(|arg| { types.get_type_by_id(arg.to_type().expect("dependent spread types")).is_dependent() - }); + }) || matches!(this_value, ThisValue::Passed(ty) if types.get_type_by_id(ty).is_dependent()); // TODO temp, need a better solution let call_anyway = matches!( @@ -472,7 +463,7 @@ impl FunctionType { called_with_new, this_value, call_site_type_arguments, - call_site: call_site.clone(), + call_site, }, parent_type_arguments, arguments, @@ -500,7 +491,7 @@ impl FunctionType { reflects_dependency: Some(ty), timing: crate::events::CallingTiming::Synchronous, called_with_new, - position: call_site.clone(), + position: call_site, }); return Ok(FunctionCallResult { @@ -538,7 +529,9 @@ impl FunctionType { FunctionBehavior::Method { free_this_id, .. } => { let value_of_this = this_value.get_passed().expect("method has no 'this' passed :?"); - crate::utils::notify!("ftid {:?} & vot {:?}", free_this_id, value_of_this); + + crate::utils::notify!("ft id {:?} & vot {:?}", free_this_id, value_of_this); + // TODO checking seeding_context .type_arguments @@ -573,9 +566,7 @@ impl FunctionType { } FunctionBehavior::Constructor { non_super_prototype, this_object_type } => { if let CalledWithNew::None = called_with_new { - errors.push(FunctionCallingError::NeedsToBeCalledWithNewKeyword( - call_site.clone(), - )); + errors.push(FunctionCallingError::NeedsToBeCalledWithNewKeyword(call_site)); } } } @@ -633,7 +624,7 @@ impl FunctionType { for (restriction, restriction_position) in restrictions_for_item { let mut behavior = BasicEquality { add_property_restrictions: false, - position: restriction_position.clone(), + position: *restriction_position, }; let result = @@ -653,7 +644,7 @@ impl FunctionType { false, ); - let restriction = Some((restriction_position.clone(), restriction_type)); + let restriction = Some((*restriction_position, restriction_type)); let synthesised_parameter = &self.parameters.parameters[param]; let parameter_type = TypeStringRepresentation::from_type_id( synthesised_parameter.ty, @@ -664,9 +655,9 @@ impl FunctionType { errors.push(FunctionCallingError::InvalidArgumentType { argument_type, - argument_position: argument_position.clone(), + argument_position, parameter_type, - parameter_position: synthesised_parameter.position.clone(), + parameter_position: synthesised_parameter.position, restriction, }); } @@ -809,7 +800,7 @@ impl FunctionType { }); // let (auto_inserted_arg, argument_type) = - // if argument_type.is_none() && checking_data.settings.allow_elided_arguments { + // if argument_type.is_none() && checking_data.options.allow_elided_arguments { // (true, Some(Cow::Owned(crate::types::Term::Undefined.into()))) // } else { // (false, argument_type.map(Cow::Borrowed)) @@ -817,7 +808,7 @@ impl FunctionType { if let Some((argument_type, argument_position)) = argument_type_and_pos { seeding_context.argument_position_and_parameter_idx = - (argument_position.clone(), parameter_idx); + (*argument_position, parameter_idx); // crate::utils::notify!( // "param {}, arg {}", @@ -849,8 +840,8 @@ impl FunctionType { types, false, ), - parameter_position: parameter.position.clone(), - argument_position: argument_position.clone(), + parameter_position: parameter.position, + argument_position: *argument_position, restriction: None, }); } @@ -858,7 +849,7 @@ impl FunctionType { // Already checked so can set. TODO destructuring etc seeding_context.set_id( parameter.ty, - (*argument_type, argument_position.clone(), parameter_idx), + (*argument_type, *argument_position, parameter_idx), false, ); } @@ -872,8 +863,8 @@ impl FunctionType { } else { // TODO group errors.push(FunctionCallingError::MissingArgument { - parameter_position: parameter.position.clone(), - call_site: call_site.clone(), + parameter_position: parameter.position, + call_site: *call_site, }); } @@ -945,8 +936,8 @@ impl FunctionType { types, false, ), - argument_position: argument_pos.clone(), - parameter_position: rest_parameter.position.clone(), + argument_position: *argument_pos, + parameter_position: rest_parameter.position, restriction: None, }); } @@ -955,7 +946,7 @@ impl FunctionType { } } } else { - // TODO types.settings.allow_extra_arguments + // TODO types.options.allow_extra_arguments let mut left_over = arguments.iter().skip(self.parameters.parameters.len()); let first = left_over.next().unwrap(); let mut count = 1; @@ -990,14 +981,12 @@ impl FunctionType { .0 .iter() .zip(call_site_type_arguments) - .map(|(param, (ty, pos))| { + .map(|(param, (ty, position))| { if let Type::RootPolyType(PolyNature::Generic { eager_fixed, .. }) = types.get_type_by_id(param.id) { - let mut basic_subtyping = BasicEquality { - add_property_restrictions: false, - position: pos.clone(), - }; + let mut basic_subtyping = + BasicEquality { add_property_restrictions: false, position }; let type_is_subtype = type_is_subtype( *eager_fixed, ty, @@ -1017,7 +1006,7 @@ impl FunctionType { // crate::utils::notify!("Generic parameter with no aliasing restriction, I think this fine on internals"); }; - (param.id, vec![(ty, pos)]) + (param.id, vec![(ty, position)]) }) .collect() } else { diff --git a/checker/src/types/classes.rs b/checker/src/types/classes.rs index dbf934bc..8aee87ad 100644 --- a/checker/src/types/classes.rs +++ b/checker/src/types/classes.rs @@ -14,11 +14,11 @@ pub enum PropertyFunctionProperty { Standard { is_async: bool, is_generator: bool }, } -pub struct ClassValue<'a, M: ASTImplementation> { +pub struct ClassValue<'a, A: crate::ASTImplementation> { pub publicity: Publicity, /// Created eagerly, don't specialise pub key: PropertyKey<'static>, - pub value: Option<&'a M::Expression>, + pub value: Option<&'a A::Expression<'a>>, } pub struct SynthesisedClassValue { @@ -45,11 +45,11 @@ pub struct RegisterClassPropertiesEvent { pub class_prototype: TypeId, } -fn register_properties_into_store( +fn register_properties_into_store( environment: &mut Environment, class_prototype: TypeId, - properties: ClassPropertiesToRegister<'_, M>, - checking_data: &mut CheckingData, + properties: ClassPropertiesToRegister<'_, A>, + checking_data: &mut CheckingData, ) { let scope = crate::Scope::Function(crate::context::environment::FunctionScope::Constructor { extends: false, @@ -78,16 +78,16 @@ fn register_properties_into_store( environment: &mut Environment, on: TypeId, - checking_data: &mut CheckingData, - properties: ClassPropertiesToRegister, + checking_data: &mut CheckingData, + properties: ClassPropertiesToRegister, ) { for ClassValue { publicity, key, value } in properties.0 { let value = if let Some(expression) = value { - PropertyValue::Value(M::synthesise_expression( + PropertyValue::Value(A::synthesise_expression( expression, TypeId::ANY_TYPE, environment, diff --git a/checker/src/types/functions.rs b/checker/src/types/functions.rs index 49a1a853..36f2dd43 100644 --- a/checker/src/types/functions.rs +++ b/checker/src/types/functions.rs @@ -49,14 +49,14 @@ pub struct FunctionType { impl FunctionType { pub(crate) fn new_auto_constructor< T: crate::ReadFromFS, - M: crate::ASTImplementation, + A: crate::ASTImplementation, S: ContextType, >( class_prototype: TypeId, - properties: ClassPropertiesToRegister, + properties: ClassPropertiesToRegister, // TODO S overkill context: &mut crate::context::Context, - checking_data: &mut CheckingData, + checking_data: &mut CheckingData, ) -> Self { let scope = Scope::Function(FunctionScope::Constructor { extends: false, @@ -188,7 +188,7 @@ pub enum SynthesisedArgument { impl SynthesisedArgument { pub(crate) fn get_position(&self) -> SpanWithSource { match self { - SynthesisedArgument::NonSpread { ty: _, position } => position.clone(), + SynthesisedArgument::NonSpread { ty: _, position } => *position, } } diff --git a/checker/src/types/mod.rs b/checker/src/types/mod.rs index e2aaf22a..95707062 100644 --- a/checker/src/types/mod.rs +++ b/checker/src/types/mod.rs @@ -108,7 +108,7 @@ pub enum Type { /// For number and other rooted types /// /// Although they all alias Object - NamedRooted { + Interface { name: String, // Whether only values under this type can be matched nominal: bool, @@ -167,7 +167,7 @@ pub enum ObjectNature { impl Type { pub(crate) fn get_parameters(&self) -> Option> { - if let Type::NamedRooted { parameters, .. } | Type::AliasTo { parameters, .. } = self { + if let Type::Interface { parameters, .. } | Type::AliasTo { parameters, .. } = self { parameters.clone() } else { None @@ -185,7 +185,7 @@ impl Type { // TODO what about if left or right Type::And(_, _) | Type::Or(_, _) => false, // TODO what about if it aliases - Type::AliasTo { .. } | Type::NamedRooted { .. } => { + Type::AliasTo { .. } | Type::Interface { .. } => { // TODO not sure false } @@ -296,7 +296,7 @@ pub fn is_type_truthy_falsy(id: TypeId, types: &TypeStore) -> Decidable { | Type::Or(_, _) | Type::RootPolyType(_) | Type::Constructor(_) - | Type::NamedRooted { .. } => { + | Type::Interface { .. } => { // TODO some of these case are known Decidable::Unknown(id) } @@ -356,7 +356,7 @@ impl SubtypeBehavior for SeedingContext { restriction_mode: bool, ) -> Result<(), NonEqualityReason> { // TODO - let (parameter_pos, parameter_idx) = self.argument_position_and_parameter_idx.clone(); + let (parameter_pos, parameter_idx) = self.argument_position_and_parameter_idx; self.set_id(parameter, (value, parameter_pos, parameter_idx), restriction_mode); Ok(()) } @@ -399,7 +399,7 @@ impl SubtypeBehavior for BasicEquality { ) { let result = environment .deferred_function_constraints - .insert(function_id, (function_type, self.position.clone())); + .insert(function_id, (function_type, self.position)); debug_assert!(result.is_none()); } @@ -437,3 +437,17 @@ pub enum PropertyError { Missing, Invalid { expected: TypeId, found: TypeId, mismatch: NonEqualityReason }, } + +/// TODO temp fix for printing +pub(crate) fn is_explicit_generic(on: TypeId, types: &TypeStore) -> bool { + if let Type::RootPolyType(PolyNature::Generic { .. }) = types.get_type_by_id(on) { + true + } else if let Type::Constructor(Constructor::Property { on, under, result }) = + types.get_type_by_id(on) + { + is_explicit_generic(*on, types) + || matches!(under, PropertyKey::Type(under) if is_explicit_generic(*under, types)) + } else { + false + } +} diff --git a/checker/src/types/others.rs b/checker/src/types/others.rs index 716e0f70..a900bc03 100644 --- a/checker/src/types/others.rs +++ b/checker/src/types/others.rs @@ -47,7 +47,7 @@ pub(crate) fn create_object_for_type( } Type::RootPolyType(_) => todo!(), Type::Constructor(_) => todo!(), - Type::NamedRooted { name, parameters, nominal } => { + Type::Interface { name, parameters, nominal } => { let name = name.clone(); // TODO: Do we need positions for the following appends? diff --git a/checker/src/types/poly_types/substitution.rs b/checker/src/types/poly_types/substitution.rs index 9a4c36ca..1cdf64b6 100644 --- a/checker/src/types/poly_types/substitution.rs +++ b/checker/src/types/poly_types/substitution.rs @@ -45,7 +45,7 @@ pub(crate) fn substitute( | Type::AliasTo { .. } | Type::And(_, _) | Type::Or(_, _) - | Type::NamedRooted { .. } => id, + | Type::Interface { .. } => id, Type::RootPolyType(nature) => { if let PolyNature::Open(_) = nature { id diff --git a/checker/src/types/printing.rs b/checker/src/types/printing.rs index 14b7862a..4702b12c 100644 --- a/checker/src/types/printing.rs +++ b/checker/src/types/printing.rs @@ -120,7 +120,7 @@ fn print_type_into_buf( } if matches!( types.get_type_by_id(*on), - Type::NamedRooted { .. } | Type::Function { .. } + Type::Interface { .. } | Type::Function { .. } ) && !arguments.type_arguments.is_empty() { // TODO might be out of order ... @@ -170,12 +170,31 @@ fn print_type_into_buf( unreachable!() } }, + Constructor::Property { on, under, result } => { + if crate::types::is_explicit_generic(*on, types) { + print_type_into_buf(*on, buf, cycles, types, ctx, debug); + buf.push('['); + match under { + PropertyKey::String(s) => { + buf.push('"'); + buf.push_str(s); + buf.push('"'); + } + PropertyKey::Type(t) => { + print_type_into_buf(*t, buf, cycles, types, ctx, debug); + } + }; + buf.push(']'); + } else { + print_type_into_buf(*result, buf, cycles, types, ctx, debug); + } + } constructor => { let base = get_on_ctx!(ctx.get_poly_base(id, types)).unwrap(); print_type_into_buf(base, buf, cycles, types, ctx, debug); } }, - Type::NamedRooted { name, parameters, nominal } => { + Type::Interface { name, parameters, nominal } => { if debug { write!(buf, "(r{} nom={:?}) {name}", id.0, nominal).unwrap(); // buf.push_str("{ "); @@ -216,7 +235,7 @@ fn print_type_into_buf( if debug { write!( buf, - "[func {}, fvs {:?}, co {:?}, this {:?}, const {:?}] ", + "[t{} func, fvs {:?}, co {:?}, this {:?}, const {:?}] ", id.0, func.free_variables, func.closed_over_variables, diff --git a/checker/src/types/properties.rs b/checker/src/types/properties.rs index 736d5513..d113e93e 100644 --- a/checker/src/types/properties.rs +++ b/checker/src/types/properties.rs @@ -235,7 +235,7 @@ fn get_from_an_object( | Type::Object(..) | Type::RootPolyType { .. } | Type::Constant(..) => Some((PropertyKind::Direct, value)), - Type::NamedRooted { .. } | Type::And(_, _) | Type::Or(_, _) => { + Type::Interface { .. } | Type::And(_, _) | Type::Or(_, _) => { crate::utils::notify!( "property was {:?} {:?}, which should be NOT be able to be returned from a function", property, ty @@ -348,9 +348,18 @@ fn evaluate_get_on_poly( match types.get_type_by_id(og) { Type::FunctionReference(func, _) => { // TODO only want to do sometimes, or even never as it can be pulled using the poly chain - let with_this = types - .register_type(Type::Function(*func, ThisValue::Passed(og))); - Some(with_this) + let depends_on_this = + types.get_function_from_id(*func).behavior.can_be_bound(); + + if depends_on_this { + let with_this = types.register_type(Type::Function( + *func, + ThisValue::Passed(on), + )); + Some(with_this) + } else { + Some(og) + } } Type::Function(..) => todo!(), Type::And(_, _) @@ -358,7 +367,7 @@ fn evaluate_get_on_poly( | Type::Constructor(_) | Type::Or(_, _) | Type::AliasTo { .. } - | Type::NamedRooted { .. } => { + | Type::Interface { .. } => { // TODO this isn't necessary sometimes let constructor_result = types.register_type(Type::Constructor(Constructor::Property { diff --git a/checker/src/types/store.rs b/checker/src/types/store.rs index f06f7e44..963277ee 100644 --- a/checker/src/types/store.rs +++ b/checker/src/types/store.rs @@ -8,7 +8,7 @@ use crate::{ FunctionId, GeneralContext, TypeId, }; -use super::{Constructor, StructureGenerics, TypeRelationOperator}; +use super::{properties::PropertyKey, Constructor, StructureGenerics, TypeRelationOperator}; /// Holds all the types. Eventually may be split across modules #[derive(Debug)] @@ -32,15 +32,15 @@ impl Default for TypeStore { fn default() -> Self { // These have to be in the order of TypeId let mut types = vec![ - Type::NamedRooted { name: "error".to_owned(), parameters: None, nominal: true }, - Type::NamedRooted { name: "never".to_owned(), parameters: None, nominal: true }, - Type::NamedRooted { name: "any".to_owned(), parameters: None, nominal: true }, - Type::NamedRooted { name: "boolean".to_owned(), parameters: None, nominal: true }, - Type::NamedRooted { name: "number".to_owned(), parameters: None, nominal: true }, - Type::NamedRooted { name: "string".to_owned(), parameters: None, nominal: true }, - Type::NamedRooted { name: "undefined".to_owned(), parameters: None, nominal: true }, - Type::NamedRooted { name: "null".to_owned(), parameters: None, nominal: true }, - Type::NamedRooted { + Type::Interface { name: "error".to_owned(), parameters: None, nominal: true }, + Type::Interface { name: "never".to_owned(), parameters: None, nominal: true }, + Type::Interface { name: "any".to_owned(), parameters: None, nominal: true }, + Type::Interface { name: "boolean".to_owned(), parameters: None, nominal: true }, + Type::Interface { name: "number".to_owned(), parameters: None, nominal: true }, + Type::Interface { name: "string".to_owned(), parameters: None, nominal: true }, + Type::Interface { name: "undefined".to_owned(), parameters: None, nominal: true }, + Type::Interface { name: "null".to_owned(), parameters: None, nominal: true }, + Type::Interface { name: "Array".to_owned(), parameters: Some(vec![TypeId::T_TYPE]), nominal: true, @@ -50,9 +50,9 @@ impl Default for TypeStore { name: "T".to_owned(), eager_fixed: TypeId::ANY_TYPE, }), - Type::NamedRooted { name: "object".to_owned(), parameters: None, nominal: false }, - Type::NamedRooted { name: "Function".to_owned(), parameters: None, nominal: false }, - Type::NamedRooted { name: "RegExp".to_owned(), parameters: None, nominal: true }, + Type::Interface { name: "object".to_owned(), parameters: None, nominal: false }, + Type::Interface { name: "Function".to_owned(), parameters: None, nominal: false }, + Type::Interface { name: "RegExp".to_owned(), parameters: None, nominal: true }, Type::Or(TypeId::STRING_TYPE, TypeId::NUMBER_TYPE), // true Type::Constant(crate::Constant::Boolean(true)), @@ -292,9 +292,9 @@ impl TypeStore { // Don't think any properties exist on this poly type let constraint = ctx.get_poly_base(on, self).unwrap(); // TODO might need to send more information here, rather than forgetting via .get_type - self.get_fact_about_type(ctx, constraint, resolver, data) + self.get_fact_about_type(ctx, on, resolver, data) } - Type::Object(..) | Type::NamedRooted { .. } => ctx + Type::Object(..) | Type::Interface { .. } => ctx .parents_iter() .find_map(|env| resolver(&env, self, on, data)) .map(Logical::Pure) @@ -335,4 +335,16 @@ impl TypeStore { self.functions.insert(id, function_type); self.register_type(Type::Function(id, Default::default())) } + + #[allow(clippy::similar_names)] + pub(crate) fn new_property_constructor( + &mut self, + indexee: TypeId, + indexer: TypeId, + base: TypeId, + ) -> TypeId { + let under = PropertyKey::from_type(indexer, self); + let ty = Type::Constructor(Constructor::Property { on: indexee, under, result: base }); + self.register_type(ty) + } } diff --git a/checker/src/types/subtyping.rs b/checker/src/types/subtyping.rs index 7e41566e..2c71eaef 100644 --- a/checker/src/types/subtyping.rs +++ b/checker/src/types/subtyping.rs @@ -446,7 +446,24 @@ fn type_is_subtype2( result_union, } => todo!(), Constructor::FunctionResult { on, with, result } => todo!(), - Constructor::Property { on, under, result } => todo!(), + Constructor::Property { on, under, result: _ } => { + // Ezno custom behavior + // TODO might be based of T + if let Type::Constructor(Constructor::Property { + on: r_on, + under: r_under, + result: _, + }) = right_ty + { + if on == r_on && under == r_under { + SubTypeResult::IsSubType + } else { + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + } + } else { + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + } + } Constructor::StructureGenerics(_) => unreachable!(), }, // TODO aliasing might work differently @@ -465,7 +482,7 @@ fn type_is_subtype2( restriction_mode, ) } - Type::NamedRooted { nominal: base_type_nominal, .. } => { + Type::Interface { nominal: base_type_nominal, .. } => { // If type matched type it would have been cleared before. So looking at properties and // prototypes here @@ -553,7 +570,7 @@ fn type_is_subtype2( restriction_mode, ) } - Type::AliasTo { .. } | Type::NamedRooted { .. } => { + Type::AliasTo { .. } | Type::Interface { .. } => { crate::utils::notify!("lhs={:?} rhs={:?}", left_ty, right_ty); // TODO SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) diff --git a/parser/Cargo.toml b/parser/Cargo.toml index e2a3259e..787ac702 100644 --- a/parser/Cargo.toml +++ b/parser/Cargo.toml @@ -49,7 +49,7 @@ get-field-by-type = "0.0.3" serde = { version = "1.0", features = ["derive"], optional = true } self-rust-tokenize = { version = "0.3.3", optional = true } -source-map = { version = "0.14.6", features = [ +source-map = { version = "0.14.7", features = [ "serde-serialize", "self-rust-tokenize", ] } diff --git a/parser/examples/chain.rs b/parser/examples/chain.rs index 946e427d..65d11e81 100644 --- a/parser/examples/chain.rs +++ b/parser/examples/chain.rs @@ -1,5 +1,5 @@ use ezno_parser::{ - visiting::{Annex, Chain, VisitSettings, Visitable, Visitor, Visitors}, + visiting::{Annex, Chain, VisitOptions, Visitable, Visitor, Visitors}, ASTNode, Expression, SourceId, }; @@ -23,7 +23,7 @@ fn main() { expr.visit( &mut Visitors { expression_visitors: vec![Box::new(ShowChain)], ..Default::default() }, &mut (), - &VisitSettings::default(), + &VisitOptions::default(), &mut Annex::new(&mut Chain::new()), ); } diff --git a/parser/examples/code_blocks_to_script.rs b/parser/examples/code_blocks_to_script.rs new file mode 100644 index 00000000..e35306f3 --- /dev/null +++ b/parser/examples/code_blocks_to_script.rs @@ -0,0 +1,115 @@ +use std::collections::HashSet; + +use ezno_parser::{ + visiting::{VisitOptions, Visitors}, + ASTNode, Declaration, Module, StatementOrDeclaration, +}; +use source_map::SourceId; + +fn main() -> Result<(), Box> { + let path = std::env::args().nth(1).ok_or("expected argument")?; + let content = std::fs::read_to_string(&path)?; + + let filters: HashSet<&str> = HashSet::from_iter(["import", "export"]); + + let blocks = if path.ends_with(".md") { + let mut blocks = Vec::new(); + + let mut lines = content.lines(); + while let Some(line) = lines.next() { + if line.starts_with("```ts") { + let code = lines.by_ref().take_while(|line| !line.starts_with("```")).fold( + String::new(), + |mut a, s| { + a.push_str(s); + a.push('\n'); + a + }, + ); + if !filters.iter().any(|filter| code.contains(filter)) { + blocks.push(code); + } + } + } + blocks + } else { + todo!("parse module, split by statement braced") + }; + + let mut final_blocks: Vec<(HashSet, String)> = Vec::new(); + for code in blocks { + let module = + match Module::from_string(code.clone(), Default::default(), SourceId::NULL, None) { + Ok(module) => module, + Err(err) => return Err(Box::new(err)), + }; + + let mut names = HashSet::new(); + + let mut visitors = Visitors { + expression_visitors: Default::default(), + statement_visitors: Default::default(), + variable_visitors: vec![Box::new(NameFinder)], + block_visitors: Default::default(), + }; + module.visit::>( + &mut visitors, + &mut names, + &VisitOptions { visit_nested_blocks: false, reverse_statements: false }, + ); + + // TODO quick fix + for s in module.items { + match s { + StatementOrDeclaration::Declaration(Declaration::TypeAlias(t)) => { + names.insert(t.type_name.name.clone()); + } + StatementOrDeclaration::Declaration(Declaration::Interface(i)) => { + names.insert(i.on.name.clone()); + } + _ => {} + } + } + + if let Some((items, block)) = + final_blocks.iter_mut().find(|(uses, _)| uses.is_disjoint(&names)) + { + items.extend(names.into_iter()); + block.push('\n'); + block.push_str(&code); + } else { + final_blocks.push((names, code)); + } + } + + eprintln!("{:?} blocks", final_blocks.len()); + + for (_items, block) in final_blocks { + // eprintln!("block includes: {:?}", items); + // eprintln!("{}", block); + // eprintln!("---"); + println!("{{\n{block}}};") + } + + Ok(()) +} + +struct NameFinder; + +impl<'a> + ezno_parser::visiting::Visitor< + ezno_parser::visiting::ImmutableVariableOrProperty<'a>, + HashSet, + > for NameFinder +{ + fn visit( + &mut self, + item: &ezno_parser::visiting::ImmutableVariableOrProperty<'a>, + data: &mut HashSet, + _chain: &ezno_parser::visiting::Chain, + ) { + if let Some(name) = item.get_variable_name() { + data.insert(name.to_owned()); + } + } +} diff --git a/parser/examples/depth.rs b/parser/examples/depth.rs new file mode 100644 index 00000000..db44d76a --- /dev/null +++ b/parser/examples/depth.rs @@ -0,0 +1,18 @@ +use ezno_parser::{ASTNode, Module, ParseOptions}; +use source_map::SourceId; + +fn main() -> Result<(), Box> { + const STACK_SIZE_MB: usize = 64; + let options = + ParseOptions { stack_size: Some(STACK_SIZE_MB * 1024 * 1024), ..ParseOptions::default() }; + + for i in 0..200 { + let mut s = "a ? b : c".to_owned(); + for _ in 0..(i * 5) { + s = format!("a ? b : ({s})"); + } + eprintln!("parsing at {}", i * 5); + let _result = Module::from_string(s, options, SourceId::NULL, None)?; + } + Ok(()) +} diff --git a/parser/examples/parse.rs b/parser/examples/parse.rs index cd0bbe75..973b60ad 100644 --- a/parser/examples/parse.rs +++ b/parser/examples/parse.rs @@ -1,18 +1,33 @@ use std::time::Instant; -use ezno_parser::{ASTNode, FromFileError, Module, ParseOptions, ToStringOptions}; +use ezno_parser::{ASTNode, Comments, FromFileError, Module, ParseOptions, ToStringOptions}; fn main() -> Result<(), Box> { - let path = std::env::args().nth(1).ok_or("expected argument")?; + let mut args: Vec<_> = std::env::args().skip(1).collect(); + let path = args.drain(0..1).next().ok_or("expected argument")?; let now = Instant::now(); let mut fs = source_map::MapFileStore::::default(); - let result = Module::from_file(&path, ParseOptions::default(), &mut fs); + // TODO temp + const STACK_SIZE_MB: usize = 32; + let comments = if args.iter().any(|item| item == "--no-comments") { + Comments::None + } else { + Comments::All + }; + + let options = ParseOptions { + stack_size: Some(STACK_SIZE_MB * 1024 * 1024), + comments, + ..ParseOptions::all_features() + }; + + let result = Module::from_file(&path, options, &mut fs); match result { Ok(module) => { eprintln!("Parsed in: {:?}", now.elapsed()); - if std::env::args().any(|item| item == "--ast") { + if args.iter().any(|item| item == "--ast") { println!("{module:#?}"); - } else { + } else if args.iter().any(|item| item == "--render") { let output = module .to_string(&ToStringOptions { trailing_semicolon: true, ..Default::default() }); println!("{output}"); diff --git a/parser/src/block.rs b/parser/src/block.rs index 978f16ef..98af59d8 100644 --- a/parser/src/block.rs +++ b/parser/src/block.rs @@ -6,14 +6,15 @@ use visitable_derive::Visitable; use super::{ASTNode, Span, TSXToken, TokenReader}; use crate::{ declarations::{export::Exportable, ExportDeclaration}, - expect_semi_colon, Declaration, Decorated, ParseOptions, ParseResult, Statement, VisitSettings, - Visitable, + expect_semi_colon, Declaration, Decorated, ParseOptions, ParseResult, Statement, TSXKeyword, + VisitOptions, Visitable, }; #[derive(Debug, Clone, PartialEq, Visitable, get_field_by_type::GetFieldByType, EnumFrom)] #[get_field_by_type_target(Span)] #[cfg_attr(feature = "self-rust-tokenize", derive(self_rust_tokenize::SelfRustTokenize))] #[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))] +#[visit_self(under statement)] pub enum StatementOrDeclaration { Statement(Statement), Declaration(Declaration), @@ -53,11 +54,21 @@ impl ASTNode for StatementOrDeclaration { state: &mut crate::ParsingState, options: &ParseOptions, ) -> ParseResult { - if Declaration::is_declaration_start(reader) { + if Declaration::is_declaration_start(reader, options) { let dec = Declaration::from_reader(reader, state, options)?; // TODO nested blocks? Interfaces...? Ok(StatementOrDeclaration::Declaration(dec)) } else { + if let Some(Token(TSXToken::Keyword(TSXKeyword::Enum | TSXKeyword::Type), _)) = + reader.peek() + { + if reader.peek_n(1).map_or(false, |t| !t.0.is_symbol()) { + return Ok(StatementOrDeclaration::Declaration(Declaration::from_reader( + reader, state, options, + )?)); + } + } + let stmt = Statement::from_reader(reader, state, options)?; Ok(StatementOrDeclaration::Statement(stmt)) } @@ -167,18 +178,19 @@ impl Visitable for Block { &self, visitors: &mut (impl crate::VisitorReceiver + ?Sized), data: &mut TData, - options: &VisitSettings, - + options: &VisitOptions, chain: &mut temporary_annex::Annex, ) { - { - visitors.visit_block(&crate::block::BlockLike { items: &self.0 }, data, chain); - } - let iter = self.iter(); - if options.reverse_statements { - iter.rev().for_each(|item| item.visit(visitors, data, options, chain)); - } else { - iter.for_each(|item| item.visit(visitors, data, options, chain)); + if options.visit_nested_blocks || chain.is_empty() { + { + visitors.visit_block(&crate::block::BlockLike { items: &self.0 }, data, chain); + } + let iter = self.iter(); + if options.reverse_statements { + iter.rev().for_each(|item| item.visit(visitors, data, options, chain)); + } else { + iter.for_each(|item| item.visit(visitors, data, options, chain)); + } } } @@ -186,30 +198,31 @@ impl Visitable for Block { &mut self, visitors: &mut (impl crate::VisitorMutReceiver + ?Sized), data: &mut TData, - options: &VisitSettings, - + options: &VisitOptions, chain: &mut temporary_annex::Annex, ) { - { - visitors.visit_block_mut( - &mut crate::block::BlockLikeMut { items: &mut self.0 }, - data, - chain, - ); - } - let iter_mut = self.iter_mut(); - if options.reverse_statements { - iter_mut.for_each(|statement| statement.visit_mut(visitors, data, options, chain)); - } else { - iter_mut - .rev() - .for_each(|statement| statement.visit_mut(visitors, data, options, chain)); + if options.visit_nested_blocks || chain.is_empty() { + { + visitors.visit_block_mut( + &mut crate::block::BlockLikeMut { items: &mut self.0 }, + data, + chain, + ); + } + let iter_mut = self.iter_mut(); + if options.reverse_statements { + iter_mut.for_each(|statement| statement.visit_mut(visitors, data, options, chain)); + } else { + iter_mut + .rev() + .for_each(|statement| statement.visit_mut(visitors, data, options, chain)); + } } } } /// For ifs and other statements -#[derive(Debug, Clone, PartialEq, Eq, Visitable, EnumFrom)] +#[derive(Debug, Clone, PartialEq, Eq, EnumFrom)] #[cfg_attr(feature = "self-rust-tokenize", derive(self_rust_tokenize::SelfRustTokenize))] #[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))] pub enum BlockOrSingleStatement { @@ -217,6 +230,52 @@ pub enum BlockOrSingleStatement { SingleStatement(Box), } +impl Visitable for BlockOrSingleStatement { + fn visit( + &self, + visitors: &mut (impl crate::visiting::VisitorReceiver + ?Sized), + data: &mut TData, + options: &VisitOptions, + chain: &mut temporary_annex::Annex, + ) { + match self { + BlockOrSingleStatement::Braced(b) => { + b.visit(visitors, data, options, chain); + } + BlockOrSingleStatement::SingleStatement(s) => { + s.visit(visitors, data, options, chain); + visitors.visit_statement( + crate::visiting::BlockItem::SingleStatement(s), + data, + chain, + ); + } + } + } + + fn visit_mut( + &mut self, + visitors: &mut (impl crate::visiting::VisitorMutReceiver + ?Sized), + data: &mut TData, + options: &VisitOptions, + chain: &mut temporary_annex::Annex, + ) { + match self { + BlockOrSingleStatement::Braced(ref mut b) => { + b.visit_mut(visitors, data, options, chain); + } + BlockOrSingleStatement::SingleStatement(ref mut s) => { + s.visit_mut(visitors, data, options, chain); + visitors.visit_statement_mut( + crate::visiting::BlockItemMut::SingleStatement(s), + data, + chain, + ); + } + } + } +} + impl From for BlockOrSingleStatement { fn from(stmt: Statement) -> Self { Self::SingleStatement(Box::new(stmt)) @@ -259,12 +318,15 @@ impl ASTNode for BlockOrSingleStatement { block.to_string_from_buffer(buf, options, depth); } BlockOrSingleStatement::SingleStatement(stmt) => { - if options.pretty { + if options.pretty && !options.single_statement_on_new_line { buf.push_new_line(); options.add_gap(buf); stmt.to_string_from_buffer(buf, options, depth + 1); } else { stmt.to_string_from_buffer(buf, options, depth); + if stmt.requires_semi_colon() { + buf.push(';'); + } } } } @@ -299,11 +361,13 @@ pub fn statements_and_declarations_to_string( depth: u8, ) { for (at_end, item) in items.iter().endiate() { - if let StatementOrDeclaration::Statement(Statement::Expression( - crate::expressions::MultipleExpression::Single(crate::Expression::Null(..)), - )) = item - { - continue; + if !options.pretty { + if let StatementOrDeclaration::Statement(Statement::Expression( + crate::expressions::MultipleExpression::Single(crate::Expression::Null(..)), + )) = item + { + continue; + } } options.add_indent(depth, buf); diff --git a/parser/src/comments.rs b/parser/src/comments.rs index e9315edb..fc652dab 100644 --- a/parser/src/comments.rs +++ b/parser/src/comments.rs @@ -1,11 +1,12 @@ //! Contains wrappers for AST with comments use super::{ASTNode, ParseError, Span, TSXToken, TokenReader}; -use crate::{ParseOptions, Visitable}; +use crate::ParseOptions; use tokenizer_lib::Token; +use visitable_derive::Visitable; -#[derive(Debug, Clone, Eq)] +#[derive(Debug, Clone, Eq, Visitable)] pub enum WithComment { None(T), PrefixComment(String, T, Span), @@ -22,14 +23,8 @@ where &self, token_stream: &mut self_rust_tokenize::proc_macro2::TokenStream, ) { - match self { - WithComment::None(item) - | WithComment::PrefixComment(_, item, _) - | WithComment::PostfixComment(item, _, _) => { - let inner = self_rust_tokenize::SelfRustTokenize::to_tokens(item); - token_stream.extend(self_rust_tokenize::quote!(WithComment::None(#inner))); - } - } + let inner = self_rust_tokenize::SelfRustTokenize::to_tokens(self.get_ast_ref()); + token_stream.extend(self_rust_tokenize::quote!(WithComment::None(#inner))); } } @@ -44,28 +39,6 @@ impl serde::Serialize for WithComment { } } -impl Visitable for WithComment { - fn visit( - &self, - visitors: &mut (impl crate::VisitorReceiver + ?Sized), - data: &mut TData, - options: &crate::VisitSettings, - chain: &mut temporary_annex::Annex, - ) { - self.get_ast_ref().visit(visitors, data, options, chain); - } - - fn visit_mut( - &mut self, - visitors: &mut (impl crate::VisitorMutReceiver + ?Sized), - data: &mut TData, - options: &crate::VisitSettings, - chain: &mut temporary_annex::Annex, - ) { - self.get_ast_mut().visit_mut(visitors, data, options, chain); - } -} - impl PartialEq for WithComment { fn eq(&self, other: &Self) -> bool { self.get_ast_ref() == other.get_ast_ref() @@ -164,7 +137,7 @@ impl ASTNode for WithComment { match self { Self::None(ast) => ast.to_string_from_buffer(buf, options, depth), Self::PrefixComment(comment, ast, _) => { - if options.should_add_comment() { + if options.should_add_comment(comment.starts_with('*')) { buf.push_str("/*"); buf.push_str_contains_new_line(comment.as_str()); buf.push_str("*/ "); @@ -173,7 +146,7 @@ impl ASTNode for WithComment { } Self::PostfixComment(ast, comment, _) => { ast.to_string_from_buffer(buf, options, depth); - if options.should_add_comment() { + if options.should_add_comment(comment.starts_with('*')) { buf.push_str(" /*"); buf.push_str_contains_new_line(comment.as_str()); buf.push_str("*/"); diff --git a/parser/src/declarations/classes/class_member.rs b/parser/src/declarations/classes/class_member.rs index 67693ae0..dddd891d 100644 --- a/parser/src/declarations/classes/class_member.rs +++ b/parser/src/declarations/classes/class_member.rs @@ -1,17 +1,19 @@ -use std::fmt::Debug; +use std::{fmt::Debug, mem}; -use crate::{errors::parse_lexing_error, property_key::PublicOrPrivate, tsx_keywords}; +use crate::{ + errors::parse_lexing_error, property_key::PublicOrPrivate, tsx_keywords, visiting::Visitable, +}; use source_map::Span; use tokenizer_lib::{Token, TokenReader}; use visitable_derive::Visitable; use crate::{ - functions::FunctionBased, ASTNode, Block, Expression, FunctionBase, Keyword, MethodHeader, - ParseOptions, ParseResult, PropertyKey, TSXKeyword, TSXToken, TypeAnnotation, WithComment, + functions::{FunctionBased, MethodHeader}, + ASTNode, Block, Expression, FunctionBase, Keyword, ParseOptions, ParseResult, PropertyKey, + TSXKeyword, TSXToken, TypeAnnotation, WithComment, }; -/// The variable id's of these is handled by their [`PropertyKey`] -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Visitable)] #[cfg_attr(feature = "self-rust-tokenize", derive(self_rust_tokenize::SelfRustTokenize))] #[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))] pub enum ClassMember { @@ -82,9 +84,36 @@ impl ASTNode for ClassMember { .conditional_next(|tok| *tok == TSXToken::Keyword(TSXKeyword::Readonly)) .map(|token| Keyword::new(token.get_span())); - let header = MethodHeader::from_reader(reader); + let mut header = MethodHeader::from_reader(reader); + + // Catch for named get or set :( + let is_named_get_set_or_async = matches!( + (reader.peek(), &header), + ( + Some(Token( + TSXToken::OpenParentheses + | TSXToken::OpenChevron | TSXToken::Colon + | TSXToken::Comma | TSXToken::CloseBrace, + _ + )), + MethodHeader::Get(..) + | MethodHeader::Set(..) + | MethodHeader::Regular { r#async: Some(_), .. } + ) + ); - let key = WithComment::>::from_reader(reader, state, options)?; + let key = if is_named_get_set_or_async { + // Backtrack allowing `get` to be a key + let (name, position) = match mem::take(&mut header) { + MethodHeader::Get(kw) => ("get", kw.1), + MethodHeader::Set(kw) => ("set", kw.1), + MethodHeader::Regular { r#async: Some(kw), .. } => ("async", kw.1), + MethodHeader::Regular { .. } => unreachable!(), + }; + WithComment::None(PropertyKey::Ident(name.to_owned(), position, false)) + } else { + WithComment::>::from_reader(reader, state, options)? + }; match reader.peek() { Some(Token(TSXToken::OpenParentheses, _)) if readonly_keyword.is_none() => { @@ -116,7 +145,7 @@ impl ASTNode for ClassMember { is_static, ClassProperty { readonly_keyword, - position: key.get_position().clone(), + position: *key.get_position(), key, type_annotation: member_type, value: member_expression.map(Box::new), @@ -168,7 +197,7 @@ impl ASTNode for ClassMember { block.to_string_from_buffer(buf, options, depth + 1); } Self::Comment(c, _) => { - if options.include_comments { + if options.should_add_comment(c.starts_with('.')) { buf.push_str("/*"); buf.push_str(c); buf.push_str("*/"); @@ -226,12 +255,32 @@ impl FunctionBased for ClassFunctionBase { fn header_left(header: &Self::Header) -> Option { header.get_start() } + + fn visit_name( + name: &Self::Name, + visitors: &mut (impl crate::VisitorReceiver + ?Sized), + data: &mut TData, + options: &crate::visiting::VisitOptions, + chain: &mut temporary_annex::Annex, + ) { + name.visit(visitors, data, options, chain); + } + + fn visit_name_mut( + name: &mut Self::Name, + visitors: &mut (impl crate::VisitorMutReceiver + ?Sized), + data: &mut TData, + options: &crate::visiting::VisitOptions, + chain: &mut temporary_annex::Annex, + ) { + name.visit_mut(visitors, data, options, chain); + } } impl FunctionBased for ClassConstructorBase { - type Body = Block; type Header = Keyword; type Name = (); + type Body = Block; // fn get_chain_variable(this: &FunctionBase) -> ChainVariable { // ChainVariable::UnderClassConstructor(this.body.1) @@ -258,4 +307,22 @@ impl FunctionBased for ClassConstructorBase { fn header_left(header: &Self::Header) -> Option { Some(header.get_position().get_start()) } + + fn visit_name( + _: &Self::Name, + _: &mut (impl crate::VisitorReceiver + ?Sized), + _: &mut TData, + _: &crate::visiting::VisitOptions, + _: &mut temporary_annex::Annex, + ) { + } + + fn visit_name_mut( + _: &mut Self::Name, + _: &mut (impl crate::VisitorMutReceiver + ?Sized), + _: &mut TData, + _: &crate::visiting::VisitOptions, + _: &mut temporary_annex::Annex, + ) { + } } diff --git a/parser/src/declarations/classes/mod.rs b/parser/src/declarations/classes/mod.rs index dfa5534c..a9cf2b38 100644 --- a/parser/src/declarations/classes/mod.rs +++ b/parser/src/declarations/classes/mod.rs @@ -9,7 +9,7 @@ use iterator_endiate::EndiateIteratorExt; use crate::{ extensions::decorators::Decorated, visiting::Visitable, ASTNode, ExpressionOrStatementPosition, GenericTypeConstraint, Keyword, ParseOptions, ParseResult, Span, TSXKeyword, TSXToken, - TypeAnnotation, VisitSettings, + TypeAnnotation, VisitOptions, }; use tokenizer_lib::{sized_tokens::TokenReaderWithTokenEnds, Token, TokenReader}; @@ -19,7 +19,7 @@ use tokenizer_lib::{sized_tokens::TokenReaderWithTokenEnds, Token, TokenReader}; #[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))] pub struct ClassDeclaration { pub class_keyword: Keyword, - pub name: T::Name, + pub name: T, pub type_parameters: Option>, pub extends: Option>, pub implements: Option>, @@ -131,7 +131,9 @@ impl ClassDeclaration { depth: u8, ) { buf.push_str("class "); - buf.push_str(U::as_option_str(&self.name).unwrap_or_default()); + if let Some(name) = self.name.as_option_str() { + buf.push_str(name); + } if let Some(type_parameters) = &self.type_parameters { to_string_bracketed(type_parameters, ('<', '>'), buf, options, depth); } @@ -161,19 +163,37 @@ impl ClassDeclaration { impl Visitable for ClassDeclaration { fn visit( &self, - _visitors: &mut (impl crate::VisitorReceiver + ?Sized), - _data: &mut TData, - _options: &VisitSettings, - _chain: &mut temporary_annex::Annex, + visitors: &mut (impl crate::VisitorReceiver + ?Sized), + data: &mut TData, + options: &VisitOptions, + chain: &mut temporary_annex::Annex, ) { + visitors.visit_variable( + &crate::visiting::ImmutableVariableOrProperty::ClassName( + self.name.as_option_variable_identifier(), + ), + data, + chain, + ); + self.extends.visit(visitors, data, options, chain); + self.members.visit(visitors, data, options, chain); } fn visit_mut( &mut self, - _visitors: &mut (impl crate::VisitorMutReceiver + ?Sized), - _data: &mut TData, - _options: &VisitSettings, - _chain: &mut temporary_annex::Annex, + visitors: &mut (impl crate::VisitorMutReceiver + ?Sized), + data: &mut TData, + options: &VisitOptions, + chain: &mut temporary_annex::Annex, ) { + visitors.visit_variable_mut( + &mut crate::visiting::MutableVariableOrProperty::ClassName( + self.name.as_option_variable_identifier_mut(), + ), + data, + chain, + ); + self.extends.visit_mut(visitors, data, options, chain); + self.members.visit_mut(visitors, data, options, chain); } } diff --git a/parser/src/declarations/export.rs b/parser/src/declarations/export.rs index 3ae9610d..4b3e2c06 100644 --- a/parser/src/declarations/export.rs +++ b/parser/src/declarations/export.rs @@ -384,7 +384,7 @@ impl ASTNode for ExportPart { } } ExportPart::PrefixComment(comment, inner, _) => { - if options.include_comments { + if options.should_add_comment(comment.starts_with('.')) { buf.push_str("/*"); buf.push_str(comment); buf.push_str("*/"); @@ -398,7 +398,7 @@ impl ASTNode for ExportPart { } ExportPart::PostfixComment(inner, comment, _) => { inner.to_string_from_buffer(buf, options, depth); - if options.include_comments { + if options.should_add_comment(comment.starts_with('.')) { buf.push_str("/*"); buf.push_str(comment); buf.push_str("*/ "); diff --git a/parser/src/declarations/import.rs b/parser/src/declarations/import.rs index 9daa8be1..89aa5b30 100644 --- a/parser/src/declarations/import.rs +++ b/parser/src/declarations/import.rs @@ -381,7 +381,7 @@ impl ASTNode for ImportPart { buf.push_str(name); } ImportPart::PrefixComment(comment, inner, _) => { - if options.include_comments { + if options.should_add_comment(comment.starts_with('.')) { buf.push_str("/*"); buf.push_str(comment); buf.push_str("*/"); @@ -395,7 +395,7 @@ impl ASTNode for ImportPart { } ImportPart::PostfixComment(inner, comment, _) => { inner.to_string_from_buffer(buf, options, depth); - if options.include_comments { + if options.should_add_comment(comment.starts_with('.')) { buf.push_str("/*"); buf.push_str(comment); buf.push_str("*/ "); diff --git a/parser/src/declarations/mod.rs b/parser/src/declarations/mod.rs index 1be916c6..3b0e9ba2 100644 --- a/parser/src/declarations/mod.rs +++ b/parser/src/declarations/mod.rs @@ -6,8 +6,8 @@ use visitable_derive::Visitable; use crate::{ errors::parse_lexing_error, extensions::decorators, throw_unexpected_token_with_token, - CursorId, Decorated, Keyword, ParseError, ParseErrors, Quoted, StatementPosition, TSXKeyword, - TSXToken, TypeDefinitionModuleDeclaration, + CursorId, Decorated, Keyword, ParseError, ParseErrors, ParseOptions, Quoted, StatementPosition, + TSXKeyword, TSXToken, TypeDefinitionModuleDeclaration, }; pub use self::{ @@ -37,7 +37,6 @@ pub use import::{ImportDeclaration, ImportExportName, ImportPart}; )] #[get_field_by_type_target(Span)] #[try_into_references(&, &mut)] -#[visit_self(under statement)] #[cfg_attr(feature = "self-rust-tokenize", derive(self_rust_tokenize::SelfRustTokenize))] #[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))] pub enum Declaration { @@ -61,6 +60,7 @@ impl Declaration { // TODO strict mode, type etc can affect result pub(crate) fn is_declaration_start( reader: &mut impl tokenizer_lib::TokenReader, + options: &ParseOptions, ) -> bool { let token = reader.peek(); let result = matches!( @@ -69,8 +69,7 @@ impl Declaration { TSXToken::Keyword( TSXKeyword::Let | TSXKeyword::Const | TSXKeyword::Function - | TSXKeyword::Class | TSXKeyword::Enum - | TSXKeyword::Type | TSXKeyword::Declare + | TSXKeyword::Class | TSXKeyword::Declare | TSXKeyword::Import | TSXKeyword::Export | TSXKeyword::Interface | TSXKeyword::Async ) | TSXToken::At, @@ -80,7 +79,7 @@ impl Declaration { #[cfg(feature = "extras")] return result - || matches!(token, Some(Token(TSXToken::Keyword(kw), _)) if kw.is_in_function_header()) + || matches!(token, Some(Token(TSXToken::Keyword(kw), _)) if (options.custom_function_headers && kw.is_special_function_header())) || (matches!(token, Some(Token(TSXToken::Keyword(TSXKeyword::From), _))) && matches!(reader.peek_n(1), Some(Token(TSXToken::StringLiteral(..), _)))); @@ -168,7 +167,7 @@ impl crate::ASTNode for Declaration { .map(|on| Declaration::Enum(Decorated::new(decorators, on))) } #[cfg(feature = "extras")] - TSXToken::Keyword(ref kw) if kw.is_in_function_header() => { + TSXToken::Keyword(ref kw) if kw.is_special_function_header() => { let function = StatementFunction::from_reader(reader, state, options)?; Ok(Declaration::Function(Decorated::new(decorators, function))) } @@ -216,19 +215,19 @@ impl crate::ASTNode for Declaration { } TypeDefinitionModuleDeclaration::Class(item) => Err(ParseError::new( ParseErrors::InvalidDeclareItem("class"), - item.get_position().clone(), + *item.get_position(), )), TypeDefinitionModuleDeclaration::Interface(item) => Err(ParseError::new( ParseErrors::InvalidDeclareItem("interface"), - item.get_position().clone(), + *item.get_position(), )), TypeDefinitionModuleDeclaration::TypeAlias(item) => Err(ParseError::new( ParseErrors::InvalidDeclareItem("type alias"), - item.get_position().clone(), + *item.get_position(), )), TypeDefinitionModuleDeclaration::Namespace(item) => Err(ParseError::new( ParseErrors::InvalidDeclareItem("namespace"), - item.get_position().clone(), + *item.get_position(), )), TypeDefinitionModuleDeclaration::LocalTypeAlias(_) | TypeDefinitionModuleDeclaration::LocalVariableDeclaration(_) diff --git a/parser/src/errors.rs b/parser/src/errors.rs index f3d86515..17e6ffd5 100644 --- a/parser/src/errors.rs +++ b/parser/src/errors.rs @@ -189,4 +189,11 @@ impl ParseError { } } +impl std::error::Error for ParseError {} +impl std::fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_fmt(format_args!("ParseError: {} @ byte indices {:?}", self.reason, self.position)) + } +} + pub type ParseResult = Result; diff --git a/parser/src/expressions/arrow_function.rs b/parser/src/expressions/arrow_function.rs index 53ce5178..5b3feb0a 100644 --- a/parser/src/expressions/arrow_function.rs +++ b/parser/src/expressions/arrow_function.rs @@ -59,12 +59,10 @@ impl FunctionBased for ArrowFunctionBase { token => { let (name, position) = token_as_identifier(token, "arrow function parameter")?; let parameters = vec![Parameter { - name: WithComment::None( - VariableIdentifier::Standard(name, position.clone()).into(), - ), + name: WithComment::None(VariableIdentifier::Standard(name, position).into()), type_annotation: None, additionally: None, - position: position.clone(), + position, }]; Ok(FunctionParameters { parameters, @@ -107,6 +105,24 @@ impl FunctionBased for ArrowFunctionBase { fn header_left(header: &Self::Header) -> Option { header.as_ref().map(|kw| kw.get_position().get_start()) } + + fn visit_name( + _: &Self::Name, + _: &mut (impl crate::VisitorReceiver + ?Sized), + _: &mut TData, + _: &crate::visiting::VisitOptions, + _: &mut temporary_annex::Annex, + ) { + } + + fn visit_name_mut( + _: &mut Self::Name, + _: &mut (impl crate::VisitorMutReceiver + ?Sized), + _: &mut TData, + _: &crate::visiting::VisitOptions, + _: &mut temporary_annex::Annex, + ) { + } } impl ArrowFunction { @@ -115,19 +131,20 @@ impl ArrowFunction { state: &mut crate::ParsingState, options: &ParseOptions, first_parameter: (String, Span), + is_async: Option>, ) -> ParseResult { let parameters = vec![crate::Parameter { name: WithComment::None( - VariableIdentifier::Standard(first_parameter.0, first_parameter.1.clone()).into(), + VariableIdentifier::Standard(first_parameter.0, first_parameter.1).into(), ), type_annotation: None, additionally: None, - position: first_parameter.1.clone(), + position: first_parameter.1, }]; reader.expect_next(TSXToken::Arrow)?; let body = ExpressionOrBlock::from_reader(reader, state, options)?; let arrow_function = FunctionBase { - header: None, + header: is_async, position: first_parameter.1.union(body.get_position()), name: (), parameters: crate::FunctionParameters { diff --git a/parser/src/expressions/assignments.rs b/parser/src/expressions/assignments.rs index 3482b5ce..0f22d2a4 100644 --- a/parser/src/expressions/assignments.rs +++ b/parser/src/expressions/assignments.rs @@ -114,13 +114,13 @@ impl TryFrom for VariableOrPropertyAccess { } else { Err(ParseError::new( crate::ParseErrors::InvalidLHSAssignment, - inner.get_position().clone(), + *inner.get_position(), )) } } expression => Err(ParseError::new( crate::ParseErrors::InvalidLHSAssignment, - expression.get_position().clone(), + *expression.get_position(), )), } } diff --git a/parser/src/expressions/mod.rs b/parser/src/expressions/mod.rs index c5ca1dd7..8e4d3a68 100644 --- a/parser/src/expressions/mod.rs +++ b/parser/src/expressions/mod.rs @@ -18,15 +18,10 @@ use self::{ object_literal::ObjectLiteral, }; +#[allow(clippy::wildcard_imports)] use super::{ - operators::{ - BinaryOperator, Operator, UnaryOperator, COMMA_PRECEDENCE, CONDITIONAL_TERNARY_PRECEDENCE, - CONSTRUCTOR_PRECEDENCE, CONSTRUCTOR_WITHOUT_PARENTHESIS_PRECEDENCE, INDEX_PRECEDENCE, - MEMBER_ACCESS_PRECEDENCE, PARENTHESIZED_EXPRESSION_AND_LITERAL_PRECEDENCE, - }, - tokens::token_as_identifier, - ASTNode, Block, FunctionBase, JSXRoot, ParseError, ParseOptions, Span, TSXToken, Token, - TokenReader, TypeAnnotation, + operators::*, tokens::token_as_identifier, ASTNode, Block, FunctionBase, JSXRoot, ParseError, + ParseOptions, Span, TSXToken, Token, TokenReader, TypeAnnotation, }; #[cfg(feature = "extras")] @@ -35,7 +30,7 @@ use crate::extensions::is_expression::{is_expression_from_reader_sub_is_keyword, use crate::tsx_keywords::{self, As, Satisfies}; use derive_partial_eq_extras::PartialEqExtras; use get_field_by_type::GetFieldByType; -use tokenizer_lib::sized_tokens::{SizedToken, TokenEnd, TokenReaderWithTokenEnds, TokenStart}; +use tokenizer_lib::sized_tokens::{TokenEnd, TokenReaderWithTokenEnds, TokenStart}; use visitable_derive::Visitable; pub mod arrow_function; @@ -149,7 +144,7 @@ pub enum Expression { position: Span, }, /// e.g `... ? ... ? ...` - ConditionalTernaryExpression { + ConditionalTernary { condition: Box, truthy_result: Box, falsy_result: Box, @@ -336,6 +331,23 @@ impl Expression { )?; Expression::ClassExpression(class_declaration) } + Token(TSXToken::Keyword(TSXKeyword::Yield), s) => { + // TODO could we do better? + let delegated = + reader.conditional_next(|t| matches!(t, TSXToken::Multiply)).is_some(); + + let operator = + if delegated { UnaryOperator::DelegatedYield } else { UnaryOperator::Yield }; + + let operand = Expression::from_reader_with_precedence( + reader, + state, + options, + operator.precedence(), + )?; + let position = s.union(operand.get_position()); + Expression::UnaryOperation { operator, operand: Box::new(operand), position } + } t @ Token(TSXToken::OpenBracket, start) => { let mut bracket_depth = 1; let after_bracket = reader.scan(|token, _| match token { @@ -367,8 +379,32 @@ impl Expression { rhs: Box::new(rhs), } } else { - let (items, end) = - parse_bracketed(reader, state, options, None, TSXToken::CloseBracket)?; + let mut items: Vec<_> = Vec::new(); + let end; + // No trailing comments + loop { + if let Some(token) = + reader.conditional_next(|token| *token == TSXToken::CloseBracket) + { + end = token.get_end(); + break; + } + + items.push(SpreadExpression::from_reader(reader, state, options)?); + + if !reader + .peek() + .map_or(false, |Token(t, _)| matches!(t, TSXToken::CloseBracket)) + { + reader.expect_next(TSXToken::Comma)?; + // TODO not great fix + if reader.peek().map_or(false, |Token(t, _)| { + matches!(t, TSXToken::CloseBracket) + }) { + items.push(SpreadExpression::Empty); + } + } + } Expression::ArrayLiteral(items, start.union(end)) } } else { @@ -396,17 +432,17 @@ impl Expression { let (members, end) = parse_bracketed(reader, state, options, None, TSXToken::CloseBrace)?; reader.next(); - let rhs = Expression::from_reader_with_precedence( + let rhs = Box::new(Expression::from_reader_with_precedence( reader, state, options, ASSIGNMENT_PRECEDENCE, - )?; + )?); let object_position = start.union(end); Expression::Assignment { position: object_position.union(rhs.get_position()), lhs: LHSOfAssignment::ObjectDestructuring(members, object_position), - rhs: Box::new(rhs), + rhs, } } else { let object_literal = ObjectLiteral::from_reader_sub_open_curly( @@ -460,7 +496,7 @@ impl Expression { if let Some(Token(TSXToken::Dot, _)) = reader.peek() { // TODO assert not lonely, else syntax error reader.expect_next(TSXToken::Dot)?; - reader.expect_next(TSXToken::IdentLiteral("target".into()))?; + reader.expect_next(TSXToken::Identifier("target".into()))?; Expression::NewTarget(t.get_span()) } else { // Pass as a function call and then adds the conversion @@ -532,7 +568,13 @@ impl Expression { )?; Expression::TemplateLiteral(template_literal) } - Token(TSXToken::Keyword(kw), start) if kw.is_in_function_header() => { + Token(TSXToken::Keyword(kw), start) + if (kw.is_special_function_header() + && reader.peek().map_or( + false, + |Token(t, _)| matches!(t, TSXToken::Keyword(kw) if kw.is_in_function_header()), + )) || kw.is_in_function_header() => + { let token = Token(TSXToken::Keyword(kw), start); let (async_keyword, token) = if let Token(TSXToken::Keyword(TSXKeyword::Async), _) = token { @@ -561,6 +603,7 @@ impl Expression { state, options, (name, position), + async_keyword, )?; return Ok(Expression::ArrowFunction(function)); } @@ -580,8 +623,8 @@ impl Expression { Some(FunctionLocationModifier::Server(Keyword::new(t.get_span()))), reader.next().unwrap(), ), - t @ Token(TSXToken::Keyword(TSXKeyword::Module), _) => ( - Some(FunctionLocationModifier::Module(Keyword::new(t.get_span()))), + t @ Token(TSXToken::Keyword(TSXKeyword::Worker), _) => ( + Some(FunctionLocationModifier::Worker(Keyword::new(t.get_span()))), reader.next().unwrap(), ), t => (None, t), @@ -596,7 +639,9 @@ impl Expression { }; let function_keyword = - Keyword::new(function_start.with_length(f.length() as usize)); + Keyword::new(function_start.with_length( + tokenizer_lib::sized_tokens::SizedToken::length(&f) as usize, + )); if let Some(generator_keyword) = generator_keyword { let position = async_keyword @@ -621,7 +666,11 @@ impl Expression { }; let function: ExpressionFunction = FunctionBase::from_reader_with_header_and_name( - reader, state, options, header, name, + reader, + state, + options, + header, + ExpressionPosition(name), )?; Expression::ExpressionFunction(function) @@ -655,7 +704,11 @@ impl Expression { }; let function: ExpressionFunction = FunctionBase::from_reader_with_header_and_name( - reader, state, options, header, name, + reader, + state, + options, + header, + ExpressionPosition(name), )?; Expression::ExpressionFunction(function) @@ -701,7 +754,11 @@ impl Expression { }; let function: ExpressionFunction = FunctionBase::from_reader_with_header_and_name( - reader, state, options, header, name, + reader, + state, + options, + header, + ExpressionPosition(name), )?; Expression::ExpressionFunction(function) @@ -796,6 +853,7 @@ impl Expression { state, options, (name, position), + None, )?; Expression::ArrowFunction(function) } else { @@ -835,13 +893,16 @@ impl Expression { return Ok(top); } reader.next(); - let lhs = Self::from_reader(reader, state, options)?; + let condition_position = *top.get_position(); + let condition = Box::new(top); + let lhs = Box::new(Self::from_reader(reader, state, options)?); reader.expect_next(TSXToken::Colon)?; let rhs = Self::from_reader(reader, state, options)?; - top = Expression::ConditionalTernaryExpression { - position: top.get_position().union(rhs.get_position()), - condition: Box::new(top), - truthy_result: Box::new(lhs), + let position = condition_position.union(rhs.get_position()); + top = Expression::ConditionalTernary { + position, + condition, + truthy_result: lhs, falsy_result: Box::new(rhs), }; } @@ -920,7 +981,7 @@ impl Expression { token_as_identifier(token, "variable reference")?; (PropertyReference::Standard { property, is_private }, position) }; - let position = top.get_position().union(&position); + let position = top.get_position().union(position); top = Expression::PropertyAccess { parent: Box::new(top), property, @@ -1179,7 +1240,7 @@ impl Expression { CONSTRUCTOR_WITHOUT_PARENTHESIS_PRECEDENCE } Self::Index { .. } => INDEX_PRECEDENCE, - Self::ConditionalTernaryExpression { .. } => CONDITIONAL_TERNARY_PRECEDENCE, + Self::ConditionalTernary { .. } => CONDITIONAL_TERNARY_PRECEDENCE, Self::PrefixComment(_, expression, _) | Self::PostfixComment(expression, _, _) => { expression.get_precedence() } @@ -1379,7 +1440,7 @@ impl Expression { } if let Some(arguments) = arguments { // Constructor calls can drop arguments if none - if !arguments.is_empty() { + if !arguments.is_empty() || options.pretty { arguments_to_string(arguments, buf, options, depth); } } @@ -1399,7 +1460,7 @@ impl Expression { } Self::ClassExpression(class) => class.to_string_from_buffer(buf, options, depth), Self::PrefixComment(comment, expression, _) => { - if options.should_add_comment() { + if options.should_add_comment(comment.starts_with('*')) { buf.push_str("/*"); buf.push_str_contains_new_line(comment.as_str()); buf.push_str("*/ "); @@ -1408,14 +1469,14 @@ impl Expression { } Self::PostfixComment(expression, comment, _) => { expression.to_string_from_buffer(buf, options, depth); - if options.should_add_comment() { + if options.should_add_comment(comment.starts_with('*')) { buf.push_str(" /*"); buf.push_str_contains_new_line(comment.as_str()); buf.push_str("*/"); } } Self::Comment(comment, _) => { - if options.should_add_comment() { + if options.should_add_comment(comment.starts_with('*')) { buf.push_str("/*"); buf.push_str_contains_new_line(comment.as_str()); buf.push_str("*/"); @@ -1424,9 +1485,7 @@ impl Expression { Self::TemplateLiteral(template_literal) => { template_literal.to_string_from_buffer(buf, options, depth); } - Self::ConditionalTernaryExpression { - condition, truthy_result, falsy_result, .. - } => { + Self::ConditionalTernary { condition, truthy_result, falsy_result, .. } => { condition.to_string_using_precedence( buf, options, @@ -1691,7 +1750,33 @@ impl ASTNode for SpreadExpression { let position = start_pos.union(expression.get_position()); Ok(Self::Spread(expression, position)) } - TSXToken::Comma => Ok(Self::Empty), + TSXToken::Comma | TSXToken::CloseParentheses | TSXToken::CloseBrace => Ok(Self::Empty), + t if t.is_comment() => { + let Ok((comment, span)) = TSXToken::try_into_comment(reader.next().unwrap()) else { + unreachable!() + }; + let e = Self::from_reader(reader, state, options)?; + Ok(match e { + SpreadExpression::Spread(e, end) => { + let pos = span.union(end); + SpreadExpression::Spread( + Expression::PrefixComment(comment, Box::new(e), pos), + pos, + ) + } + SpreadExpression::NonSpread(e) => { + let pos = span.union(e.get_position().get_end()); + SpreadExpression::NonSpread(Expression::PrefixComment( + comment, + Box::new(e), + pos, + )) + } + SpreadExpression::Empty => { + SpreadExpression::NonSpread(Expression::Comment(comment, span)) + } + }) + } _ => Ok(Self::NonSpread(Expression::from_reader(reader, state, options)?)), } } @@ -1746,7 +1831,7 @@ impl Expression { /// IIFE = immediate invoked function execution #[must_use] pub fn build_iife(block: Block) -> Self { - let position = block.get_position().clone(); + let position = *block.get_position(); Expression::FunctionCall { function: Expression::ParenthesizedExpression( Box::new( @@ -1757,18 +1842,18 @@ impl Expression { parameters: crate::FunctionParameters { parameters: Default::default(), rest_parameter: Default::default(), - position: position.clone(), + position, this_type: None, super_type: None, }, return_type: None, type_parameters: None, - position: position.clone(), + position, body: ExpressionOrBlock::Block(block), }) .into(), ), - position.clone(), + position, ) .into(), type_arguments: None, diff --git a/parser/src/expressions/object_literal.rs b/parser/src/expressions/object_literal.rs index aa1b798b..47b58d9f 100644 --- a/parser/src/expressions/object_literal.rs +++ b/parser/src/expressions/object_literal.rs @@ -5,9 +5,13 @@ use tokenizer_lib::sized_tokens::{TokenReaderWithTokenEnds, TokenStart}; use visitable_derive::Visitable; use crate::{ - errors::parse_lexing_error, functions::FunctionBased, property_key::AlwaysPublic, - throw_unexpected_token_with_token, ASTNode, Block, Expression, FunctionBase, MethodHeader, - ParseOptions, ParseResult, PropertyKey, Span, TSXToken, Token, TokenReader, WithComment, + errors::parse_lexing_error, + functions::{FunctionBased, MethodHeader}, + property_key::AlwaysPublic, + throw_unexpected_token_with_token, + visiting::Visitable, + ASTNode, Block, Expression, FunctionBase, ParseOptions, ParseResult, PropertyKey, Span, + TSXToken, Token, TokenReader, WithComment, }; #[derive(Debug, Clone, Eq, PartialEq, Visitable, get_field_by_type::GetFieldByType)] @@ -15,7 +19,7 @@ use crate::{ #[cfg_attr(feature = "self-rust-tokenize", derive(self_rust_tokenize::SelfRustTokenize))] #[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))] pub struct ObjectLiteral { - pub members: Vec, + pub members: Vec>, pub position: Span, } @@ -26,7 +30,7 @@ pub struct ObjectLiteral { pub enum ObjectLiteralMember { Spread(Expression, Span), Shorthand(String, Span), - Property(WithComment>, Expression, Span), + Property(PropertyKey, Expression, Span), Method(ObjectLiteralMethod), } @@ -35,7 +39,7 @@ impl crate::Visitable for ObjectLiteralMember { &self, visitors: &mut (impl crate::VisitorReceiver + ?Sized), data: &mut TData, - options: &crate::VisitSettings, + options: &crate::VisitOptions, chain: &mut temporary_annex::Annex, ) { match self { @@ -50,7 +54,7 @@ impl crate::Visitable for ObjectLiteralMember { &mut self, visitors: &mut (impl crate::VisitorMutReceiver + ?Sized), data: &mut TData, - options: &crate::VisitSettings, + options: &crate::VisitOptions, chain: &mut temporary_annex::Annex, ) { match self { @@ -67,7 +71,7 @@ pub struct ObjectLiteralMethodBase; pub type ObjectLiteralMethod = FunctionBase; impl FunctionBased for ObjectLiteralMethodBase { - type Name = WithComment>; + type Name = PropertyKey; type Header = MethodHeader; type Body = Block; @@ -76,10 +80,7 @@ impl FunctionBased for ObjectLiteralMethodBase { state: &mut crate::ParsingState, options: &ParseOptions, ) -> ParseResult<(Self::Header, Self::Name)> { - Ok(( - MethodHeader::from_reader(reader), - WithComment::>::from_reader(reader, state, options)?, - )) + Ok((MethodHeader::from_reader(reader), PropertyKey::from_reader(reader, state, options)?)) } fn header_and_name_to_string_from_buffer( @@ -96,6 +97,26 @@ impl FunctionBased for ObjectLiteralMethodBase { fn header_left(header: &Self::Header) -> Option { header.get_start() } + + fn visit_name( + name: &Self::Name, + visitors: &mut (impl crate::VisitorReceiver + ?Sized), + data: &mut TData, + options: &crate::visiting::VisitOptions, + chain: &mut temporary_annex::Annex, + ) { + name.visit(visitors, data, options, chain); + } + + fn visit_name_mut( + name: &mut Self::Name, + visitors: &mut (impl crate::VisitorMutReceiver + ?Sized), + data: &mut TData, + options: &crate::visiting::VisitOptions, + chain: &mut temporary_annex::Annex, + ) { + name.visit_mut(visitors, data, options, chain); + } } impl Eq for ObjectLiteralMember {} @@ -141,12 +162,12 @@ impl ObjectLiteral { options: &ParseOptions, start: TokenStart, ) -> ParseResult { - let mut members: Vec = Vec::new(); + let mut members: Vec> = Vec::new(); loop { if matches!(reader.peek(), Some(Token(TSXToken::CloseBrace, _))) { break; } - members.push(ObjectLiteralMember::from_reader(reader, state, options)?); + members.push(WithComment::from_reader(reader, state, options)?); if let Some(Token(TSXToken::Comma, _)) = reader.peek() { reader.next(); } else { @@ -175,27 +196,38 @@ impl ASTNode for ObjectLiteralMember { }; // TODO this probably needs with comment here: + while reader.conditional_next(TSXToken::is_comment).is_some() {} + let mut header = MethodHeader::from_reader(reader); // Catch for named get or set :( - let is_named_get_or_set = matches!( + let is_named_get_set_or_async = matches!( (reader.peek(), &header), ( - Some(Token(TSXToken::OpenParentheses | TSXToken::Colon, _)), - MethodHeader::Get(..) | MethodHeader::Set(..) + Some(Token( + TSXToken::OpenParentheses + | TSXToken::OpenChevron | TSXToken::Colon + | TSXToken::Comma | TSXToken::CloseBrace, + _ + )), + MethodHeader::Get(..) + | MethodHeader::Set(..) + | MethodHeader::Regular { r#async: Some(_), .. } ) ); - let key = if is_named_get_or_set { + let key = if is_named_get_set_or_async { // Backtrack allowing `get` to be a key let (name, position) = match mem::take(&mut header) { MethodHeader::Get(kw) => ("get", kw.1), MethodHeader::Set(kw) => ("set", kw.1), + MethodHeader::Regular { r#async: Some(kw), .. } => ("async", kw.1), MethodHeader::Regular { .. } => unreachable!(), }; - WithComment::None(PropertyKey::Ident(name.to_owned(), position, ())) + PropertyKey::Ident(name.to_owned(), position, ()) } else { - WithComment::>::from_reader(reader, state, options)? + PropertyKey::from_reader(reader, state, options)? }; + let Token(token, _) = &reader.peek().ok_or_else(parse_lexing_error)?; match token { // Functions, (OpenChevron is for generic parameters) @@ -211,7 +243,7 @@ impl ASTNode for ObjectLiteralMember { return crate::throw_unexpected_token(reader, &[TSXToken::OpenParentheses]); } if let Some(Token(TSXToken::Comma | TSXToken::CloseBrace, _)) = reader.peek() { - if let PropertyKey::Ident(name, position, ()) = key.get_ast() { + if let PropertyKey::Ident(name, position, ()) = key { Ok(Self::Shorthand(name, position)) } else { let token = reader.next().ok_or_else(parse_lexing_error)?; diff --git a/parser/src/expressions/template_literal.rs b/parser/src/expressions/template_literal.rs index 764e605d..1d546221 100644 --- a/parser/src/expressions/template_literal.rs +++ b/parser/src/expressions/template_literal.rs @@ -28,7 +28,7 @@ impl crate::Visitable for TemplateLiteralPart &self, visitors: &mut (impl crate::VisitorReceiver + ?Sized), data: &mut TData, - options: &crate::VisitSettings, + options: &crate::VisitOptions, chain: &mut temporary_annex::Annex, ) { if let Self::Dynamic(dynamic) = self { @@ -40,7 +40,7 @@ impl crate::Visitable for TemplateLiteralPart &mut self, visitors: &mut (impl crate::VisitorMutReceiver + ?Sized), data: &mut TData, - options: &crate::VisitSettings, + options: &crate::VisitOptions, chain: &mut temporary_annex::Annex, ) { if let Self::Dynamic(dynamic) = self { diff --git a/parser/src/extensions/decorators.rs b/parser/src/extensions/decorators.rs index 1e3de429..020dcf2c 100644 --- a/parser/src/extensions/decorators.rs +++ b/parser/src/extensions/decorators.rs @@ -191,7 +191,7 @@ impl Visitable for Decorated { &self, visitors: &mut (impl crate::VisitorReceiver + ?Sized), data: &mut TData, - options: &crate::VisitSettings, + options: &crate::VisitOptions, chain: &mut temporary_annex::Annex, ) { @@ -202,7 +202,7 @@ impl Visitable for Decorated { &mut self, visitors: &mut (impl crate::VisitorMutReceiver + ?Sized), data: &mut TData, - options: &crate::VisitSettings, + options: &crate::VisitOptions, chain: &mut temporary_annex::Annex, ) { diff --git a/parser/src/extensions/jsx.rs b/parser/src/extensions/jsx.rs index 6e5bdd35..f1df593c 100644 --- a/parser/src/extensions/jsx.rs +++ b/parser/src/extensions/jsx.rs @@ -18,7 +18,6 @@ pub enum JSXRoot { #[get_field_by_type_target(Span)] #[cfg_attr(feature = "self-rust-tokenize", derive(self_rust_tokenize::SelfRustTokenize))] #[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))] -#[visit_self] pub struct JSXElement { /// Name of the element (TODO or reference to element) pub tag_name: String, @@ -273,7 +272,8 @@ impl ASTNode for JSXNode { JSXNode::Element(element) => element.to_string_from_buffer(buf, options, depth + 1), JSXNode::TextNode(text, _) => buf.push_str(text), JSXNode::InterpolatedExpression(expression, _) => { - if !options.should_add_comment() && matches!(&**expression, Expression::Comment(..)) + if !options.should_add_comment(false) + && matches!(&**expression, Expression::Comment(..)) { return; } diff --git a/parser/src/functions.rs b/parser/src/functions.rs index b779906a..be34de9e 100644 --- a/parser/src/functions.rs +++ b/parser/src/functions.rs @@ -1,14 +1,15 @@ use std::{fmt::Debug, marker::PhantomData}; -use crate::tsx_keywords; +use crate::visiting::{ImmutableVariableOrProperty, MutableVariableOrProperty}; use crate::{ parse_bracketed, to_string_bracketed, ASTNode, Block, ExpressionOrStatementPosition, ExpressionPosition, GenericTypeConstraint, Keyword, ParseOptions, ParseResult, TSXToken, - TypeAnnotation, VisitSettings, Visitable, + TypeAnnotation, VisitOptions, Visitable, }; +use crate::{tsx_keywords, TSXKeyword}; use derive_partial_eq_extras::PartialEqExtras; use source_map::{Span, ToString}; -use tokenizer_lib::TokenReader; +use tokenizer_lib::{Token, TokenReader}; pub use crate::parameters::*; @@ -82,6 +83,22 @@ pub trait FunctionBased: Debug + Clone + PartialEq + Eq + Send + Sync { ) { options.add_gap(buf); } + + fn visit_name( + name: &Self::Name, + visitors: &mut (impl crate::VisitorReceiver + ?Sized), + data: &mut TData, + options: &VisitOptions, + chain: &mut temporary_annex::Annex, + ); + + fn visit_name_mut( + name: &mut Self::Name, + visitors: &mut (impl crate::VisitorMutReceiver + ?Sized), + data: &mut TData, + options: &VisitOptions, + chain: &mut temporary_annex::Annex, + ); } /// Base for all function based structures with bodies (no interface, type reference etc) @@ -180,17 +197,20 @@ impl FunctionBase { impl Visitable for FunctionBase where T::Body: Visitable, + // T::Header: Visitable, { fn visit( &self, visitors: &mut (impl crate::VisitorReceiver + ?Sized), data: &mut TData, - options: &VisitSettings, - + options: &VisitOptions, chain: &mut temporary_annex::Annex, ) { - self.parameters.visit(visitors, data, options, chain); - if options.visit_function_bodies { + // Don't think there is anything useful about visiting header + // self.header.visit(visitors, data, options, chain); + T::visit_name(&self.name, visitors, data, options, chain); + if options.visit_nested_blocks || chain.is_empty() { + self.parameters.visit(visitors, data, options, chain); self.body.visit(visitors, data, options, chain); } } @@ -199,12 +219,14 @@ where &mut self, visitors: &mut (impl crate::VisitorMutReceiver + ?Sized), data: &mut TData, - options: &VisitSettings, - + options: &VisitOptions, chain: &mut temporary_annex::Annex, ) { - self.parameters.visit_mut(visitors, data, options, chain); - if options.visit_function_bodies { + // Don't think there is anything useful about visiting header + // self.header.visit_mut(visitors, data, options, chain); + T::visit_name_mut(&mut self.name, visitors, data, options, chain); + if options.visit_nested_blocks || chain.is_empty() { + self.parameters.visit_mut(visitors, data, options, chain); self.body.visit_mut(visitors, data, options, chain); } } @@ -220,7 +242,7 @@ pub type ExpressionFunction = FunctionBase FunctionBased for GeneralFunctionBase { type Body = Block; type Header = FunctionHeader; - type Name = T::Name; + type Name = T; fn header_and_name_from_reader( reader: &mut impl TokenReader, @@ -240,7 +262,7 @@ impl FunctionBased for GeneralFunctionBase depth: u8, ) { header.to_string_from_buffer(buf, options, depth); - if let Some(name) = T::as_option_str(name) { + if let Some(name) = name.as_option_str() { buf.push_str(name); } } @@ -248,6 +270,34 @@ impl FunctionBased for GeneralFunctionBase fn header_left(header: &Self::Header) -> Option { Some(header.get_position().get_start()) } + + fn visit_name( + name: &Self::Name, + visitors: &mut (impl crate::VisitorReceiver + ?Sized), + data: &mut TData, + _options: &VisitOptions, + chain: &mut temporary_annex::Annex, + ) { + visitors.visit_variable( + &ImmutableVariableOrProperty::FunctionName(name.as_option_variable_identifier()), + data, + chain, + ); + } + + fn visit_name_mut( + name: &mut Self::Name, + visitors: &mut (impl crate::VisitorMutReceiver + ?Sized), + data: &mut TData, + _options: &VisitOptions, + chain: &mut temporary_annex::Annex, + ) { + visitors.visit_variable_mut( + &mut MutableVariableOrProperty::FunctionName(name.as_option_variable_identifier_mut()), + data, + chain, + ); + } } #[cfg(feature = "extras")] @@ -256,7 +306,7 @@ impl FunctionBased for GeneralFunctionBase #[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))] pub enum FunctionLocationModifier { Server(Keyword), - Module(Keyword), + Worker(Keyword), } #[derive(Debug, PartialEq, Eq, Clone)] @@ -329,7 +379,7 @@ pub(crate) fn function_header_from_reader_with_async_keyword( if let Some(token) = next_generator { let span = token.get_span(); - let generator_keyword = Some(Keyword::new(span.clone())); + let generator_keyword = Some(Keyword::new(span)); let location = parse_function_location(reader); let function_keyword = Keyword::from_reader(reader)?; @@ -358,17 +408,15 @@ pub(crate) fn function_header_from_reader_with_async_keyword( pub(crate) fn parse_function_location( reader: &mut impl TokenReader, ) -> Option { - use crate::{TSXKeyword, Token}; - - if let Some(Token(TSXToken::Keyword(TSXKeyword::Server | TSXKeyword::Module), _)) = + if let Some(Token(TSXToken::Keyword(TSXKeyword::Server | TSXKeyword::Worker), _)) = reader.peek() { Some(match reader.next().unwrap() { t @ Token(TSXToken::Keyword(TSXKeyword::Server), _) => { FunctionLocationModifier::Server(Keyword::new(t.get_span())) } - t @ Token(TSXToken::Keyword(TSXKeyword::Module), _) => { - FunctionLocationModifier::Module(Keyword::new(t.get_span())) + t @ Token(TSXToken::Keyword(TSXKeyword::Worker), _) => { + FunctionLocationModifier::Worker(Keyword::new(t.get_span())) } _ => unreachable!(), }) @@ -389,10 +437,8 @@ fn parse_regular_header( .conditional_next(|tok| matches!(tok, TSXToken::Multiply)) .map(|token| token.get_span()); - let mut position = async_keyword - .as_ref() - .map_or(function_keyword.get_position(), |kw| kw.get_position()) - .clone(); + let mut position = + *async_keyword.as_ref().map_or(function_keyword.get_position(), |kw| kw.get_position()); if let Some(ref generator_star_token_position) = generator_star_token_position { position = position.union(generator_star_token_position); @@ -440,3 +486,117 @@ impl FunctionHeader { } } } + +/// This structure removes possible invalid combinations with async +#[derive(Eq, PartialEq, Clone, Debug)] +#[cfg_attr(feature = "self-rust-tokenize", derive(self_rust_tokenize::SelfRustTokenize))] +#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))] +pub enum MethodHeader { + Get(Keyword), + Set(Keyword), + Regular { r#async: Option>, generator: Option }, +} + +impl Default for MethodHeader { + fn default() -> Self { + Self::Regular { r#async: None, generator: None } + } +} + +impl MethodHeader { + pub(crate) fn to_string_from_buffer(&self, buf: &mut T) { + match self { + MethodHeader::Get(_) => buf.push_str("get "), + MethodHeader::Set(_) => buf.push_str("set "), + MethodHeader::Regular { r#async, generator } => { + if r#async.is_some() { + buf.push_str("async "); + } + if let Some(_generator) = generator { + buf.push('*'); + } + } + } + } + + pub(crate) fn from_reader(reader: &mut impl TokenReader) -> Self { + match reader.peek() { + Some(Token(TSXToken::Keyword(TSXKeyword::Get), _)) => { + MethodHeader::Get(Keyword::new(reader.next().unwrap().get_span())) + } + Some(Token(TSXToken::Keyword(TSXKeyword::Set), _)) => { + MethodHeader::Set(Keyword::new(reader.next().unwrap().get_span())) + } + _ => { + let r#async = reader + .conditional_next(|tok| matches!(tok, TSXToken::Keyword(TSXKeyword::Async))) + .map(|tok| Keyword::new(tok.get_span())); + + let generator = GeneratorSpecifier::from_reader(reader); + + MethodHeader::Regular { r#async, generator } + } + } + } + + pub(crate) fn get_start(&self) -> Option { + match self { + MethodHeader::Get(kw) => Some(kw.1.get_start()), + MethodHeader::Set(kw) => Some(kw.1.get_start()), + MethodHeader::Regular { r#async: Some(r#async), .. } => { + Some(r#async.get_position().get_start()) + } + MethodHeader::Regular { generator: Some(generator), .. } => Some(generator.get_start()), + MethodHeader::Regular { .. } => None, + } + } + + #[must_use] + pub fn is_async(&self) -> bool { + matches!(self, Self::Regular { r#async: Some(_), .. }) + } + + #[must_use] + pub fn is_generator(&self) -> bool { + matches!(self, Self::Regular { generator: Some(_), .. }) + } + + #[must_use] + pub fn is_some(&self) -> bool { + !matches!(self, Self::Regular { r#async: None, generator: None }) + } +} + +#[derive(Eq, PartialEq, Clone, Debug)] +#[cfg_attr(feature = "self-rust-tokenize", derive(self_rust_tokenize::SelfRustTokenize))] +#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))] +pub enum GeneratorSpecifier { + Star(Span), + #[cfg(feature = "extras")] + Keyword(Keyword), +} + +impl GeneratorSpecifier { + pub(crate) fn from_reader( + reader: &mut impl TokenReader, + ) -> Option { + match reader.peek() { + Some(Token(TSXToken::Multiply, _)) => { + Some(GeneratorSpecifier::Star(reader.next().unwrap().get_span())) + } + #[cfg(feature = "extras")] + Some(Token(TSXToken::Keyword(TSXKeyword::Generator), _)) => { + Some(GeneratorSpecifier::Keyword(Keyword::new(reader.next().unwrap().get_span()))) + } + _ => None, + } + } + + fn get_start(&self) -> source_map::Start { + match self { + GeneratorSpecifier::Star(pos) => pos.get_start(), + #[cfg(feature = "extras")] + GeneratorSpecifier::Keyword(kw) => kw.get_position().get_start(), + } + } +} diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs index f5c4b138..9720192d 100644 --- a/parser/src/lexer.rs +++ b/parser/src/lexer.rs @@ -5,7 +5,7 @@ use super::{Span, TSXToken}; use crate::{ cursor::EmptyCursorId, errors::LexingErrors, html_tag_contains_literal_content, - html_tag_is_self_closing, Quoted, + html_tag_is_self_closing, Comments, Quoted, }; use tokenizer_lib::{sized_tokens::TokenStart, Token, TokenSender}; @@ -15,7 +15,7 @@ use derive_finite_automaton::{ pub struct LexerOptions { /// Whether to append tokens when lexing. If false will just ignore - pub include_comments: bool, + pub comments: Comments, /// Whether to parse JSX. TypeScripts ` 2` breaks the lexer so this can be disabled to allow /// for that syntax pub lex_jsx: bool, @@ -26,7 +26,7 @@ pub struct LexerOptions { impl Default for LexerOptions { fn default() -> Self { Self { - include_comments: true, + comments: Comments::All, lex_jsx: true, allow_unsupported_characters_in_jsx_attribute_keys: true, } @@ -156,6 +156,8 @@ pub fn lex_script( escaped: bool, /// aka on flags after_last_slash: bool, + /// Forward slash while in `[...]` is allowed + in_set: bool, }, } @@ -328,6 +330,9 @@ pub fn lex_script( { *literal_type = NumberLiteralType::BigInt; } + // `10e-5` is a valid literal + '-' if matches!(literal_type, NumberLiteralType::Exponent) + && matches!(script[..idx].as_bytes().last(), Some(b'e' | b'E')) => {} chr => { if is_number_delimiter(chr) { // Note not = as don't want to include chr @@ -415,7 +420,7 @@ pub fn lex_script( expect_expression = false; continue; } - '\\' => { + '\\' if !*escaped => { *escaped = true; } _ => { @@ -424,7 +429,7 @@ pub fn lex_script( }, LexingState::Comment => { if let '\n' = chr { - if options.include_comments { + if matches!(options.comments, Comments::All) { push_token!(TSXToken::Comment( script[(start + 2)..idx].trim_end().to_owned() ),); @@ -435,10 +440,12 @@ pub fn lex_script( } LexingState::MultiLineComment { ref mut last_char_was_star } => match chr { '/' if *last_char_was_star => { - if options.include_comments { - push_token!(TSXToken::MultiLineComment( - script[(start + 2)..(idx - 1)].to_owned() - )); + let comment = &script[(start + 2)..(idx - 1)]; + let include = matches!(options.comments, Comments::All) + || (matches!(options.comments, Comments::JustDocumentation) + && comment.starts_with('*')); + if include { + push_token!(TSXToken::MultiLineComment(comment.to_owned())); } set_state!(LexingState::None); continue; @@ -447,7 +454,11 @@ pub fn lex_script( *last_char_was_star = chr == '*'; } }, - LexingState::RegexLiteral { ref mut escaped, ref mut after_last_slash } => { + LexingState::RegexLiteral { + ref mut escaped, + ref mut after_last_slash, + ref mut in_set, + } => { if *after_last_slash { if !matches!(chr, 'd' | 'g' | 'i' | 'm' | 's' | 'u' | 'y') { if start != idx { @@ -465,15 +476,24 @@ pub fn lex_script( state = LexingState::MultiLineComment { last_char_was_star: false }; continue; } - '/' if !*escaped => { + '/' if !*escaped && !*in_set => { push_token!(TSXToken::RegexLiteral( script[(start + 1)..idx].to_owned() )); *after_last_slash = true; start = idx + 1; } - chr => { - *escaped = chr == '\\'; + '\\' if !*escaped => { + *escaped = true; + } + '[' => { + *in_set = true; + } + ']' if *in_set => { + *in_set = false; + } + _ => { + *escaped = false; } } } @@ -488,10 +508,12 @@ pub fn lex_script( if idx > start + 1 { push_token!(TSXToken::TemplateLiteralChunk( script[start..(idx - 1)].to_owned() - ),); + )); } + start = idx - 1; push_token!(TSXToken::TemplateLiteralExpressionStart); *interpolation_depth += 1; + *last_char_was_dollar = false; state_stack.push(state); start = idx + 1; @@ -502,15 +524,18 @@ pub fn lex_script( if idx > start + 1 { push_token!(TSXToken::TemplateLiteralChunk(script[start..idx].to_owned())); } + start = idx; push_token!(TSXToken::TemplateLiteralEnd); start = idx + 1; state = LexingState::None; continue; } '\\' => { + *last_char_was_dollar = false; *escaped = true; } _ => { + *last_char_was_dollar = false; *escaped = false; } }, @@ -908,7 +933,6 @@ pub fn lex_script( ) => { *interpolation_depth -= 1; if *interpolation_depth == 0 { - start += 1; push_token!(TSXToken::TemplateLiteralExpressionEnd); start = idx + '}'.len_utf8(); state = state_stack.pop().unwrap(); @@ -971,6 +995,7 @@ pub fn lex_script( state = LexingState::RegexLiteral { escaped: false, after_last_slash: false, + in_set: false, }; } (true, '.') => { diff --git a/parser/src/lib.rs b/parser/src/lib.rs index 4e2cc8f9..db19920c 100644 --- a/parser/src/lib.rs +++ b/parser/src/lib.rs @@ -51,15 +51,14 @@ pub use types::{ }; pub use variable_fields::*; pub(crate) use visiting::{ - Chain, ChainVariable, ImmutableVariableOrPropertyPart, MutableVariablePart, VisitSettings, - Visitable, VisitorMutReceiver, VisitorReceiver, + Chain, ChainVariable, VisitOptions, Visitable, VisitorMutReceiver, VisitorReceiver, }; use tokenizer_lib::{sized_tokens::TokenEnd, Token, TokenReader}; pub(crate) use tokenizer_lib::sized_tokens::TokenStart; -use std::{borrow::Cow, ops::Neg, str::FromStr}; +use std::{borrow::Cow, str::FromStr}; /// The notation of a string #[derive(PartialEq, Eq, Debug, Clone, Copy)] @@ -79,7 +78,7 @@ impl Quoted { } } -/// Settings to customize parsing +/// Options to customize parsing #[allow(unused)] #[derive(Copy, Clone)] // TODO: Can be refactored with bit to reduce memory @@ -91,23 +90,26 @@ pub struct ParseOptions { pub special_jsx_attributes: bool, /// Parses decorators on items pub decorators: bool, - pub generator_keyword: bool, /// Skip **all** comments from the AST - pub include_comments: bool, + pub comments: Comments, /// See [crate::extensions::is_expression::IsExpression] pub is_expressions: bool, /// Allows functions to be prefixed with 'server' - pub server_functions: bool, - /// Allows functions to be prefixed with 'module' - pub module_functions: bool, + pub custom_function_headers: bool, /// For LSP allows incomplete AST for completions. TODO tidy up pub slots: bool, + /// TODO temp for seeing how channel performs + pub buffer_size: usize, + /// TODO temp for seeing how channel performs + /// + /// Has no effect on WASM + pub stack_size: Option, } impl ParseOptions { fn get_lex_options(&self) -> LexerOptions { LexerOptions { - include_comments: self.include_comments, + comments: self.comments, lex_jsx: self.jsx, allow_unsupported_characters_in_jsx_attribute_keys: self.special_jsx_attributes, } @@ -118,13 +120,13 @@ impl ParseOptions { Self { jsx: true, special_jsx_attributes: true, - include_comments: true, + comments: Comments::All, decorators: true, slots: true, - generator_keyword: true, - server_functions: true, - module_functions: true, + custom_function_headers: true, is_expressions: true, + buffer_size: 100, + stack_size: None, } } } @@ -135,13 +137,13 @@ impl Default for ParseOptions { Self { jsx: true, special_jsx_attributes: false, - include_comments: true, + comments: Comments::All, decorators: true, slots: false, - generator_keyword: true, - server_functions: false, - module_functions: false, - is_expressions: true, + custom_function_headers: false, + is_expressions: false, + buffer_size: 100, + stack_size: None, } } } @@ -154,11 +156,13 @@ pub struct ToStringOptions { pub pretty: bool, /// Blocks have trailing semicolons. Has no effect if pretty == false pub trailing_semicolon: bool, + /// Single statements get put on the same line as their parent statement + pub single_statement_on_new_line: bool, /// Include type annotation syntax pub include_types: bool, /// TODO not sure about this pub include_decorators: bool, - pub include_comments: bool, + pub comments: Comments, pub indent_with: String, /// If false, panics if sees JSX pub expect_jsx: bool, @@ -170,8 +174,9 @@ impl Default for ToStringOptions { ToStringOptions { pretty: true, include_types: false, + single_statement_on_new_line: true, include_decorators: false, - include_comments: true, + comments: Comments::All, expect_jsx: false, trailing_semicolon: false, expect_cursors: false, @@ -185,7 +190,7 @@ impl ToStringOptions { pub fn minified() -> Self { ToStringOptions { pretty: false, - include_comments: false, + comments: Comments::None, indent_with: String::new(), ..Default::default() } @@ -198,14 +203,16 @@ impl ToStringOptions { } /// Whether to include comment in source - pub(crate) fn should_add_comment(&self) -> bool { - self.pretty && self.include_comments + pub(crate) fn should_add_comment(&self, document_comment: bool) -> bool { + matches!(self.comments, Comments::All) + || (matches!(self.comments, Comments::JustDocumentation) && document_comment) } pub(crate) fn add_indent(&self, indent: u8, buf: &mut T) { (0..indent).for_each(|_| buf.push_str(&self.indent_with)); } + /// Adds whitespace **conditionally** (based on pretty setting) pub(crate) fn add_gap(&self, buf: &mut T) { if self.pretty { buf.push(' '); @@ -213,6 +220,14 @@ impl ToStringOptions { } } +#[derive(Debug, Clone, Copy)] +pub enum Comments { + All, + /// Only multiline comments starting with `/**` + JustDocumentation, + None, +} + /// Defines common methods that would exist on a AST part include position in source, creation from reader and /// serializing to string from options. /// @@ -267,22 +282,30 @@ pub fn lex_and_parse_script( offset: Option, cursors: Vec<(usize, CursorId<()>)>, ) -> Result { - let (mut sender, mut reader) = tokenizer_lib::ParallelTokenQueue::new(); + let (mut sender, mut reader) = + tokenizer_lib::ParallelTokenQueue::new_with_buffer_size(options.buffer_size); let lex_options = options.get_lex_options(); let length = script.len() as u32; - let parsing_thread = std::thread::spawn(move || { - let mut state = ParsingState { - line_starts, - source, - length_of_source: length, - constant_imports: Default::default(), - }; - let res = T::from_reader(&mut reader, &mut state, &options); - if res.is_ok() { - reader.expect_next(TSXToken::EOS)?; - } - res - }); + let mut thread = std::thread::Builder::new().name("AST parsing".into()); + if let Some(stack_size) = options.stack_size { + thread = thread.stack_size(stack_size); + } + + let parsing_thread = thread + .spawn(move || { + let mut state = ParsingState { + line_starts, + source, + length_of_source: length, + constant_imports: Default::default(), + }; + let res = T::from_reader(&mut reader, &mut state, &options); + if res.is_ok() { + reader.expect_next(TSXToken::EOS)?; + } + res + }) + .unwrap(); let lex_result = lexer::lex_script(script, &mut sender, &lex_options, offset, cursors); if let Err((reason, pos)) = lex_result { @@ -428,7 +451,7 @@ impl Visitable for Keyword { &self, visitors: &mut (impl VisitorReceiver + ?Sized), data: &mut TData, - _options: &VisitSettings, + _options: &VisitOptions, chain: &mut Annex, ) { visitors.visit_keyword(&(self.0.into(), &self.1), data, chain); @@ -438,7 +461,7 @@ impl Visitable for Keyword { &mut self, _visitors: &mut (impl VisitorMutReceiver + ?Sized), _data: &mut TData, - _options: &VisitSettings, + _options: &VisitOptions, _chain: &mut Annex, ) { // TODO should this have a implementation? @@ -476,6 +499,8 @@ impl std::fmt::Display for NumberSign { /// TODO a mix between runtime numbers and source syntax based number /// TODO hex cases lost in input :( /// +/// +/// Some of these can't be parsed, but are there to make is so that a number can be generated from just a f64 #[derive(Debug, Clone)] #[cfg_attr(feature = "self-rust-tokenize", derive(self_rust_tokenize::SelfRustTokenize))] #[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))] @@ -483,15 +508,35 @@ pub enum NumberRepresentation { Infinity, NegativeInfinity, NaN, - Hex(NumberSign, u64), - Bin(NumberSign, u64), - // Last one is whether it was specified with a leading zero (boo) - Octal(NumberSign, u64, bool), + Hex { + sign: NumberSign, + identifier_uppercase: bool, + value: u64, + }, + Bin { + sign: NumberSign, + identifier_uppercase: bool, + value: u64, + }, + Octal { + sign: NumberSign, + /// None = leading 0 (boo 👎) + identifier_uppercase: Option, + value: u64, + }, Number { - elided_zero_before_point: bool, - trailing_point: bool, /// TODO could do as something other than f64 - internal: f64, + value: f64, + /// To preserve formatting + before_point: u8, + /// To preserve formatting + after_point: Option, + }, + Exponential { + sign: NumberSign, + value: f64, + exponent: i32, + identifier_uppercase: bool, }, BigInt(NumberSign, String), } @@ -502,21 +547,31 @@ impl std::hash::Hash for NumberRepresentation { } } -impl From for f64 { - fn from(this: NumberRepresentation) -> f64 { +impl TryFrom for f64 { + // BigInt!! + type Error = (); + + fn try_from(this: NumberRepresentation) -> Result { match this { - NumberRepresentation::Infinity => f64::INFINITY, - NumberRepresentation::NegativeInfinity => f64::NEG_INFINITY, - NumberRepresentation::NaN => f64::NAN, - NumberRepresentation::Number { internal, .. } => internal, - NumberRepresentation::Hex(sign, nat) - | NumberRepresentation::Bin(sign, nat) - | NumberRepresentation::Octal(sign, nat, _) => sign.apply(nat as f64), - NumberRepresentation::BigInt(..) => todo!(), + NumberRepresentation::Infinity => Ok(f64::INFINITY), + NumberRepresentation::NegativeInfinity => Ok(f64::NEG_INFINITY), + NumberRepresentation::NaN => Ok(f64::NAN), + NumberRepresentation::Number { value: internal, .. } => Ok(internal), + NumberRepresentation::Hex { sign, value, .. } + | NumberRepresentation::Bin { sign, value, .. } + | NumberRepresentation::Octal { sign, value, .. } => Ok(sign.apply(value as f64)), + NumberRepresentation::Exponential { + sign, + value, + exponent, + identifier_uppercase: _, + } => Ok(sign.apply(value * 10f64.powi(exponent))), + NumberRepresentation::BigInt(..) => Err(()), } } } +// For code generation impl From for NumberRepresentation { fn from(value: f64) -> Self { if value == f64::INFINITY { @@ -526,7 +581,12 @@ impl From for NumberRepresentation { } else if value.is_nan() { Self::NaN } else { - Self::Number { internal: value, elided_zero_before_point: false, trailing_point: false } + Self::Number { + value, + // These values should be fine + before_point: 0, + after_point: None, + } } } } @@ -557,101 +617,128 @@ impl FromStr for NumberRepresentation { let next_char = s.chars().next(); match next_char { Some('.') => { - if s.len() == 2 { - Ok(Self::Number { - internal: 0f64, - elided_zero_before_point: false, - trailing_point: true, - }) + let after_point = Some(s.len() as u8 - 1); + let before_point = 1; + if s.len() == 1 { + Ok(Self::Number { value: 0f64, before_point, after_point }) } else { Ok(Self::Number { - internal: sign.apply(s.parse().map_err(|_| s.to_owned())?), - elided_zero_before_point: false, - trailing_point: false, + value: sign.apply(s.parse().map_err(|_| s.to_owned())?), + before_point, + after_point, }) } } - Some('X' | 'x') => { - let mut number = 0u64; + Some(c @ ('X' | 'x')) => { + let identifier_uppercase = c.is_uppercase(); + let mut value = 0u64; for c in s[2..].as_bytes() { - number <<= 4; // 16=2^4 + value <<= 4; // 16=2^4 match c { b'0'..=b'9' => { - number += u64::from(c - b'0'); + value += u64::from(c - b'0'); } b'a'..=b'f' => { - number += u64::from(c - b'a') + 10; + value += u64::from(c - b'a') + 10; } b'A'..=b'F' => { - number += u64::from(c - b'A') + 10; + value += u64::from(c - b'A') + 10; } _ => return Err(s.to_owned()), } } - Ok(Self::Hex(sign, number)) + Ok(Self::Hex { sign, identifier_uppercase, value }) } - Some('B' | 'b') => { - let mut number = 0u64; + Some(c @ ('B' | 'b')) => { + let identifier_uppercase = c.is_uppercase(); + let mut value = 0u64; for c in s[2..].as_bytes() { - number <<= 1; + value <<= 1; match c { b'0' | b'1' => { - number += u64::from(c - b'0'); + value += u64::from(c - b'0'); } _ => return Err(s.to_owned()), } } - Ok(Self::Bin(sign, number)) + Ok(Self::Bin { identifier_uppercase, sign, value }) + } + Some(c @ ('e' | 'E')) => { + let exponent: i32 = s[1..].parse().map_err(|_| s.to_owned())?; + Ok(Self::Exponential { + sign, + value: 0f64, + exponent, + identifier_uppercase: c.is_uppercase(), + }) } // 'o' | 'O' but can also be missed Some(c) => { let uses_character = matches!(c, 'o' | 'O'); - let start = if uses_character { 2 } else { 1 }; - let mut number = 0u64; + + if !uses_character && s.contains(['8', '9', '.']) { + let (before_point, after_point) = + s.split_once('.').map_or((s.len() as u8 + 1, None), |(l, r)| { + (l.len() as u8 + 1, Some(r.len() as u8)) + }); + + return Ok(Self::Number { + value: sign.apply(s.parse().map_err(|_| s.to_owned())?), + before_point, + after_point, + }); + } + + // If it uses the the character then skip one, else skip zero + let start: usize = uses_character.into(); + + let mut value = 0u64; for c in s[start..].as_bytes() { - number <<= 3; // 8=2^3 + value <<= 3; // 8=2^3 if matches!(c, b'0'..=b'7') { - number += u64::from(c - b'0'); + value += u64::from(c - b'0'); } else { return Err(s.to_owned()); } } - Ok(Self::Octal(sign, number, !uses_character)) + Ok(Self::Octal { + sign, + value, + identifier_uppercase: uses_character.then_some(c.is_uppercase()), + }) } - None => Ok(Self::Number { - internal: 0f64, - elided_zero_before_point: false, - trailing_point: false, - }), + None => Ok(Self::Number { value: 0f64, before_point: 1, after_point: None }), } - } else if s.ends_with('.') { - Ok(Self::Number { - internal: sign.apply(s.strip_suffix('.').unwrap().parse().map_err(|_| s.clone())?), - elided_zero_before_point: false, - trailing_point: true, - }) } else if s.starts_with('.') { - let value: f64 = s.strip_prefix('.').unwrap().parse().map_err(|_| s.clone())?; - let digits = value.log10().floor() + 1f64; - let result = value * (10f64.powf(-digits)); + let value: f64 = format!("0{s}").parse().map_err(|_| s.clone())?; Ok(Self::Number { - internal: sign.apply(result), - elided_zero_before_point: true, - trailing_point: false, + value: sign.apply(value), + before_point: 0, + after_point: Some(s.len() as u8 - 1), }) - } else if let Some((left, right)) = s.split_once(['e', 'E']) { - let value: f64 = left.parse().map_err(|_| s.clone())?; - let expo: i32 = right.parse().map_err(|_| s.clone())?; + } else if let Some(s) = s.strip_suffix('.') { Ok(Self::Number { - internal: sign.apply(value * 10f64.powi(expo)), - elided_zero_before_point: false, - trailing_point: false, + value: sign.apply(s.parse().map_err(|_| s.clone())?), + before_point: s.len() as u8, + after_point: Some(0), }) + } else if let Some((left, right)) = s.split_once('e') { + let value: f64 = left.parse().map_err(|_| s.clone())?; + let exponent: i32 = right.parse().map_err(|_| s.clone())?; + Ok(Self::Exponential { sign, value, exponent, identifier_uppercase: false }) + } else if let Some((left, right)) = s.split_once('E') { + let value: f64 = left.parse().map_err(|_| s.clone())?; + let exponent: i32 = right.parse().map_err(|_| s.clone())?; + Ok(Self::Exponential { sign, value, exponent, identifier_uppercase: true }) } else { + let (before_point, after_point) = s + .split_once('.') + .map_or((s.len() as u8, None), |(l, r)| (l.len() as u8, Some(r.len() as u8))); + Ok(Self::Number { - internal: sign.apply(s.parse().map_err(|_| s.clone())?), - elided_zero_before_point: false, - trailing_point: false, + value: sign.apply(s.parse().map_err(|_| s.clone())?), + before_point, + after_point, }) } } @@ -665,13 +752,11 @@ impl std::fmt::Display for NumberRepresentation { impl PartialEq for NumberRepresentation { fn eq(&self, other: &Self) -> bool { - match (self, other) { - // TODO needs to do conversion - (Self::Bin(l0, l1), Self::Bin(r0, r1)) - | (Self::Octal(l0, l1, _), Self::Octal(r0, r1, _)) - | (Self::Hex(l0, l1), Self::Hex(r0, r1)) => l0 == r0 && l1 == r1, - (Self::Number { internal: l0, .. }, Self::Number { internal: r0, .. }) => l0 == r0, - _ => core::mem::discriminant(self) == core::mem::discriminant(other), + if let (Ok(a), Ok(b)) = (f64::try_from(self.clone()), f64::try_from(other.clone())) { + a == b + } else { + // TODO ... + false } } } @@ -679,164 +764,70 @@ impl PartialEq for NumberRepresentation { impl Eq for NumberRepresentation {} impl NumberRepresentation { - #[must_use] - pub fn negate(&self) -> Self { - f64::from(self.clone()).neg().into() - } - #[must_use] pub fn as_js_string(self) -> String { match self { NumberRepresentation::Infinity => "Infinity".to_owned(), NumberRepresentation::NegativeInfinity => "-Infinity".to_owned(), NumberRepresentation::NaN => "NaN".to_owned(), - NumberRepresentation::Hex(sign, value) => format!("{sign}{value:#x}"), - NumberRepresentation::Bin(sign, value) => { - format!("{sign}0b{value:#b}") + NumberRepresentation::Hex { sign, identifier_uppercase: true, value, .. } => { + format!("{sign}0X{value:X}") } - NumberRepresentation::Octal(sign, value, true) => { - format!("{sign}0{value:o}") + NumberRepresentation::Hex { sign, identifier_uppercase: false, value, .. } => { + format!("{sign}0x{value:x}") } - NumberRepresentation::Octal(sign, value, false) => { - format!("{sign}0o{value:o}") + NumberRepresentation::Bin { sign, identifier_uppercase: true, value, .. } => { + format!("{sign}0B{value}") } - NumberRepresentation::Number { internal, elided_zero_before_point, trailing_point } => { - let mut start = internal.to_string(); - if elided_zero_before_point { - start.remove(0); - } - if trailing_point { - start.push('.'); - } - start + NumberRepresentation::Bin { sign, identifier_uppercase: false, value, .. } => { + format!("{sign}0b{value}") } - NumberRepresentation::BigInt(s, value) => format!("{s}{value}n"), - } - } -} - -#[derive(Eq, PartialEq, Clone, Debug)] -#[cfg_attr(feature = "self-rust-tokenize", derive(self_rust_tokenize::SelfRustTokenize))] -#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))] -pub enum GeneratorSpecifier { - Star(Span), - #[cfg(feature = "extras")] - Keyword(Keyword), -} - -impl GeneratorSpecifier { - pub(crate) fn from_reader( - reader: &mut impl TokenReader, - ) -> Option { - match reader.peek() { - Some(Token(TSXToken::Multiply, _)) => { - Some(GeneratorSpecifier::Star(reader.next().unwrap().get_span())) + NumberRepresentation::Octal { identifier_uppercase: None, sign, value } => { + format!("{sign}0{value:o}") } - #[cfg(feature = "extras")] - Some(Token(TSXToken::Keyword(TSXKeyword::Generator), _)) => { - Some(GeneratorSpecifier::Keyword(Keyword::new(reader.next().unwrap().get_span()))) + NumberRepresentation::Octal { identifier_uppercase: Some(true), sign, value } => { + format!("{sign}0O{value:o}") } - _ => None, - } - } - - fn get_start(&self) -> source_map::Start { - match self { - GeneratorSpecifier::Star(pos) => pos.get_start(), - #[cfg(feature = "extras")] - GeneratorSpecifier::Keyword(kw) => kw.get_position().get_start(), - } - } -} - -/// This structure removes possible invalid combinations with async -#[derive(Eq, PartialEq, Clone, Debug)] -#[cfg_attr(feature = "self-rust-tokenize", derive(self_rust_tokenize::SelfRustTokenize))] -#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))] -pub enum MethodHeader { - Get(Keyword), - Set(Keyword), - Regular { r#async: Option>, generator: Option }, -} - -impl Default for MethodHeader { - fn default() -> Self { - Self::Regular { r#async: None, generator: None } - } -} + NumberRepresentation::Octal { identifier_uppercase: Some(false), sign, value } => { + format!("{sign}0o{value:o}") + } + NumberRepresentation::Number { value, after_point, before_point } => { + let is_negative = value.is_sign_negative(); + let mut buf = value.abs().to_string(); + + // TODO only `options.preserve_formatting` + let (bp, ap) = buf + .split_once('.') + .map_or((buf.len() as u8, None), |(l, r)| (l.len() as u8, Some(r.len() as u8))); + + // Remove leading zero + if bp > before_point { + let removed = buf.remove(0); + debug_assert_eq!(removed, '0'); + } -impl MethodHeader { - pub(crate) fn to_string_from_buffer(&self, buf: &mut T) { - match self { - MethodHeader::Get(_) => buf.push_str("get "), - MethodHeader::Set(_) => buf.push_str("set "), - MethodHeader::Regular { r#async, generator } => { - if r#async.is_some() { - buf.push_str("async "); + (bp..before_point).for_each(|_| buf.insert(0, '0')); + if let Some(after_point) = after_point { + if ap.is_none() { + buf.push('.'); + } + (ap.unwrap_or_default()..after_point).for_each(|_| buf.push('0')); } - if let Some(_generator) = generator { - buf.push('*'); + if is_negative { + buf.insert(0, '-'); } + buf } - } - } - - pub(crate) fn from_reader(reader: &mut impl TokenReader) -> Self { - match reader.peek() { - Some(Token(TSXToken::Keyword(TSXKeyword::Get), _)) => { - MethodHeader::Get(Keyword::new(reader.next().unwrap().get_span())) - } - Some(Token(TSXToken::Keyword(TSXKeyword::Set), _)) => { - MethodHeader::Set(Keyword::new(reader.next().unwrap().get_span())) - } - _ => { - let r#async = reader - .conditional_next(|tok| matches!(tok, TSXToken::Keyword(TSXKeyword::Async))) - .map(|tok| Keyword::new(tok.get_span())); - - let generator = GeneratorSpecifier::from_reader(reader); - - MethodHeader::Regular { r#async, generator } - } - } - } - - pub(crate) fn get_start(&self) -> Option { - match self { - MethodHeader::Get(kw) => Some(kw.1.get_start()), - MethodHeader::Set(kw) => Some(kw.1.get_start()), - MethodHeader::Regular { r#async: Some(r#async), .. } => { - Some(r#async.get_position().get_start()) + NumberRepresentation::Exponential { sign, value, exponent, identifier_uppercase } => { + if identifier_uppercase { + format!("{sign}{value}E{exponent}") + } else { + format!("{sign}{value}e{exponent}") + } } - MethodHeader::Regular { generator: Some(generator), .. } => Some(generator.get_start()), - MethodHeader::Regular { .. } => None, + NumberRepresentation::BigInt(s, value) => format!("{s}{value}n"), } } - - #[must_use] - pub fn is_async(&self) -> bool { - matches!(self, Self::Regular { r#async: Some(_), .. }) - } - - #[must_use] - pub fn is_generator(&self) -> bool { - matches!(self, Self::Regular { generator: Some(_), .. }) - } - - fn is_some(&self) -> bool { - !matches!(self, Self::Regular { r#async: None, generator: None }) - } - - // pub(crate) fn get_end(&self) -> source_map::End { - // match self { - // MethodHeader::Get(kw) => kw.1.get_end(), - // MethodHeader::Set(kw) => kw.1.get_end(), - // MethodHeader::Async(kw) => kw.1.get_end(), - // MethodHeader::GeneratorStar(_, a) => a.get_end(), - // #[cfg(feature = "extras")] - // MethodHeader::Generator(_, a) => a.1.get_end(), - // } - // } } /// Classes and `function` functions have two variants depending whether in statement position @@ -844,79 +835,85 @@ impl MethodHeader { pub trait ExpressionOrStatementPosition: Clone + std::fmt::Debug + Sync + Send + PartialEq + Eq + 'static { - type Name: Clone + std::fmt::Debug + Sync + Send + PartialEq + Eq + 'static; - fn from_reader( reader: &mut impl TokenReader, state: &mut crate::ParsingState, options: &ParseOptions, - ) -> ParseResult; + ) -> ParseResult; - fn as_option_str(name: &Self::Name) -> Option<&str>; - fn as_option_string_mut(name: &mut Self::Name) -> Option<&mut String>; + fn as_option_variable_identifier(&self) -> Option<&VariableIdentifier>; + + fn as_option_variable_identifier_mut(&mut self) -> Option<&mut VariableIdentifier>; + + fn as_option_str(&self) -> Option<&str> { + if let Some(VariableIdentifier::Standard(name, _)) = self.as_option_variable_identifier() { + Some(name) + } else { + None + } + } } -#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] -pub struct StatementPosition; +#[derive(Debug, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "self-rust-tokenize", derive(self_rust_tokenize::SelfRustTokenize))] +#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))] +pub struct StatementPosition(VariableIdentifier); impl ExpressionOrStatementPosition for StatementPosition { - type Name = VariableIdentifier; - fn from_reader( reader: &mut impl TokenReader, state: &mut crate::ParsingState, options: &ParseOptions, - ) -> ParseResult { - VariableIdentifier::from_reader(reader, state, options) + ) -> ParseResult { + VariableIdentifier::from_reader(reader, state, options).map(Self) } - fn as_option_str(name: &Self::Name) -> Option<&str> { - if let VariableIdentifier::Standard(name, ..) = name { - Some(name) - } else { - None - } + fn as_option_variable_identifier(&self) -> Option<&VariableIdentifier> { + Some(&self.0) } - fn as_option_string_mut(name: &mut Self::Name) -> Option<&mut String> { - if let VariableIdentifier::Standard(name, ..) = name { - Some(name) - } else { - None - } + fn as_option_variable_identifier_mut(&mut self) -> Option<&mut VariableIdentifier> { + Some(&mut self.0) } } -#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] -pub struct ExpressionPosition; +#[derive(Debug, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "self-rust-tokenize", derive(self_rust_tokenize::SelfRustTokenize))] +#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))] +pub struct ExpressionPosition(Option); impl ExpressionOrStatementPosition for ExpressionPosition { - type Name = Option; - fn from_reader( reader: &mut impl TokenReader, state: &mut crate::ParsingState, options: &ParseOptions, - ) -> ParseResult { - if let Some(Token(TSXToken::OpenBrace, _)) | None = reader.peek() { - Ok(None) + ) -> ParseResult { + if let Some(Token( + TSXToken::OpenBrace + | TSXToken::OpenParentheses + | TSXToken::Keyword(TSXKeyword::Extends), + _, + )) + | None = reader.peek() + { + Ok(Self(None)) } else { - StatementPosition::from_reader(reader, state, options).map(Some) + Ok(Self(Some(VariableIdentifier::from_reader(reader, state, options)?))) } } - fn as_option_str(name: &Self::Name) -> Option<&str> { - name.as_ref().and_then(StatementPosition::as_option_str) + fn as_option_variable_identifier(&self) -> Option<&VariableIdentifier> { + self.0.as_ref() } - fn as_option_string_mut(name: &mut Self::Name) -> Option<&mut String> { - name.as_mut().and_then(StatementPosition::as_option_string_mut) + fn as_option_variable_identifier_mut(&mut self) -> Option<&mut VariableIdentifier> { + self.0.as_mut() } } /// Parses items surrounded in `{`, `[`, `(`, etc. /// -/// Supports trailing commas +/// Supports trailing commas. But **does not create** *empty* like items afterwards pub(crate) fn parse_bracketed( reader: &mut impl TokenReader, state: &mut crate::ParsingState, @@ -997,8 +994,7 @@ fn receiver_to_tokens( } let span = token.get_span(); let start = span.start; - let section = - (input.get(std::ops::Range::from(span.clone())).unwrap_or("?").to_owned(), true); + let section = (input.get(std::ops::Range::from(span)).unwrap_or("?").to_owned(), true); if last == start { last = span.end; Some(section) @@ -1063,12 +1059,12 @@ pub mod ast { expressions::*, extensions::jsx::*, functions::{ - FunctionBase, FunctionHeader, FunctionParameters, Parameter, ParameterData, - SpreadParameter, + FunctionBase, FunctionHeader, FunctionParameters, MethodHeader, Parameter, + ParameterData, SpreadParameter, }, statements::*, - Block, Decorated, Keyword, MethodHeader, NumberRepresentation, PropertyKey, - StatementOrDeclaration, VariableField, VariableIdentifier, WithComment, + Block, Decorated, Keyword, NumberRepresentation, PropertyKey, StatementOrDeclaration, + VariableField, VariableIdentifier, WithComment, }; pub use source_map::{BaseSpan, SourceId}; diff --git a/parser/src/modules.rs b/parser/src/modules.rs index feef2710..66748425 100644 --- a/parser/src/modules.rs +++ b/parser/src/modules.rs @@ -15,7 +15,7 @@ use crate::{ InterfaceDeclaration, }, BlockLike, BlockLikeMut, Decorated, Decorator, ParseOptions, ParseResult, - StatementOrDeclaration, TSXKeyword, VisitSettings, + StatementOrDeclaration, TSXKeyword, VisitOptions, }; use super::{ASTNode, ParseError, Span, TSXToken, Token, TokenReader}; @@ -108,7 +108,7 @@ impl Module { &self, visitors: &mut (impl crate::VisitorReceiver + ?Sized), data: &mut TData, - options: &VisitSettings, + options: &VisitOptions, ) { use crate::visiting::Visitable; let mut chain = crate::Chain::new_with_initial(crate::ChainVariable::Module(self.source)); @@ -130,7 +130,7 @@ impl Module { &mut self, visitors: &mut (impl crate::VisitorMutReceiver + ?Sized), data: &mut TData, - options: &VisitSettings, + options: &VisitOptions, ) { use crate::visiting::Visitable; let mut chain = crate::Chain::new_with_initial(crate::ChainVariable::Module(self.source)); @@ -195,8 +195,6 @@ impl TypeDefinitionModule { mut options: ParseOptions, source: SourceId, ) -> ParseResult { - // Unfortunately some comments contain data (variable ids) - options.include_comments = true; // Important not to parse JSX as <> is used for casting options.jsx = false; diff --git a/parser/src/operators.rs b/parser/src/operators.rs index 8f48564f..bca21659 100644 --- a/parser/src/operators.rs +++ b/parser/src/operators.rs @@ -53,7 +53,8 @@ pub enum BinaryAssignmentOperator { pub enum UnaryOperator { Plus, Negation, BitwiseNot, LogicalNot, - Await, TypeOf, Void, Delete, Yield, DelegatedYield, + Await, TypeOf, Void, Delete, + Yield, DelegatedYield, } #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -403,14 +404,12 @@ impl TryFrom<&TSXToken> for BinaryOperator { } } +// Note `yield` and `yield *` handled differently impl TryFrom<&TSXToken> for UnaryOperator { type Error = (); fn try_from(expression: &TSXToken) -> Result { match expression { - TSXToken::Keyword(TSXKeyword::Yield) => Ok(UnaryOperator::Yield), - // TODO - // TSXToken::Keyword(TSXKeywordToken::DelegatedYield) => Ok(UnaryOperators::DelegatedYield), TSXToken::Keyword(TSXKeyword::TypeOf) => Ok(UnaryOperator::TypeOf), TSXToken::Keyword(TSXKeyword::Await) => Ok(UnaryOperator::Await), TSXToken::Keyword(TSXKeyword::Void) => Ok(UnaryOperator::Void), diff --git a/parser/src/property_key.rs b/parser/src/property_key.rs index 6b9e049f..d4b82c47 100644 --- a/parser/src/property_key.rs +++ b/parser/src/property_key.rs @@ -1,6 +1,10 @@ -use crate::{Quoted, TSXToken}; +use crate::{ + visiting::{Chain, VisitOptions, Visitable}, + Quoted, TSXToken, +}; use source_map::Span; use std::fmt::Debug; +use temporary_annex::Annex; use tokenizer_lib::{sized_tokens::TokenReaderWithTokenEnds, Token, TokenReader}; use crate::{ @@ -137,8 +141,13 @@ impl ASTNode for PropertyKey { Ok(Self::Computed(Box::new(expression), start.union(end))) } token => { - let (name, position, private) = U::parse_ident(token, reader)?; - Ok(Self::Ident(name, position, private)) + if token.0.is_comment() { + // TODO could add marker? + Self::from_reader(reader, state, options) + } else { + let (name, position, private) = U::parse_ident(token, reader)?; + Ok(Self::Ident(name, position, private)) + } } } } @@ -166,33 +175,63 @@ impl ASTNode for PropertyKey { } } -// TODO -// impl WithComment { -// pub(crate) fn visit( -// &self, -// visitors: &mut (impl crate::VisitorReceiver + ?Sized), -// data: &mut TData, -// chain: &mut temporary_annex::Annex, -// location: PropertyKeyLocation, -// ) { -// visitors.visit_variable( -// &ImmutableVariableOrPropertyPart::PropertyKey(self, location), -// data, -// chain, -// ); -// } - -// pub(crate) fn visit_mut( -// &mut self, -// visitors: &mut (impl crate::VisitorMutReceiver + ?Sized), -// data: &mut TData, -// chain: &mut temporary_annex::Annex, -// location: PropertyKeyLocation, -// ) { -// visitors.visit_variable_mut( -// &mut MutableVariablePart::PropertyKey(self, location), -// data, -// chain, -// ); -// } -// } +// TODO visit expression? +impl Visitable for PropertyKey { + fn visit( + &self, + visitors: &mut (impl crate::VisitorReceiver + ?Sized), + data: &mut TData, + _options: &VisitOptions, + chain: &mut Annex, + ) { + visitors.visit_variable( + &crate::visiting::ImmutableVariableOrProperty::ClassPropertyKey(self), + data, + chain, + ); + } + + fn visit_mut( + &mut self, + visitors: &mut (impl crate::VisitorMutReceiver + ?Sized), + data: &mut TData, + _options: &VisitOptions, + chain: &mut Annex, + ) { + visitors.visit_variable_mut( + &mut crate::visiting::MutableVariableOrProperty::ClassPropertyKey(self), + data, + chain, + ); + } +} + +impl Visitable for PropertyKey { + fn visit( + &self, + visitors: &mut (impl crate::VisitorReceiver + ?Sized), + data: &mut TData, + _options: &VisitOptions, + chain: &mut Annex, + ) { + visitors.visit_variable( + &crate::visiting::ImmutableVariableOrProperty::ObjectPropertyKey(self), + data, + chain, + ); + } + + fn visit_mut( + &mut self, + visitors: &mut (impl crate::VisitorMutReceiver + ?Sized), + data: &mut TData, + _options: &VisitOptions, + chain: &mut Annex, + ) { + visitors.visit_variable_mut( + &mut crate::visiting::MutableVariableOrProperty::ObjectPropertyKey(self), + data, + chain, + ); + } +} diff --git a/parser/src/statements/mod.rs b/parser/src/statements/mod.rs index 93f64b60..59243e95 100644 --- a/parser/src/statements/mod.rs +++ b/parser/src/statements/mod.rs @@ -31,7 +31,6 @@ pub use while_statement::{DoWhileStatement, WhileStatement}; #[get_field_by_type_target(Span)] #[try_into_references(&, &mut)] #[partial_eq_ignore_types(Span)] -#[visit_self(under statement)] #[cfg_attr(feature = "self-rust-tokenize", derive(self_rust_tokenize::SelfRustTokenize))] #[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))] pub enum Statement { @@ -151,7 +150,7 @@ impl ASTNode for Statement { reader.peek(), Some(Token(TSXToken::SemiColon | TSXToken::CloseBrace, _)) ) { - let position = return_keyword.get_position().clone(); + let position = *return_keyword.get_position(); Statement::Return(ReturnStatement(return_keyword, None, position)) } else { let multiple_expression = @@ -246,13 +245,13 @@ impl ASTNode for Statement { Statement::DoWhileStatement(dws) => dws.to_string_from_buffer(buf, options, depth), Statement::TryCatchStatement(tcs) => tcs.to_string_from_buffer(buf, options, depth), Statement::Comment(comment, _) => { - if options.should_add_comment() { + if options.should_add_comment(false) { buf.push_str("//"); buf.push_str_contains_new_line(comment.as_str().trim_end()); } } Statement::MultiLineComment(comment, _) => { - if options.should_add_comment() { + if options.should_add_comment(comment.starts_with('*')) { buf.push_str("/*"); buf.push_str_contains_new_line(comment.as_str()); buf.push_str("*/"); diff --git a/parser/src/statements/switch_statement.rs b/parser/src/statements/switch_statement.rs index bc5e87cd..90059015 100644 --- a/parser/src/statements/switch_statement.rs +++ b/parser/src/statements/switch_statement.rs @@ -5,10 +5,10 @@ use visitable_derive::Visitable; use crate::{ ast::MultipleExpression, errors::parse_lexing_error, throw_unexpected_token_with_token, - ASTNode, Expression, ParseOptions, Statement, TSXKeyword, TSXToken, + ASTNode, Expression, ParseOptions, StatementOrDeclaration, TSXKeyword, TSXToken, }; -#[derive(Debug, PartialEq, Eq, Clone, Visitable, get_field_by_type::GetFieldByType)] +#[derive(Debug, PartialEq, Clone, Visitable, get_field_by_type::GetFieldByType)] #[get_field_by_type_target(Span)] #[cfg_attr(feature = "self-rust-tokenize", derive(self_rust_tokenize::SelfRustTokenize))] #[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))] @@ -18,12 +18,12 @@ pub struct SwitchStatement { pub position: Span, } -#[derive(Debug, PartialEq, Eq, Clone, Visitable)] +#[derive(Debug, PartialEq, Clone, Visitable)] #[cfg_attr(feature = "self-rust-tokenize", derive(self_rust_tokenize::SelfRustTokenize))] #[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))] pub enum SwitchBranch { - Default(Vec), - Case(Expression, Vec), + Default(Vec), + Case(Expression, Vec), } impl ASTNode for SwitchStatement { @@ -80,7 +80,7 @@ impl ASTNode for SwitchStatement { { break; } - statements.push(Statement::from_reader(reader, state, options)?); + statements.push(StatementOrDeclaration::from_reader(reader, state, options)?); if let Some(Token(TSXToken::SemiColon, _)) = reader.peek() { reader.next(); } diff --git a/parser/src/statements/while_statement.rs b/parser/src/statements/while_statement.rs index f2c95ce7..b930460d 100644 --- a/parser/src/statements/while_statement.rs +++ b/parser/src/statements/while_statement.rs @@ -1,14 +1,16 @@ use source_map::Span; use visitable_derive::Visitable; -use crate::{block::BlockOrSingleStatement, ASTNode, Expression, TSXKeyword, TSXToken}; +use crate::{ + ast::MultipleExpression, block::BlockOrSingleStatement, ASTNode, TSXKeyword, TSXToken, +}; #[derive(Debug, PartialEq, Eq, Clone, Visitable, get_field_by_type::GetFieldByType)] #[get_field_by_type_target(Span)] #[cfg_attr(feature = "self-rust-tokenize", derive(self_rust_tokenize::SelfRustTokenize))] #[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))] pub struct WhileStatement { - pub condition: Expression, + pub condition: MultipleExpression, pub inner: BlockOrSingleStatement, pub position: Span, } @@ -25,7 +27,7 @@ impl ASTNode for WhileStatement { ) -> Result { let start = reader.expect_next(TSXToken::Keyword(TSXKeyword::While))?; reader.expect_next(TSXToken::OpenParentheses)?; - let condition = Expression::from_reader(reader, state, options)?; + let condition = MultipleExpression::from_reader(reader, state, options)?; reader.expect_next(TSXToken::CloseParentheses)?; let inner = BlockOrSingleStatement::from_reader(reader, state, options)?; Ok(Self { position: start.union(inner.get_position()), condition, inner }) @@ -53,7 +55,7 @@ impl ASTNode for WhileStatement { #[cfg_attr(feature = "self-rust-tokenize", derive(self_rust_tokenize::SelfRustTokenize))] #[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))] pub struct DoWhileStatement { - pub condition: Expression, + pub condition: MultipleExpression, // TODO not sure about true here pub inner: BlockOrSingleStatement, pub position: Span, @@ -73,7 +75,7 @@ impl ASTNode for DoWhileStatement { let inner = BlockOrSingleStatement::from_reader(reader, state, options)?; reader.expect_next(TSXToken::Keyword(TSXKeyword::While))?; reader.expect_next(TSXToken::OpenParentheses)?; - let condition = Expression::from_reader(reader, state, options)?; + let condition = MultipleExpression::from_reader(reader, state, options)?; reader.expect_next(TSXToken::CloseParentheses)?; Ok(Self { position: start.union(inner.get_position()), condition, inner }) } diff --git a/parser/src/tokens.rs b/parser/src/tokens.rs index fd460cd3..9ea268a3 100644 --- a/parser/src/tokens.rs +++ b/parser/src/tokens.rs @@ -67,7 +67,6 @@ use crate::{ParseError, Quoted}; "-?:" => TSXToken::NonOptionalMember, "??" => TSXToken::NullishCoalescing, "??=" => TSXToken::NullishCoalescingAssign, - "!" => TSXToken::LogicalNot, "!=" => TSXToken::NotEqual, "!==" => TSXToken::StrictNotEqual, "<" => TSXToken::OpenChevron, @@ -91,7 +90,7 @@ use crate::{ParseError, Quoted}; ))] #[rustfmt::skip] pub enum TSXToken { - IdentLiteral(String), + Identifier(String), Keyword(TSXKeyword), NumberLiteral(String), StringLiteral(String, Quoted), @@ -190,7 +189,7 @@ impl tokenizer_lib::sized_tokens::SizedToken for TSXToken { | TSXToken::JSXContent(lit) | TSXToken::JSXComment(lit) | TSXToken::JSXTagName(lit) - | TSXToken::IdentLiteral(lit) + | TSXToken::Identifier(lit) | TSXToken::NumberLiteral(lit) | TSXToken::RegexFlagLiteral(lit) => lit.len() as u32, @@ -326,8 +325,10 @@ pub enum TSXKeyword { Private, Public, Protected, // TS Keywords As, Declare, Readonly, Infer, Is, Satisfies, Namespace, KeyOf, + // TODO not sure + #[cfg(feature = "extras")] Module, // Extra function modifiers - #[cfg(feature = "extras")] Server, #[cfg(feature = "extras")] Module, + #[cfg(feature = "extras")] Server, #[cfg(feature = "extras")] Worker, // Type declaration changes #[cfg(feature = "extras")] Nominal, #[cfg(feature = "extras")] Performs, @@ -341,18 +342,15 @@ pub enum TSXKeyword { impl TSXKeyword { #[cfg(feature = "extras")] - pub(crate) fn is_in_function_header(&self) -> bool { - matches!( - self, - TSXKeyword::Function - | TSXKeyword::Async - | TSXKeyword::Module - | TSXKeyword::Server - | TSXKeyword::Generator - ) + pub(crate) fn is_special_function_header(&self) -> bool { + matches!(self, TSXKeyword::Worker | TSXKeyword::Server | TSXKeyword::Generator) } #[cfg(not(feature = "extras"))] + pub(crate) fn is_special_function_header(&self) -> bool { + false + } + pub(crate) fn is_in_function_header(&self) -> bool { matches!(self, TSXKeyword::Function | TSXKeyword::Async) } @@ -369,7 +367,7 @@ impl std::fmt::Display for TSXToken { match self { TSXToken::Keyword(kw) => std::fmt::Debug::fmt(kw, f), TSXToken::NumberLiteral(num) => std::fmt::Display::fmt(num, f), - TSXToken::IdentLiteral(value) => std::fmt::Display::fmt(value, f), + TSXToken::Identifier(value) => std::fmt::Display::fmt(value, f), _ => std::fmt::Debug::fmt(&self, f), } } @@ -400,16 +398,17 @@ impl TSXToken { pub fn is_expression_prefix(&self) -> bool { matches!( self, - TSXToken::Keyword(TSXKeyword::Return) - | TSXToken::Assign - | TSXToken::Arrow - | TSXToken::OpenParentheses - | TSXToken::OpenBrace - | TSXToken::JSXExpressionStart - | TSXToken::QuestionMark - | TSXToken::Colon - // This is for match bindings - | TSXToken::At + TSXToken::Keyword(TSXKeyword::Return | TSXKeyword::Yield | TSXKeyword::Throw) + | TSXToken::Assign + | TSXToken::Arrow + | TSXToken::OpenParentheses + | TSXToken::OpenBrace + | TSXToken::JSXExpressionStart + | TSXToken::QuestionMark + | TSXToken::Colon + | TSXToken::LogicalNot + | TSXToken::LogicalAnd + | TSXToken::LogicalOr ) } @@ -418,9 +417,13 @@ impl TSXToken { pub fn from_slice(slice: &str) -> Self { match TSXKeyword::from_str(slice) { Ok(keyword_token) => TSXToken::Keyword(keyword_token), - Err(_) => TSXToken::IdentLiteral(slice.to_owned()), + Err(_) => TSXToken::Identifier(slice.to_owned()), } } + + pub(crate) fn is_symbol(&self) -> bool { + !matches!(self, TSXToken::Keyword(_) | TSXToken::Identifier(..)) + } } /// Some tokens can be used as names for variables, methods (eg 'get' in .get()). This function @@ -432,7 +435,7 @@ pub(crate) fn token_as_identifier( ) -> Result<(String, Span), ParseError> { let position = token.get_span(); let name = match token.0 { - TSXToken::IdentLiteral(value) => value, + TSXToken::Identifier(value) => value, TSXToken::Keyword(keyword) => EnumVariantsStrings::to_str(&keyword).to_owned(), token_type => { return Err(ParseError::new( diff --git a/parser/src/types/declares.rs b/parser/src/types/declares.rs index 9d31e52f..5e0764eb 100644 --- a/parser/src/types/declares.rs +++ b/parser/src/types/declares.rs @@ -79,13 +79,13 @@ impl DeclareVariableDeclaration { let kw_position = token.get_span(); let keyword = match token { Token(TSXToken::Keyword(TSXKeyword::Const), _) => { - DeclareVariableKeyword::Const(Keyword::new(kw_position.clone())) + DeclareVariableKeyword::Const(Keyword::new(kw_position)) } Token(TSXToken::Keyword(TSXKeyword::Let), _) => { - DeclareVariableKeyword::Let(Keyword::new(kw_position.clone())) + DeclareVariableKeyword::Let(Keyword::new(kw_position)) } Token(TSXToken::Keyword(TSXKeyword::Var), _) => { - DeclareVariableKeyword::Var(Keyword::new(kw_position.clone())) + DeclareVariableKeyword::Var(Keyword::new(kw_position)) } token => { return crate::throw_unexpected_token_with_token( diff --git a/parser/src/types/interface.rs b/parser/src/types/interface.rs index a3359725..c4ffe525 100644 --- a/parser/src/types/interface.rs +++ b/parser/src/types/interface.rs @@ -1,16 +1,10 @@ use crate::{ - errors::parse_lexing_error, - extensions::decorators::Decorated, - parse_bracketed, - property_key::PublicOrPrivate, - throw_unexpected_token_with_token, to_string_bracketed, - tokens::token_as_identifier, - tsx_keywords, - types::{ - type_annotations::TypeAnnotationFunctionParameters, type_declarations::TypeDeclaration, - }, - ASTNode, Expression, GenericTypeConstraint, Keyword, MethodHeader, NumberRepresentation, - ParseOptions, ParseResult, PropertyKey, Span, TSXKeyword, TSXToken, TypeAnnotation, + errors::parse_lexing_error, extensions::decorators::Decorated, functions::MethodHeader, + parse_bracketed, property_key::PublicOrPrivate, throw_unexpected_token_with_token, + to_string_bracketed, tokens::token_as_identifier, tsx_keywords, + types::type_annotations::TypeAnnotationFunctionParameters, ASTNode, Expression, + GenericTypeConstraint, Keyword, NumberRepresentation, ParseOptions, ParseResult, PropertyKey, + Span, TSXKeyword, TSXToken, TypeAnnotation, TypeDeclaration, WithComment, }; use get_field_by_type::GetFieldByType; @@ -28,7 +22,7 @@ pub struct InterfaceDeclaration { pub type_parameters: Option>, /// The document interface extends a multiple of other interfaces pub extends: Option>, - pub members: Vec>, + pub members: Vec>>, pub position: Span, } @@ -272,10 +266,8 @@ impl ASTNode for InterfaceMember { } else { None }; - let position = return_type - .as_ref() - .map_or(¶meters.position, ASTNode::get_position) - .clone(); + let position = + *return_type.as_ref().map_or(¶meters.position, ASTNode::get_position); Ok(InterfaceMember::Caller { is_readonly, @@ -329,16 +321,13 @@ impl ASTNode for InterfaceMember { return_type, }) } - TSXToken::MultiLineComment(..) | TSXToken::Comment(..) => { + token if token.is_comment() => { let token = reader.next().unwrap(); - let span = token.get_span(); - - let (TSXToken::MultiLineComment(comment) | TSXToken::Comment(comment)) = token.0 - else { + if let Ok((comment, span)) = TSXToken::try_into_comment(token) { + Ok(InterfaceMember::Comment(comment, span)) + } else { unreachable!() - }; - - Ok(InterfaceMember::Comment(comment, span)) + } } _ => { let header = MethodHeader::from_reader(reader); @@ -358,7 +347,7 @@ impl ASTNode for InterfaceMember { let position = start.with_length(value.len()); PropertyKey::NumberLiteral( value.parse::().unwrap(), - position.clone(), + position, ) } token => { @@ -489,7 +478,7 @@ impl ASTNode for InterfaceMember { TypeAnnotationFunctionParameters::from_reader_sub_open_parenthesis( reader, state, options, start, )?; - let mut position = start.union(¶meters.position); + let mut position = start.union(parameters.position); let return_type = if reader .conditional_next(|tok| matches!(tok, TSXToken::Colon)) .is_some() @@ -529,7 +518,7 @@ impl ASTNode for InterfaceMember { let parameters = TypeAnnotationFunctionParameters::from_reader(reader, state, options)?; - let mut position = start.union(¶meters.position); + let mut position = start.union(parameters.position); let return_type = if reader .conditional_next(|tok| matches!(tok, TSXToken::Colon)) @@ -665,13 +654,13 @@ pub(crate) fn parse_interface_members( reader: &mut impl TokenReader, state: &mut crate::ParsingState, options: &ParseOptions, -) -> ParseResult>> { +) -> ParseResult>>> { let mut members = Vec::new(); loop { if let Some(Token(TSXToken::CloseBrace, _)) = reader.peek() { break; } - let decorated_member = Decorated::::from_reader(reader, state, options)?; + let decorated_member = WithComment::from_reader(reader, state, options)?; members.push(decorated_member); // Semi colons and commas are optional here if let Some(Token(TSXToken::SemiColon | TSXToken::Comma, _)) = reader.peek() { diff --git a/parser/src/types/type_annotations.rs b/parser/src/types/type_annotations.rs index 633078e8..cfceb5b3 100644 --- a/parser/src/types/type_annotations.rs +++ b/parser/src/types/type_annotations.rs @@ -20,8 +20,6 @@ use crate::{ }; /// A reference to a type -/// -/// TODO need to figure out what [`TypeId`] is used for here and where it might be useful for the checker #[derive(Debug, Clone, PartialEqExtras, Eq, get_field_by_type::GetFieldByType)] #[get_field_by_type_target(Span)] #[partial_eq_ignore_types(Span)] @@ -64,8 +62,7 @@ pub enum TypeAnnotation { position: Span, }, /// Object literal e.g. `{ y: string }` - /// Here [TypeId] refers to the type it declares - ObjectLiteral(Vec>, Span), + ObjectLiteral(Vec>>, Span), /// Tuple literal e.g. `[number, x: string]` TupleLiteral(Vec<(SpreadKind, AnnotationWithBinder)>, Span), /// ? @@ -724,10 +721,10 @@ impl TypeAnnotation { reader.next(); let return_type = Self::from_reader_with_config(reader, state, options, true, false)?; - let parameters_position = reference.get_position().clone(); + let parameters_position = *reference.get_position(); let position = parameters_position.union(return_type.get_position()); Ok(Self::FunctionLiteral { - position: position.clone(), + position, type_parameters: None, parameters: TypeAnnotationFunctionParameters { parameters: vec![TypeAnnotationFunctionParameter { diff --git a/parser/src/variable_fields.rs b/parser/src/variable_fields.rs index 9aee3de0..e4aaae50 100644 --- a/parser/src/variable_fields.rs +++ b/parser/src/variable_fields.rs @@ -4,10 +4,14 @@ use std::fmt::Debug; use crate::{ - errors::parse_lexing_error, parse_bracketed, property_key::PropertyKey, - throw_unexpected_token_with_token, tokens::token_as_identifier, ASTNode, CursorId, Expression, - ImmutableVariableOrPropertyPart, MutableVariablePart, ParseError, ParseOptions, ParseResult, - Span, TSXToken, Token, VisitSettings, Visitable, WithComment, + errors::parse_lexing_error, + parse_bracketed, + property_key::PropertyKey, + throw_unexpected_token_with_token, + tokens::token_as_identifier, + visiting::{ImmutableVariableOrProperty, MutableVariableOrProperty}, + ASTNode, CursorId, Expression, ParseError, ParseOptions, ParseResult, Span, TSXToken, Token, + VisitOptions, Visitable, WithComment, }; use derive_partial_eq_extras::PartialEqExtras; @@ -265,9 +269,31 @@ impl ASTNode for VariableField { } TSXToken::OpenBracket => { let Token(_, start_pos) = reader.next().unwrap(); - let (items, end_pos) = - parse_bracketed(reader, state, options, None, TSXToken::CloseBracket)?; - Ok(Self::Array(items, start_pos.union(end_pos))) + let mut items: Vec<_> = Vec::new(); + // No trailing comments + loop { + if let Some(token) = + reader.conditional_next(|token| *token == TSXToken::CloseBracket) + { + return Ok(Self::Array(items, start_pos.union(token.get_end()))); + } + + items.push(ArrayDestructuringField::from_reader(reader, state, options)?); + + if !reader + .peek() + .map_or(false, |Token(t, _)| matches!(t, TSXToken::CloseBracket)) + { + reader.expect_next(TSXToken::Comma)?; + // TODO not great fix + if reader + .peek() + .map_or(false, |Token(t, _)| matches!(t, TSXToken::CloseBracket)) + { + items.push(ArrayDestructuringField::None); + } + } + } } _ => Ok(Self::Name(VariableIdentifier::from_reader(reader, state, options)?)), } @@ -357,7 +383,7 @@ impl ASTNode for ObjectDestructuringField { if let Some(pos) = U::optional_expression_get_position(&default_value) { key.get_position().union(pos) } else { - key.get_position().clone() + *key.get_position() }; Ok(Self::Map { from: key, name: variable_name, default_value, position }) } else if let PropertyKey::Ident(name, key_pos, ()) = key { @@ -367,7 +393,7 @@ impl ASTNode for ObjectDestructuringField { if let Some(pos) = U::optional_expression_get_position(&default_value) { standard.get_position().union(pos) } else { - standard.get_position().clone() + *standard.get_position() }; Ok(Self::Name(standard, default_value, position)) } else { @@ -466,7 +492,9 @@ impl ASTNode for ArrayDestructuringField { name.to_string_from_buffer(buf, options, depth); U::optional_expression_to_string_from_buffer(default_value, buf, options, depth); } - Self::None => {} + Self::None => { + options.add_gap(buf); + } } } @@ -481,21 +509,21 @@ impl Visitable for VariableField { &self, visitors: &mut (impl crate::VisitorReceiver + ?Sized), data: &mut TData, - options: &VisitSettings, + options: &VisitOptions, chain: &mut temporary_annex::Annex, ) { // TODO map match self { VariableField::Name(id) => { if let VariableIdentifier::Standard(name, pos) = id { - let item = ImmutableVariableOrPropertyPart::VariableFieldName(name, pos); + let item = ImmutableVariableOrProperty::VariableFieldName(name, pos); visitors.visit_variable(&item, data, chain); } } VariableField::Array(array_destructuring_fields, _) => { for field in array_destructuring_fields { visitors.visit_variable( - &ImmutableVariableOrPropertyPart::ArrayDestructuringMember(field), + &ImmutableVariableOrProperty::ArrayDestructuringMember(field), data, chain, ); @@ -512,7 +540,7 @@ impl Visitable for VariableField { VariableField::Object(object_destructuring_fields, _) => { for field in object_destructuring_fields { visitors.visit_variable( - &ImmutableVariableOrPropertyPart::ObjectDestructuringMember(field), + &ImmutableVariableOrProperty::ObjectDestructuringMember(field), data, chain, ); @@ -539,14 +567,14 @@ impl Visitable for VariableField { &mut self, visitors: &mut (impl crate::VisitorMutReceiver + ?Sized), data: &mut TData, - options: &VisitSettings, + options: &VisitOptions, chain: &mut temporary_annex::Annex, ) { match self { VariableField::Name(identifier) => { if let VariableIdentifier::Standard(name, _span) = identifier { visitors.visit_variable_mut( - &mut MutableVariablePart::VariableFieldName(name), + &mut MutableVariableOrProperty::VariableFieldName(name), data, chain, ); @@ -555,7 +583,7 @@ impl Visitable for VariableField { VariableField::Array(array_destructuring_fields, _) => { for field in array_destructuring_fields.iter_mut() { visitors.visit_variable_mut( - &mut MutableVariablePart::ArrayDestructuringMember(field), + &mut MutableVariableOrProperty::ArrayDestructuringMember(field), data, chain, ); @@ -572,7 +600,7 @@ impl Visitable for VariableField { VariableField::Object(object_destructuring_fields, _) => { for field in object_destructuring_fields.iter_mut() { visitors.visit_variable_mut( - &mut MutableVariablePart::ObjectDestructuringMember(field), + &mut MutableVariableOrProperty::ObjectDestructuringMember(field), data, chain, ); diff --git a/parser/src/visiting.rs b/parser/src/visiting.rs index 1305fcdf..d7e768ec 100644 --- a/parser/src/visiting.rs +++ b/parser/src/visiting.rs @@ -1,7 +1,9 @@ +//! Contains logic that makes viewing and transform parts of AST trivial through the visitor pattern + use source_map::SourceId; use crate::{ - ArrayDestructuringField, Expression, JSXElement, ObjectDestructuringField, PropertyKey, + ArrayDestructuringField, Expression, ObjectDestructuringField, PropertyKey, StatementOrDeclaration, WithComment, }; @@ -18,28 +20,28 @@ mod ast { use crate::block::{BlockLike, BlockLikeMut}; use super::{ - Chain, Expression, ImmutableVariableOrPropertyPart, JSXElement, MutableVariablePart, - StatementOrDeclarationMut, StatementOrDeclarationRef, VisitorMutReceiver, VisitorReceiver, + BlockItem, BlockItemMut, Chain, Expression, ImmutableVariableOrProperty, + MutableVariableOrProperty, VisitorMutReceiver, VisitorReceiver, }; /// Options for behavior when visiting AST. /// Customizable behavior is important for analysis - pub struct VisitSettings { + pub struct VisitOptions { /// Visits statements in reverse, /// e.g /// ```ts /// const x = 2; /// const y = 3; /// ``` - /// If `reverse_statements` is true will visit the `const y = 3` statement first + /// If `reverse_statements` is true, `const y = 3` will be visited before `const x = 2` pub reverse_statements: bool, - /// Will not visit parameters and statements in functions. This includes arrow functions - pub visit_function_bodies: bool, + /// Will not visit anything under nested blocks, functions etc + pub visit_nested_blocks: bool, } - impl Default for VisitSettings { + impl Default for VisitOptions { fn default() -> Self { - Self { reverse_statements: false, visit_function_bodies: true } + Self { reverse_statements: false, visit_nested_blocks: true } } } @@ -51,15 +53,19 @@ mod ast { )* } } + + /// Yielded during visiting AST. Might not be 1:1 with AST and include a wrapping type pub trait SelfVisitable {} + + /// Yielded during visiting AST. Might not be 1:1 with AST and include a wrapping type pub trait SelfVisitableMut {} mark_items! { - impl SelfVisitable for Expression, StatementOrDeclarationRef<'_>, BlockLike<'_>, JSXElement, ImmutableVariableOrPropertyPart<'_> + impl SelfVisitable for Expression, BlockItem<'_>, BlockLike<'_>, ImmutableVariableOrProperty<'_> } mark_items! { - impl SelfVisitableMut for Expression, StatementOrDeclarationMut<'_>, BlockLikeMut<'_>, JSXElement, MutableVariablePart<'_> + impl SelfVisitableMut for Expression, BlockLikeMut<'_>, BlockItemMut<'_>, MutableVariableOrProperty<'_> } /// For something to visitable it can visit all nested fields. @@ -68,7 +74,7 @@ mod ast { &self, visitors: &mut (impl VisitorReceiver + ?Sized), data: &mut TData, - options: &VisitSettings, + options: &VisitOptions, chain: &mut Annex, ); @@ -76,7 +82,7 @@ mod ast { &mut self, visitors: &mut (impl VisitorMutReceiver + ?Sized), data: &mut TData, - options: &VisitSettings, + options: &VisitOptions, chain: &mut Annex, ); } @@ -87,7 +93,7 @@ mod ast { &self, v: &mut (impl VisitorReceiver + ?Sized), d: &mut TData, - s: &VisitSettings, + s: &VisitOptions, c: &mut Annex, ) { Visitable::visit(&**self, v, d, s, c); @@ -97,7 +103,7 @@ mod ast { &mut self, v: &mut (impl VisitorMutReceiver + ?Sized), d: &mut TData, - s: &VisitSettings, + s: &VisitOptions, c: &mut Annex, ) { Visitable::visit_mut(&mut **self, v, d, s, c); @@ -109,7 +115,7 @@ mod ast { &self, v: &mut (impl VisitorReceiver + ?Sized), d: &mut TData, - s: &VisitSettings, + s: &VisitOptions, c: &mut Annex, ) { self.iter().for_each(|item| item.visit(v, d, s, c)); @@ -119,7 +125,7 @@ mod ast { &mut self, v: &mut (impl VisitorMutReceiver + ?Sized), d: &mut TData, - s: &VisitSettings, + s: &VisitOptions, c: &mut Annex, ) { self.iter_mut().for_each(|item| item.visit_mut(v, d, s, c)); @@ -131,7 +137,7 @@ mod ast { &self, v: &mut (impl VisitorReceiver + ?Sized), d: &mut TData, - s: &VisitSettings, + s: &VisitOptions, c: &mut Annex, ) { if let Some(item) = self { @@ -143,7 +149,7 @@ mod ast { &mut self, v: &mut (impl VisitorMutReceiver + ?Sized), d: &mut TData, - s: &VisitSettings, + s: &VisitOptions, c: &mut Annex, ) { if let Some(item) = self { @@ -157,7 +163,7 @@ mod ast { &self, v: &mut (impl VisitorReceiver + ?Sized), d: &mut TData, - s: &VisitSettings, + s: &VisitOptions, c: &mut Annex, ) { self.0.visit(v, d, s, c); @@ -168,7 +174,7 @@ mod ast { &mut self, v: &mut (impl VisitorMutReceiver + ?Sized), d: &mut TData, - s: &VisitSettings, + s: &VisitOptions, c: &mut Annex, ) { self.0.visit_mut(v, d, s, c); @@ -184,7 +190,7 @@ mod ast { &self, _visitors: &mut (impl VisitorReceiver + ?Sized), _data: &mut TData, - _options: &VisitSettings, + _options: &VisitOptions, _chain: &mut Annex, ) {} @@ -192,7 +198,7 @@ mod ast { &mut self, _visitors: &mut (impl VisitorMutReceiver + ?Sized), _data: &mut TData, - _options: &VisitSettings, + _options: &VisitOptions, _chain: &mut Annex, ) {} } @@ -200,9 +206,8 @@ mod ast { } } - // Create a bunch of blank implementations for data types that do not have + // A bunch of blank implementations for data types that do not have // any AST nested / aren't important for visiting. - // TODO propertyKey should be visited create_blank_visiting_implementations![ (), bool, @@ -243,16 +248,16 @@ mod ast { crate::Quoted, crate::declarations::ImportExportName, crate::declarations::ImportLocation, - crate::PropertyKey, - crate::PropertyKey + crate::functions::FunctionHeader, + crate::functions::MethodHeader ]; } -/// These are structures used when visiting AST +/// Data used when visiting AST mod structures { use crate::{ property_key::{AlwaysPublic, PublicOrPrivate}, - Declaration, Statement, VariableFieldInSourceCode, + Statement, VariableFieldInSourceCode, VariableIdentifier, }; use super::{ @@ -269,7 +274,7 @@ mod structures { Block(Span), } - /// The current location in the AST + /// Contains [`ChainVariable`]s which signal the position in the AST #[derive(Debug, Clone)] pub struct Chain(Vec); @@ -300,12 +305,6 @@ mod structures { self.0.truncate(to_size); } - /// TODO remove - #[must_use] - pub fn get_chain(&self) -> &[ChainVariable] { - &self.0 - } - #[must_use] pub fn get_module(&self) -> SourceId { if let ChainVariable::Module(source) = self.0.first().unwrap() { @@ -334,59 +333,72 @@ mod structures { } } - /// TODO these may go #[derive(Debug)] - pub enum MutableVariablePart<'a> { - VariableFieldName(&'a mut String), + pub enum ImmutableVariableOrProperty<'a> { + // TODO maybe WithComment on some of these + VariableFieldName(&'a str, &'a Span), // TODO these should maybe only be the spread variables - ArrayDestructuringMember(&'a mut ArrayDestructuringField), + ArrayDestructuringMember(&'a ArrayDestructuringField), ObjectDestructuringMember( - &'a mut WithComment>, + &'a WithComment>, ), - ClassName(Option<&'a mut String>), - FunctionName(&'a mut String), - ClassPropertyKey(&'a mut WithComment>), - ObjectPropertyKey(&'a mut WithComment>), + ClassName(Option<&'a VariableIdentifier>), + FunctionName(Option<&'a VariableIdentifier>), + ClassPropertyKey(&'a PropertyKey), + ObjectPropertyKey(&'a PropertyKey), } - /// TODO these may go #[derive(Debug)] - pub enum ImmutableVariableOrPropertyPart<'a> { - // TODO maybe WithComment on some of these - VariableFieldName(&'a str, &'a Span), + pub enum MutableVariableOrProperty<'a> { + VariableFieldName(&'a mut String), // TODO these should maybe only be the spread variables - ArrayDestructuringMember(&'a ArrayDestructuringField), + ArrayDestructuringMember(&'a mut ArrayDestructuringField), ObjectDestructuringMember( - &'a WithComment>, + &'a mut WithComment>, ), - ClassName(Option<&'a str>, &'a Span), - FunctionName(&'a str, &'a Span), - ClassPropertyKey(&'a WithComment>), - ObjectPropertyKey(&'a WithComment>), + ClassName(Option<&'a mut VariableIdentifier>), + FunctionName(Option<&'a mut VariableIdentifier>), + ClassPropertyKey(&'a mut PropertyKey), + ObjectPropertyKey(&'a mut PropertyKey), } - impl<'a> ImmutableVariableOrPropertyPart<'a> { + impl<'a> ImmutableVariableOrProperty<'a> { #[must_use] - pub fn get_name(&self) -> Option<&'a str> { + pub fn get_variable_name(&self) -> Option<&'a str> { match self { - ImmutableVariableOrPropertyPart::FunctionName(name, _) - | ImmutableVariableOrPropertyPart::VariableFieldName(name, _) => Some(name), - ImmutableVariableOrPropertyPart::ArrayDestructuringMember(_) - | ImmutableVariableOrPropertyPart::ObjectDestructuringMember(_) => None, - ImmutableVariableOrPropertyPart::ClassName(name, _) => *name, - ImmutableVariableOrPropertyPart::ObjectPropertyKey(property) => { - match property.get_ast_ref() { - PropertyKey::Ident(ident, _, ()) - | PropertyKey::StringLiteral(ident, _, _) => Some(ident.as_str()), - PropertyKey::NumberLiteral(_, _) | PropertyKey::Computed(_, _) => None, + ImmutableVariableOrProperty::VariableFieldName(name, _) => Some(name), + ImmutableVariableOrProperty::ArrayDestructuringMember(a) => match a { + ArrayDestructuringField::Spread(_, a) => Some(a.as_str()), + ArrayDestructuringField::Name(_, _) | ArrayDestructuringField::None => None, + }, + ImmutableVariableOrProperty::ObjectDestructuringMember(o) => { + match o.get_ast_ref() { + ObjectDestructuringField::Spread(name, _) + | ObjectDestructuringField::Name(name, _, _) => Some(name.as_str()), + ObjectDestructuringField::Map { .. } => None, } } - ImmutableVariableOrPropertyPart::ClassPropertyKey(property) => { - match property.get_ast_ref() { - PropertyKey::Ident(ident, _, _) - | PropertyKey::StringLiteral(ident, _, _) => Some(ident.as_str()), - PropertyKey::NumberLiteral(_, _) | PropertyKey::Computed(_, _) => None, - } + ImmutableVariableOrProperty::FunctionName(name) + | ImmutableVariableOrProperty::ClassName(name) => { + name.map(crate::variable_fields::VariableIdentifier::as_str) + } + ImmutableVariableOrProperty::ObjectPropertyKey(_property) => { + // Just want variable names + None + // match property.get_ast_ref() { + // PropertyKey::Ident(ident, _, _) + // | PropertyKey::StringLiteral(ident, _, _) => Some(ident.as_str()), + // PropertyKey::NumberLiteral(_, _) | PropertyKey::Computed(_, _) => None, + // } + } + ImmutableVariableOrProperty::ClassPropertyKey(_property) => { + // Just want variable names + None + // match property.get_ast_ref() { + // PropertyKey::Ident(ident, _, _) + // | PropertyKey::StringLiteral(ident, _, _) => Some(ident.as_str()), + // PropertyKey::NumberLiteral(_, _) | PropertyKey::Computed(_, _) => None, + // } } } } @@ -395,117 +407,79 @@ mod structures { pub fn get_position(&self) -> &Span { use crate::ASTNode; match self { - ImmutableVariableOrPropertyPart::FunctionName(_, pos) - | ImmutableVariableOrPropertyPart::ClassName(_, pos) - | ImmutableVariableOrPropertyPart::VariableFieldName(_, pos) => pos, - ImmutableVariableOrPropertyPart::ArrayDestructuringMember(member) => { - member.get_position() - } - ImmutableVariableOrPropertyPart::ObjectDestructuringMember(member) => { - member.get_position() - } - ImmutableVariableOrPropertyPart::ClassPropertyKey(property_key) => { - property_key.get_position() - } - ImmutableVariableOrPropertyPart::ObjectPropertyKey(property_key) => { - property_key.get_position() - } + ImmutableVariableOrProperty::FunctionName(pos) + | ImmutableVariableOrProperty::ClassName(pos) => match pos { + Some(p) => p.get_position(), + None => &Span::NULL_SPAN, + }, + ImmutableVariableOrProperty::VariableFieldName(_, pos) => pos, + ImmutableVariableOrProperty::ArrayDestructuringMember(m) => m.get_position(), + ImmutableVariableOrProperty::ObjectDestructuringMember(m) => m.get_position(), + ImmutableVariableOrProperty::ClassPropertyKey(k) => k.get_position(), + ImmutableVariableOrProperty::ObjectPropertyKey(k) => k.get_position(), } } } - pub enum StatementOrDeclarationRef<'a> { - Statement(&'a crate::Statement), - Declaration(&'a crate::Declaration), + /// Wrapper type for [`StatementOrDeclaration`]. Needed because [`crate::Statement`] doesn't + /// come under [`StatementOrDeclaration`] in the case of [`crate::BlockOrSingleStatement`] + pub enum BlockItem<'a> { + StatementOrDeclaration(&'a crate::StatementOrDeclaration), + SingleStatement(&'a crate::Statement), } - impl<'a> From<&'a StatementOrDeclaration> for StatementOrDeclarationRef<'a> { - fn from(value: &'a StatementOrDeclaration) -> Self { - match value { - StatementOrDeclaration::Statement(item) => { - StatementOrDeclarationRef::Statement(item) - } - StatementOrDeclaration::Declaration(item) => { - StatementOrDeclarationRef::Declaration(item) - } - } + impl<'a> From<&'a StatementOrDeclaration> for BlockItem<'a> { + fn from(item: &'a StatementOrDeclaration) -> Self { + BlockItem::StatementOrDeclaration(item) } } - impl<'a> From<&'a Statement> for StatementOrDeclarationRef<'a> { + impl<'a> From<&'a Statement> for BlockItem<'a> { fn from(item: &'a Statement) -> Self { - StatementOrDeclarationRef::Statement(item) + BlockItem::SingleStatement(item) } } - impl<'a> From<&'a Declaration> for StatementOrDeclarationRef<'a> { - fn from(item: &'a Declaration) -> Self { - StatementOrDeclarationRef::Declaration(item) - } + /// Wrapper type for [`StatementOrDeclaration`]. Needed because [`crate::Statement`] doesn't + /// come under [`StatementOrDeclaration`] in the case of [`crate::BlockOrSingleStatement`] + pub enum BlockItemMut<'a> { + StatementOrDeclaration(&'a mut crate::StatementOrDeclaration), + SingleStatement(&'a mut crate::Statement), } - pub enum StatementOrDeclarationMut<'a> { - Statement(&'a mut crate::Statement), - Declaration(&'a mut crate::Declaration), - } - - impl<'a> From<&'a mut StatementOrDeclaration> for StatementOrDeclarationMut<'a> { - fn from(value: &'a mut StatementOrDeclaration) -> Self { - match value { - StatementOrDeclaration::Statement(item) => { - StatementOrDeclarationMut::Statement(item) - } - StatementOrDeclaration::Declaration(item) => { - StatementOrDeclarationMut::Declaration(item) - } - } + impl<'a> From<&'a mut StatementOrDeclaration> for BlockItemMut<'a> { + fn from(item: &'a mut StatementOrDeclaration) -> Self { + BlockItemMut::StatementOrDeclaration(item) } } - impl<'a> From<&'a mut Statement> for StatementOrDeclarationMut<'a> { + impl<'a> From<&'a mut Statement> for BlockItemMut<'a> { fn from(item: &'a mut Statement) -> Self { - StatementOrDeclarationMut::Statement(item) - } - } - - impl<'a> From<&'a mut Declaration> for StatementOrDeclarationMut<'a> { - fn from(item: &'a mut Declaration) -> Self { - StatementOrDeclarationMut::Declaration(item) + BlockItemMut::SingleStatement(item) } } } mod visitors { - use super::{ - Chain, Expression, ImmutableVariableOrPropertyPart, JSXElement, SelfVisitable, - StatementOrDeclarationRef, - }; + use super::{BlockItem, Chain, Expression, ImmutableVariableOrProperty, SelfVisitable}; use crate::{block::BlockLike, TSXKeyword}; use source_map::Span; - /// A visitor over something which is hooked/is `SelfVisitable` with some Data + /// A visitor over something which is hooked/is [`SelfVisitable`] with some generic `Data` pub trait Visitor { fn visit(&mut self, item: &Item, data: &mut Data, chain: &Chain); } - /// These are a receiver traits of the visitor + /// Something which has a bunch of callbacks for AST #[allow(unused_variables)] pub trait VisitorReceiver { fn visit_expression(&mut self, expression: &Expression, data: &mut T, chain: &Chain) {} - fn visit_statement( - &mut self, - statement: StatementOrDeclarationRef, - data: &mut T, - chain: &Chain, - ) { - } - - fn visit_jsx_element(&mut self, element: &JSXElement, data: &mut T, chain: &Chain) {} + fn visit_statement(&mut self, statement: BlockItem, data: &mut T, chain: &Chain) {} fn visit_variable( &mut self, - variable: &ImmutableVariableOrPropertyPart, + variable: &ImmutableVariableOrProperty, data: &mut T, chain: &Chain, ) { @@ -521,24 +495,13 @@ mod visitors { self.expression_visitors.iter_mut().for_each(|vis| vis.visit(expression, data, chain)); } - fn visit_statement( - &mut self, - statement: StatementOrDeclarationRef, - data: &mut T, - chain: &Chain, - ) { + fn visit_statement(&mut self, statement: BlockItem, data: &mut T, chain: &Chain) { self.statement_visitors.iter_mut().for_each(|vis| vis.visit(&statement, data, chain)); } - fn visit_jsx_element(&mut self, jsx_element: &JSXElement, data: &mut T, chain: &Chain) { - self.jsx_element_visitors - .iter_mut() - .for_each(|vis| vis.visit(jsx_element, data, chain)); - } - fn visit_variable( &mut self, - variable: &ImmutableVariableOrPropertyPart, + variable: &ImmutableVariableOrProperty, data: &mut T, chain: &Chain, ) { @@ -554,14 +517,16 @@ mod visitors { } type ExpressionVisitor = Box>; - type StatementVisitor = Box Visitor, T>>; - type VariableVisitor = Box Visitor, T>>; + type StatementVisitor = Box Visitor, T>>; + type VariableVisitor = Box Visitor, T>>; type BlockVisitor = Box Visitor, T>>; + + /// A utility type which implements [`VisitorReceiver`]. Use for running a bunch of different **immutable** + /// visitors over a **immutable** AST. Used for simple analysis #[derive(Default)] pub struct Visitors { pub expression_visitors: Vec>, pub statement_visitors: Vec>, - pub jsx_element_visitors: Vec>>, pub variable_visitors: Vec>, pub block_visitors: Vec>, } @@ -602,12 +567,9 @@ mod visitors { mod visitors_mut { use crate::block::BlockLikeMut; - use super::{ - Chain, Expression, JSXElement, MutableVariablePart, SelfVisitableMut, - StatementOrDeclarationMut, - }; + use super::{BlockItemMut, Chain, Expression, MutableVariableOrProperty, SelfVisitableMut}; - /// A visitor over something which is hooked/is `SelfVisitable` with some Data + /// A visitor over something which is hooked/is [`SelfVisitableMut`] with some Data pub trait VisitorMut { fn visit_mut(&mut self, item: &mut Item, data: &mut Data, chain: &Chain); } @@ -623,20 +585,11 @@ mod visitors_mut { ) { } - fn visit_statement_mut( - &mut self, - statement: StatementOrDeclarationMut, - data: &mut T, - chain: &Chain, - ) { - } - - fn visit_jsx_element_mut(&mut self, element: &mut JSXElement, data: &mut T, chain: &Chain) { - } + fn visit_statement_mut(&mut self, statement: BlockItemMut, data: &mut T, chain: &Chain) {} fn visit_variable_mut( &mut self, - variable: &mut MutableVariablePart, + variable: &mut MutableVariableOrProperty, data: &mut T, chain: &Chain, ) { @@ -645,14 +598,15 @@ mod visitors_mut { fn visit_block_mut(&mut self, block: &mut BlockLikeMut, data: &mut T, chain: &Chain) {} } - type StatementVisitor = Box VisitorMut, T>>; - type VariableVisitor = Box VisitorMut, T>>; + type StatementVisitor = Box VisitorMut, T>>; + type VariableVisitor = Box VisitorMut, T>>; type BlockVisitor = Box VisitorMut, T>>; + /// A utility type which implements [`VisitorMutReceiver`]. Use for running a bunch of different **mutable** + /// visitors over a **mutable** AST. Therefore can remove, add or change AST pub struct VisitorsMut { pub expression_visitors_mut: Vec>>, pub statement_visitors_mut: Vec>, - pub jsx_element_visitors_mut: Vec>>, pub variable_visitors_mut: Vec>, pub block_visitors_mut: Vec>, } @@ -662,7 +616,6 @@ mod visitors_mut { Self { expression_visitors_mut: Default::default(), statement_visitors_mut: Default::default(), - jsx_element_visitors_mut: Default::default(), variable_visitors_mut: Default::default(), block_visitors_mut: Default::default(), } @@ -681,26 +634,15 @@ mod visitors_mut { .for_each(|vis| vis.visit_mut(expression, data, chain)); } - fn visit_statement_mut( - &mut self, - mut statement: StatementOrDeclarationMut, - data: &mut T, - chain: &Chain, - ) { + fn visit_statement_mut(&mut self, mut item: BlockItemMut, data: &mut T, chain: &Chain) { self.statement_visitors_mut .iter_mut() - .for_each(|vis| vis.visit_mut(&mut statement, data, chain)); - } - - fn visit_jsx_element_mut(&mut self, element: &mut JSXElement, data: &mut T, chain: &Chain) { - self.jsx_element_visitors_mut - .iter_mut() - .for_each(|vis| vis.visit_mut(element, data, chain)); + .for_each(|vis| vis.visit_mut(&mut item, data, chain)); } fn visit_variable_mut( &mut self, - variable: &mut MutableVariablePart, + variable: &mut MutableVariableOrProperty, data: &mut T, chain: &Chain, ) { diff --git a/parser/tests/statements.rs b/parser/tests/statements.rs index b5e3cdfb..3776aa53 100644 --- a/parser/tests/statements.rs +++ b/parser/tests/statements.rs @@ -227,18 +227,21 @@ from "module-name" import defaultExport, * as name; #[cfg(feature = "extras")] #[test] fn function_custom_headers() { - let input = r" + use ezno_parser::ParseOptions; + + let input = " function a() {} generator function a() {} generator server function a() {} generator server function a() {} async server function a() {} -module function a() {} +worker function a() {} " .trim(); let module = - Module::from_string(input.to_owned(), Default::default(), SourceId::NULL, None).unwrap(); + Module::from_string(input.to_owned(), ParseOptions::all_features(), SourceId::NULL, None) + .unwrap(); eprintln!("Module: {module:#?}"); diff --git a/parser/tests/visiting.rs b/parser/tests/visiting.rs index a09cae8d..d9ff5ace 100644 --- a/parser/tests/visiting.rs +++ b/parser/tests/visiting.rs @@ -1,7 +1,8 @@ use ezno_parser::{ statements::UnconditionalElseStatement, - visiting::{Chain, StatementOrDeclarationMut, VisitSettings, VisitorMut, VisitorsMut}, - ASTNode, Expression, Module, SourceId, Span, Statement, ToStringOptions, + visiting::{BlockItemMut, Chain, VisitOptions, VisitorMut, VisitorsMut}, + ASTNode, Expression, Module, SourceId, Span, Statement, StatementOrDeclaration, + ToStringOptions, }; use pretty_assertions::assert_eq; @@ -22,15 +23,14 @@ fn visiting() { let mut visitors = VisitorsMut { expression_visitors_mut: vec![Box::new(MakeStringsUppercase)], statement_visitors_mut: vec![Box::new(AddElseClause)], - jsx_element_visitors_mut: Default::default(), variable_visitors_mut: Default::default(), block_visitors_mut: Default::default(), }; - module.visit_mut(&mut visitors, &mut (), &VisitSettings::default()); + module.visit_mut(&mut visitors, &mut (), &VisitOptions::default()); let output = module.to_string(&ToStringOptions::minified()); - let expected = r#"const x="HELLO WORLD";function y(){if(condition){do_thing("HELLO WORLD"+" TEST")}else console.log("ELSE!")}"#; + let expected = r#"const x="HELLO WORLD";function y(){if(condition){do_thing("HELLO WORLD"+" TEST")}else console.log("ELSE!");}"#; assert_eq!(output, expected); } @@ -48,9 +48,13 @@ impl VisitorMut for MakeStringsUppercase { /// Add else cases to if statements without one. In the else statements, it logs "else!" struct AddElseClause; -impl VisitorMut, ()> for AddElseClause { - fn visit_mut(&mut self, item: &mut StatementOrDeclarationMut, _data: &mut (), _chain: &Chain) { - if let StatementOrDeclarationMut::Statement(Statement::IfStatement(if_statement)) = item { +impl VisitorMut, ()> for AddElseClause { + fn visit_mut(&mut self, item: &mut BlockItemMut, _data: &mut (), _chain: &Chain) { + if let BlockItemMut::SingleStatement(Statement::IfStatement(if_statement)) + | BlockItemMut::StatementOrDeclaration(StatementOrDeclaration::Statement( + Statement::IfStatement(if_statement), + )) = item + { if if_statement.trailing_else.is_none() { let inner = Statement::from_string( "console.log(\"else!\")".to_owned(), diff --git a/parser/visitable-derive/macro.rs b/parser/visitable-derive/macro.rs index d5c445c7..22d4503a 100644 --- a/parser/visitable-derive/macro.rs +++ b/parser/visitable-derive/macro.rs @@ -28,7 +28,7 @@ pub fn generate_visit_implementation(input: TokenStream) -> TokenStream { vec![ parse_quote!(visitors: &mut (impl crate::visiting::VisitorReceiver + ?Sized)), parse_quote!(data: &mut TData), - parse_quote!(options: &crate::VisitSettings), + parse_quote!(options: &crate::VisitOptions), parse_quote!(chain: &mut ::temporary_annex::Annex), ], None, @@ -42,7 +42,7 @@ pub fn generate_visit_implementation(input: TokenStream) -> TokenStream { vec![ parse_quote!(visitors: &mut (impl crate::visiting::VisitorMutReceiver + ?Sized)), parse_quote!(data: &mut TData), - parse_quote!(options: &crate::VisitSettings), + parse_quote!(options: &crate::VisitOptions), parse_quote!(chain: &mut ::temporary_annex::Annex), ], None, @@ -119,7 +119,13 @@ fn generated_visit_item( .flat_map(|mut field: NamedOrUnnamedFieldMut| -> Option { let attributes = field.get_attributes(); - let skip_field = attributes.iter().any(|attr| attr.path.is_ident(VISIT_SKIP_NAME)); + let skip_field_attr = + attributes.iter().find(|attr| attr.path.is_ident(VISIT_SKIP_NAME)); + + // TODO maybe? + // // None == unconditional + // let _skip_field_expression: Option = + // skip_field_attr.as_ref().map(|attr| attr.bracket_token); let visit_with_chain = attributes.iter().find_map(|attr| { attr.path.is_ident(VISIT_WITH_CHAIN_NAME).then_some(&attr.tokens) @@ -131,7 +137,7 @@ fn generated_visit_item( quote!(chain) }; - if !skip_field { + if skip_field_attr.is_none() { let reference = field.get_reference(); Some(match visit_type { VisitType::Immutable => parse_quote! { diff --git a/src/ast_explorer.rs b/src/ast_explorer.rs index 65dde5bc..113be279 100644 --- a/src/ast_explorer.rs +++ b/src/ast_explorer.rs @@ -176,12 +176,12 @@ impl ExplorerSubCommand { let res = Module::from_string(input, Default::default(), source_id, None); match res { Ok(module) => { - let settings = if matches!(self, ExplorerSubCommand::Prettifier(_)) { + let options = if matches!(self, ExplorerSubCommand::Prettifier(_)) { ToStringOptions { trailing_semicolon: true, ..Default::default() } } else { ToStringOptions::minified() }; - print_to_cli(format_args!("{}", module.to_string(&settings))); + print_to_cli(format_args!("{}", module.to_string(&options))); } Err(err) => emit_ezno_diagnostic((err, source_id).into(), &fs).unwrap(), } diff --git a/src/commands.rs b/src/build.rs similarity index 63% rename from src/commands.rs rename to src/build.rs index d07e314a..05463e89 100644 --- a/src/commands.rs +++ b/src/build.rs @@ -1,43 +1,13 @@ -use checker::{DiagnosticsContainer, PostCheckData}; +use std::path::{Path, PathBuf}; + +use checker::DiagnosticsContainer; use parser::{ source_map::{MapFileStore, WithPathMap}, - ASTNode, ParseOptions, ToStringOptions, + ToStringOptions, }; use serde::Deserialize; -use std::path::{Path, PathBuf}; - -pub type EznoCheckerData = PostCheckData; -pub fn check( - read_from_filesystem: &T, - input: &Path, - type_definition_module: Option<&Path>, -) -> (checker::DiagnosticsContainer, Result>) { - let definitions = if let Some(tdm) = type_definition_module { - std::iter::once(tdm.into()).collect() - } else { - std::iter::once(checker::INTERNAL_DEFINITION_FILE_PATH.into()).collect() - }; - - let read_from_fs = |path: &Path| { - if path == Path::new(checker::INTERNAL_DEFINITION_FILE_PATH) { - Some(checker::INTERNAL_DEFINITION_FILE.to_owned()) - } else { - read_from_filesystem.get_content_at_path(path) - } - }; - - let type_check_options = None; - let parsing_options = ParseOptions::default(); - - checker::check_project( - input.to_path_buf(), - definitions, - read_from_fs, - type_check_options, - parsing_options, - ) -} +use crate::check::EznoCheckerData; #[cfg_attr(target_family = "wasm", derive(serde::Serialize))] pub struct Output { @@ -81,8 +51,9 @@ pub fn build( config: &BuildConfig, transformers: Option, ) -> Result { - // TODO parse settings + non_standard_library & non_standard_syntax - let (diagnostics, data_and_module) = check(fs_resolver, input_path, type_definition_module); + // TODO parse options + non_standard_library & non_standard_syntax + let (diagnostics, data_and_module) = + crate::check(fs_resolver, input_path, type_definition_module); match data_and_module { Ok(mut data) => { @@ -99,7 +70,7 @@ pub fn build( module.visit_mut::( &mut transformers, &mut data, - &parser::visiting::VisitSettings::default(), + &parser::visiting::VisitOptions::default(), ); let to_string_options = if config.strip_whitespace { @@ -108,7 +79,7 @@ pub fn build( ToStringOptions::default() }; - let content = module.to_string(&to_string_options); + let content = parser::ASTNode::to_string(&module, &to_string_options); let main_output = Output { output_path: output_path.to_path_buf(), content, diff --git a/src/check.rs b/src/check.rs new file mode 100644 index 00000000..215e352c --- /dev/null +++ b/src/check.rs @@ -0,0 +1,29 @@ +use checker::PostCheckData; +use parser::source_map::{MapFileStore, WithPathMap}; +use std::{collections::HashSet, path::Path}; + +pub type EznoCheckerData = PostCheckData; + +pub fn check( + read_from_filesystem: &T, + input: &Path, + type_definition_module: Option<&Path>, +) -> (checker::DiagnosticsContainer, Result>) { + let definitions = if let Some(tdm) = type_definition_module { + HashSet::from_iter(std::iter::once(tdm.into())) + } else { + HashSet::from_iter(std::iter::once(checker::INTERNAL_DEFINITION_FILE_PATH.into())) + }; + + let read_from_fs = |path: &Path| { + if path == Path::new(checker::INTERNAL_DEFINITION_FILE_PATH) { + Some(checker::INTERNAL_DEFINITION_FILE.to_owned()) + } else { + read_from_filesystem.get_content_at_path(path) + } + }; + + let type_check_options = None; + + checker::check_project(input.to_path_buf(), definitions, read_from_fs, type_check_options) +} diff --git a/src/cli.rs b/src/cli.rs index 6ddac9da..638f1950 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -8,7 +8,9 @@ use std::{ }; use crate::{ - commands::{BuildOutput, FailedBuildOutput}, + build::{build, BuildOutput, FailedBuildOutput}, + build::{BuildConfig, EznoParsePostCheckVisitors}, + check::check, error_handling::emit_ezno_diagnostic, utilities::print_to_cli, }; @@ -25,9 +27,9 @@ struct TopLevel { #[argh(subcommand)] enum CompilerSubCommand { Info(Info), - Build(BuildArguments), ASTExplorer(crate::ast_explorer::ExplorerArguments), Check(CheckArguments), + Experimental(ExperimentalArguments), // Run(RunArguments), #[cfg(not(target_family = "wasm"))] Repl(crate::repl::ReplArguments), @@ -40,20 +42,22 @@ enum CompilerSubCommand { #[argh(subcommand, name = "info")] struct Info {} -// /// Generates binary form of a type definition module -// #[derive(FromArgs, Debug)] -// #[argh(subcommand, name = "pack")] -// struct Pack { -// /// path to module -// #[argh(positional)] -// input: PathBuf, -// /// output path -// #[argh(positional)] -// output: PathBuf, -// } +/// Experimental Ezno features +#[derive(FromArgs, Debug)] +#[argh(subcommand, name = "experimental")] +pub(crate) struct ExperimentalArguments { + #[argh(subcommand)] + nested: ExperimentalSubcommand, +} + +#[derive(FromArgs, Debug)] +#[argh(subcommand)] +pub(crate) enum ExperimentalSubcommand { + Build(BuildArguments), +} -// TODO definition file as list /// Build project +/// TODO definition file as list #[derive(FromArgs, PartialEq, Debug)] #[argh(subcommand, name = "build")] // TODO: Can be refactored with bit to reduce memory @@ -160,16 +164,47 @@ pub fn run_cli { crate::utilities::print_info(); } - CompilerSubCommand::Build(build_config) => { + CompilerSubCommand::Check(check_arguments) => { + let CheckArguments { input, watch: _, definition_file } = check_arguments; + let (diagnostics, _others) = check(read_file, &input, definition_file.as_deref()); + + let fs = match _others { + Ok(data) => data.module_contents, + Err(data) => data, + }; + if !diagnostics.has_error() { + print_to_cli(format_args!("No type errors found 🎉")) + } + for diagnostic in diagnostics.into_iter() { + emit_ezno_diagnostic(diagnostic, &fs).unwrap(); + } + } + CompilerSubCommand::Experimental(ExperimentalArguments { + nested: ExperimentalSubcommand::Build(build_config), + }) => { let output_path = build_config.output.unwrap_or("ezno_output.js".into()); - let output = crate::commands::build( + + // TODO + let default_builders = EznoParsePostCheckVisitors { + expression_visitors_mut: vec![Box::new( + crate::transformers::optimisations::ExpressionOptimiser, + )], + statement_visitors_mut: vec![Box::new( + crate::transformers::optimisations::StatementOptimiser, + )], + variable_visitors_mut: Default::default(), + block_visitors_mut: Default::default(), + }; + + let output = build( read_file, &build_config.input, build_config.definition_file.as_deref(), &output_path, - &crate::commands::BuildConfig { strip_whitespace: build_config.minify }, - None, + &BuildConfig { strip_whitespace: build_config.minify }, + Some(default_builders), ); + match output { Ok(BuildOutput { diagnostics, fs, outputs }) => { for output in outputs { @@ -178,6 +213,8 @@ pub fn run_cli { for diagnostic in diagnostics { @@ -187,19 +224,6 @@ pub fn run_cli repl.run(read_file, cli_input_resolver), - CompilerSubCommand::Check(check_arguments) => { - let CheckArguments { input, watch: _, definition_file } = check_arguments; - let (diagnostics, others) = - crate::commands::check(read_file, &input, definition_file.as_deref()); - - let fs = match others { - Ok(data) => data.module_contents, - Err(data) => data, - }; - for diagnostic in diagnostics { - emit_ezno_diagnostic(diagnostic, &fs).unwrap(); - } - } #[cfg(not(target_family = "wasm"))] CompilerSubCommand::Repl(argument) => crate::repl::run_deno_repl(cli_input_resolver, argument), // CompilerSubCommand::Run(run_arguments) => { diff --git a/src/js-based-plugin/index.mjs b/src/js-based-plugin/index.mjs index 0f496a56..962aacfb 100644 --- a/src/js-based-plugin/index.mjs +++ b/src/js-based-plugin/index.mjs @@ -1,5 +1,5 @@ import { createUnplugin } from "unplugin"; -import { build as ezno_build, just_imports } from "ezno/initialised"; +import { experimental_build as ezno_build, just_imports } from "ezno/initialised"; import { readFileSync } from "node:fs"; /// diff --git a/src/js-cli-and-library/src/index.mjs b/src/js-cli-and-library/src/index.mjs index a77d24a1..aae6b8e8 100644 --- a/src/js-cli-and-library/src/index.mjs +++ b/src/js-cli-and-library/src/index.mjs @@ -1,2 +1,2 @@ -import init, { initSync, build, check, parse_expression, parse_module, just_imports, minify_module, get_version } from "../build/ezno_lib.js"; -export { init, initSync, build, check, parse_expression, parse_module, just_imports, minify_module, get_version }; \ No newline at end of file +import init, { initSync, experimental_build, check, parse_expression, parse_module, just_imports, minify_module, get_version } from "../build/ezno_lib.js"; +export { init, initSync, experimental_build, check, parse_expression, parse_module, just_imports, minify_module, get_version }; \ No newline at end of file diff --git a/src/js-cli-and-library/src/initialised.js b/src/js-cli-and-library/src/initialised.js index a4631dc5..3292d4b5 100644 --- a/src/js-cli-and-library/src/initialised.js +++ b/src/js-cli-and-library/src/initialised.js @@ -1,4 +1,4 @@ -import { initSync, build, check, parse_expression, parse_module, just_imports, minify_module, get_version } from "./index.mjs"; +import { initSync, experimental_build, check, parse_expression, parse_module, just_imports, minify_module, get_version } from "./index.mjs"; import { readFileSync } from "node:fs"; const wasmPath = new URL("./shared/ezno_lib_bg.wasm", import.meta.url); @@ -8,4 +8,4 @@ if (wasmPath.protocol === "https:") { initSync(readFileSync(wasmPath)); } -export { build, check, parse_expression, parse_module, just_imports, minify_module, get_version } \ No newline at end of file +export { experimental_build, check, parse_expression, parse_module, just_imports, minify_module, get_version } \ No newline at end of file diff --git a/src/js-cli-and-library/test.mjs b/src/js-cli-and-library/test.mjs index 359644d6..68e201bd 100644 --- a/src/js-cli-and-library/test.mjs +++ b/src/js-cli-and-library/test.mjs @@ -1,5 +1,5 @@ -import { build, check, parse_expression, get_version } from "./dist/initialised.mjs"; -import assert, { deepStrictEqual, equal } from "node:assert"; +import { check, parse_expression, get_version } from "./dist/initialised.mjs"; +import assert, { deepStrictEqual } from "node:assert"; function buildTest() { // TODO @@ -12,20 +12,19 @@ function checkTest() { const output = check("input.js", (_path) => example); deepStrictEqual(output, [ { - type: "PositionWithAdditionLabels", reason: "Type 2 is not assignable to type 4", - position: { start: 13, end: 14, source: 1 }, + position: { start: 13, end: 14, source: 2 }, labels: [ [ "Variable declared with type 4", { end: 10, - source: 1, + source: 2, start: 9, }, ], ], - kind: { type: "Error" } + kind: "Error" }, ], ); diff --git a/src/lib.rs b/src/lib.rs index 78d8c496..72133530 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ mod ast_explorer; -mod commands; +mod build; +mod check; mod error_handling; mod repl; @@ -8,8 +9,10 @@ pub(crate) mod utilities; pub mod cli; pub mod transformers; +pub use build::build; +pub use check::check; pub use checker::{Diagnostic, DiagnosticKind}; -pub use commands::{build, check}; + pub use parser::{source_map, ASTNode, ToStringOptions}; use parser::{Module, ParseError}; @@ -48,7 +51,7 @@ impl WriteToFS for T where T: Fn(&std::path::Path, String) {} mod wasm_bindings; #[cfg(target_family = "wasm")] -pub use wasm_bindings::build_wasm; +pub use wasm_bindings::experimental_build_wasm; #[cfg(target_family = "wasm")] pub use wasm_bindings::run_cli_wasm; diff --git a/src/main.rs b/src/main.rs index 598da795..1752f675 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,24 +18,24 @@ use ezno_lib::cli::run_cli; use std::io; #[cfg(target_family = "windows")] -pub(crate) fn cli_input_resolver(prompt: &str) -> Option { - print!("{}> ", prompt); +pub(crate) fn cli_input_resolver(prompt: &str) -> String { + print!("{prompt}> "); io::Write::flush(&mut io::stdout()).unwrap(); let mut input = String::new(); let std_in = &mut io::stdin(); let _n = multiline_term_input::read_string(std_in, &mut input); - Some(input) + input } #[cfg(target_family = "unix")] #[allow(clippy::unnecessary_wraps)] -pub(crate) fn cli_input_resolver(prompt: &str) -> Option { +pub(crate) fn cli_input_resolver(prompt: &str) -> String { print!("{prompt}> "); io::Write::flush(&mut io::stdout()).unwrap(); let mut input = String::new(); let std_in = &mut io::stdin(); let _n = std_in.read_line(&mut input).unwrap(); - Some(input) + input } fn main() { @@ -50,5 +50,5 @@ fn main() { let arguments = std::env::args().skip(1).collect::>(); let arguments = arguments.iter().map(String::as_str).collect::>(); - run_cli(&arguments, &read_from_file, write_to_file, cli_input_resolver); + run_cli(&arguments, &read_from_file, write_to_file, |p| Some(cli_input_resolver(p))); } diff --git a/src/repl.rs b/src/repl.rs index a1d32d91..d848c7c8 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -99,7 +99,7 @@ pub(crate) fn run_deno_repl( let offset = Some(from_index as u32); let result = if input.trim_start().starts_with('{') { Expression::from_string(input, options, source, offset).map(|expression| Module { - span: expression.get_position().clone(), + span: *expression.get_position(), items: vec![Statement::Expression(expression.into()).into()], source, }) diff --git a/src/transformers/mod.rs b/src/transformers/mod.rs index f3a57971..655d4412 100644 --- a/src/transformers/mod.rs +++ b/src/transformers/mod.rs @@ -1,15 +1,20 @@ -use parser::{visiting::StatementOrDeclarationMut, Declaration, Module, StatementOrDeclaration}; +pub mod optimisations; + +use parser::{visiting::BlockItemMut, Declaration, Module, StatementOrDeclaration}; pub struct ConstToLet; -impl parser::visiting::VisitorMut, ()> for ConstToLet { +impl parser::visiting::VisitorMut, ()> for ConstToLet { fn visit_mut( &mut self, - item: &mut StatementOrDeclarationMut, + item: &mut BlockItemMut, _data: &mut (), _chain: &parser::visiting::Chain, ) { - if let StatementOrDeclarationMut::Declaration(Declaration::Variable(decl)) = item { + if let BlockItemMut::StatementOrDeclaration(StatementOrDeclaration::Declaration( + Declaration::Variable(decl), + )) = item + { if let parser::declarations::VariableDeclaration::ConstDeclaration { keyword, declarations, @@ -17,7 +22,7 @@ impl parser::visiting::VisitorMut, ()> for ConstTo } = decl { *decl = parser::declarations::VariableDeclaration::LetDeclaration { - keyword: parser::Keyword::new(keyword.get_position().clone()), + keyword: parser::Keyword::new(*keyword.get_position()), declarations: declarations .drain(..) .map(|dec| parser::declarations::VariableDeclarationItem { @@ -27,7 +32,7 @@ impl parser::visiting::VisitorMut, ()> for ConstTo position: dec.position, }) .collect(), - position: position.clone(), + position: *position, }; } } diff --git a/src/transformers/optimisations.rs b/src/transformers/optimisations.rs new file mode 100644 index 00000000..9a4b07bd --- /dev/null +++ b/src/transformers/optimisations.rs @@ -0,0 +1,156 @@ +use std::collections::HashSet; + +use checker::{synthesis::EznoParser, FunctionId, PostCheckData}; +use parser::{ + declarations::{ + classes::{ClassMember, ClassProperty}, + ClassDeclaration, + }, + expressions::object_literal::ObjectLiteralMember, + visiting::{BlockItemMut, VisitorMut}, + ASTNode, Expression, ExpressionOrStatementPosition, SourceId, StatementOrDeclaration, +}; + +/// A transformer that optimises expression code +/// - Removes dead functions +/// +/// TODO this can still break somethings if functions are used but not called +pub struct ExpressionOptimiser; + +impl VisitorMut> for ExpressionOptimiser { + fn visit_mut( + &mut self, + item: &mut Expression, + data: &mut PostCheckData, + chain: &parser::visiting::Chain, + ) { + match item { + Expression::ObjectLiteral(literal) => { + // TODO properties and even entire object + for item in literal.members.iter_mut() { + let item = item.get_ast_mut(); + if let ObjectLiteralMember::Method(method) = item { + let current_module = chain.get_module(); + let position = *method.get_position(); + let function_id = FunctionId(current_module, position.start); + if !data.type_mappings.called_functions.contains(&function_id) { + // Make it null for now to not break `Object.keys` + let name = method.name.clone(); + *item = ObjectLiteralMember::Property( + name, + Expression::Null(position), + position, + ); + } + } + } + } + Expression::ArrowFunction(func) => { + if !data + .type_mappings + .called_functions + .contains(&FunctionId(chain.get_module(), func.get_position().start)) + { + *item = Expression::Null(*func.get_position()); + } + } + Expression::ExpressionFunction(func) => { + if !data + .type_mappings + .called_functions + .contains(&FunctionId(chain.get_module(), func.get_position().start)) + { + *item = Expression::Null(*func.get_position()); + } + } + Expression::ClassExpression(cls) => { + shake_class(cls, &data.type_mappings.called_functions, chain.get_module()); + } + _ => {} + } + } +} + +/// A transformer that optimises statement code +/// - Removes dead functions +pub struct StatementOptimiser; + +impl VisitorMut, PostCheckData> for StatementOptimiser { + fn visit_mut( + &mut self, + item: &mut BlockItemMut, + data: &mut PostCheckData, + chain: &parser::visiting::Chain, + ) { + if let BlockItemMut::StatementOrDeclaration(StatementOrDeclaration::Declaration( + declaration, + )) = item + { + match declaration { + parser::Declaration::Variable(_) => { + // TODO remove if never read + } + parser::Declaration::Function(func) => { + if !data + .type_mappings + .called_functions + .contains(&FunctionId(chain.get_module(), func.get_position().start)) + { + // Replace with property to not break Object.keys for now + // TODO replacing this with variable isn't great but is the unfortunate design of `StatementOrDeclarationMut` + *declaration = parser::Declaration::Variable( + parser::declarations::VariableDeclaration::LetDeclaration { + keyword: parser::Keyword::new(parser::Span::NULL_SPAN), + declarations: Vec::new(), + position: *func.get_position(), + }, + ) + } + } + parser::Declaration::Class(cls) => { + shake_class( + &mut cls.on, + &data.type_mappings.called_functions, + chain.get_module(), + ); + } + parser::Declaration::Import(_) => { + // TODO imported items + } + parser::Declaration::Enum(_) + | parser::Declaration::Interface(_) + | parser::Declaration::TypeAlias(_) + | parser::Declaration::DeclareVariable(_) + | parser::Declaration::DeclareFunction(_) + | parser::Declaration::DeclareInterface(_) + | parser::Declaration::Export(_) => {} + } + } + } +} + +/// TODO properties and even entire class +fn shake_class( + class: &mut ClassDeclaration, + called_functions: &HashSet, + source: SourceId, +) { + for item in class.members.iter_mut() { + if let ClassMember::Method(static_kw, func) = &item.on { + let id = FunctionId(source, func.position.start); + if !called_functions.contains(&id) { + // Replace with property to not break Object.keys for now + item.on = ClassMember::Property( + static_kw.clone(), + ClassProperty { + readonly_keyword: None, + key: func.name.clone(), + type_annotation: None, + value: Some(Box::new(Expression::Null(func.position))), + position: func.position, + }, + ); + } + } + } +} diff --git a/src/wasm_bindings.rs b/src/wasm_bindings.rs index 7b4d5dfd..186ea521 100644 --- a/src/wasm_bindings.rs +++ b/src/wasm_bindings.rs @@ -7,8 +7,12 @@ extern "C" { pub(crate) fn log(s: &str); } -#[wasm_bindgen(js_name = build)] -pub fn build_wasm(entry_path: String, fs_resolver_js: &js_sys::Function, minify: bool) -> JsValue { +#[wasm_bindgen(js_name = experimental_build)] +pub fn experimental_build_wasm( + entry_path: String, + fs_resolver_js: &js_sys::Function, + minify: bool, +) -> JsValue { std::panic::set_hook(Box::new(console_error_panic_hook::hook)); let fs_resolver = |path: &std::path::Path| { @@ -16,14 +20,15 @@ pub fn build_wasm(entry_path: String, fs_resolver_js: &js_sys::Function, minify: fs_resolver_js.call1(&JsValue::null(), &JsValue::from(path.display().to_string())); res.ok().and_then(|res| res.as_string()) }; - let result = crate::commands::build( + let result = crate::build::build( &fs_resolver, Path::new(&entry_path), None, Path::new("out.js"), - &crate::commands::BuildConfig { strip_whitespace: minify }, + &crate::build::BuildConfig { strip_whitespace: minify }, None, ); + serde_wasm_bindgen::to_value(&result).unwrap() } @@ -36,7 +41,7 @@ pub fn check_wasm(entry_path: String, fs_resolver_js: &js_sys::Function) -> JsVa fs_resolver_js.call1(&JsValue::null(), &JsValue::from(path.display().to_string())); res.ok().and_then(|res| res.as_string()) }; - let (diagnostics, _) = crate::commands::check(&fs_resolver, Path::new(&entry_path), None); + let (diagnostics, _) = crate::check::check(&fs_resolver, Path::new(&entry_path), None); // TODO also emit mappings serde_wasm_bindgen::to_value(&diagnostics).unwrap() } @@ -44,8 +49,8 @@ pub fn check_wasm(entry_path: String, fs_resolver_js: &js_sys::Function) -> JsVa #[wasm_bindgen(js_name = run_cli)] pub fn run_cli_wasm( cli_arguments: Box<[JsValue]>, - read_file: &js_sys::Function, - write_file: &js_sys::Function, + read_from_file: &js_sys::Function, + write_to_file: &js_sys::Function, cli_input_resolver_js: &js_sys::Function, ) { std::panic::set_hook(Box::new(console_error_panic_hook::hook)); @@ -53,13 +58,14 @@ pub fn run_cli_wasm( let arguments = cli_arguments.into_iter().flat_map(JsValue::as_string).collect::>(); let arguments = arguments.iter().map(String::as_str).collect::>(); - let read_file = |path: &std::path::Path| { - let res = read_file.call1(&JsValue::null(), &JsValue::from(path.display().to_string())); + let read_from_file = |path: &std::path::Path| { + let res = + read_from_file.call1(&JsValue::null(), &JsValue::from(path.display().to_string())); res.ok().and_then(|res| res.as_string()) }; - let write_file = |path: &std::path::Path, content: String| { - write_file + let write_to_file = |path: &std::path::Path, content: String| { + write_to_file .call2( &JsValue::null(), &JsValue::from(path.display().to_string()), @@ -76,7 +82,7 @@ pub fn run_cli_wasm( .and_then(JsValue::as_string) }; - crate::run_cli(&arguments, &read_file, write_file, cli_input_resolver) + crate::run_cli(&arguments, &read_from_file, write_to_file, cli_input_resolver); } #[wasm_bindgen(js_name = parse_expression)]