diff --git a/.github/workflows/performance-and-size.yml b/.github/workflows/performance-and-size.yml
index 9ce0f5b3..2b88e15d 100644
--- a/.github/workflows/performance-and-size.yml
+++ b/.github/workflows/performance-and-size.yml
@@ -29,18 +29,6 @@ jobs:
with:
packages: hyperfine
- # For displaying line count in file
- - name: Download scc
- run: |
- mkdir scc
- cd scc
- gh release download v3.1.0 -R boyter/scc -p '*Linux_x86_64.tar.gz' -O scc.tar.gz
- tar -xf scc.tar.gz
- chmod +x scc
- pwd >> $GITHUB_PATH
- env:
- GH_TOKEN: ${{ github.token }}
-
- name: Build Ezno
run: cargo build --release
env:
@@ -52,8 +40,6 @@ jobs:
# Generate a file which contains everything that Ezno currently implements
cargo run -p ezno-parser --example code_blocks_to_script ./checker/specification/specification.md --comment-headers --out ./demo.tsx
- LINES_OF_CODE=$(scc -c --no-cocomo -f json demo.tsx | jq ".[0].Code")
-
echo "### Checking
\`\`\`shell
$(hyperfine -i './target/release/ezno check demo.tsx')
@@ -62,10 +48,8 @@ jobs:
echo "
Input
+ > Code generated from specification.md. this is not meant to accurately represent a program but instead give an idea for how it scales across all the type checking features
\`\`\`tsx
- // $LINES_OF_CODE lines of TypeScript generated from specification.md
- // this is not meant to accurately represent a program but instead give an idea
- // for how it scales across all the type checking features
$(cat ./demo.tsx)
\`\`\`
@@ -73,22 +57,65 @@ jobs:
echo "::info::Wrote code to summary"
+ command_output=$(./target/release/ezno check demo.tsx --timings --max-diagnostics all 2>&1 || true)
+ diagnostics=""; statistics=""; found_splitter=false;
+
+ while IFS= read -r line; do
+ if [[ "$line" == "---"* ]]; then found_splitter=true;
+ elif [[ "$found_splitter" == false ]]; then diagnostics+="$line"$'\n';
+ else statistics+="$line"$'\n'; fi
+ done <<< "$command_output"
+
echo "
Diagnostics
-
+
\`\`\`
- $(./target/release/ezno check demo.tsx --timings --max-diagnostics all 2>&1 || true)
+ $diagnostics
+ \`\`\`
+
+
+
+ Statistics
+
+ \`\`\`
+ $statistics
\`\`\`
" >> $GITHUB_STEP_SUMMARY
+ - name: Run checker performance w/staging
+ shell: bash
+ if: github.ref_name != 'main'
+ run: |
+ echo "::group::Running all"
+
+ cat ./checker/specification/specification.md ./checker/specification/staging.md > all.md
+ cargo run -p ezno-parser --example code_blocks_to_script all.md --comment-headers --out ./all.tsx
+
+ ./target/release/ezno check all.tsx --timings || true
+ echo "::endgroup::"
+
+ - name: Run checker performance on large file
+ shell: bash
+ run: |
+ echo "::group::Running large"
+
+ cat ./checker/specification/specification.md > main.md
+ cargo run -p ezno-parser --example code_blocks_to_script all.md --comment-headers --out ./code.tsx
+ for i in {1..10}; do
+ cat ./code.tsx >> large.tsx
+ done
+
+ ./target/release/ezno check large.tsx --timings --max-diagnostics 0 || true
+ echo "::endgroup::"
+
- name: Run parser, minfier/stringer performance
shell: bash
run: |
strings=(
"https://esm.sh/v128/react-dom@18.2.0/es2022/react-dom.mjs"
- "https://esm.sh/v135/typescript@5.3.3/es2022/typescript.mjs"
)
+ # Currently broken "https://esm.sh/v135/typescript@5.3.3/es2022/typescript.mjs"
for url in "${strings[@]}"; do
curl -sS $url > input.js
diff --git a/.gitignore b/.gitignore
index 4ba56a16..0dcdeb02 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,4 @@ node_modules
/TODO.txt
checker/specification/Cargo.lock
checker/definitions/es5.d.ts
+.grit
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 56803504..baddb3f1 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -25,7 +25,7 @@ Now in the `ezno` directory, `cargo run` should show the CLI.
You can run just the checker with
```shell
-cargo run -p ezno-checker --example run-checker path/to/file.ts
+cargo run -p ezno-checker --example run_checker path/to/file.ts
```
> [!TIP]
diff --git a/Cargo.lock b/Cargo.lock
index 38bb9718..c11fef7b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -308,6 +308,7 @@ dependencies = [
"ezno-parser",
"iterator-endiate",
"levenshtein",
+ "match_deref",
"ordered-float",
"path-absolutize",
"serde",
diff --git a/Cargo.toml b/Cargo.toml
index bec01db9..7e2350c8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -79,16 +79,20 @@ js-sys = "0.3"
tsify = "0.4.5"
[workspace.lints.clippy]
-all = "deny"
-pedantic = "deny"
+all = { level = "deny", priority = -1 }
+pedantic = { level = "deny", priority = -1 }
cast_precision_loss = "warn"
cast_possible_truncation = "warn"
cast_sign_loss = "warn"
default_trait_access = "allow"
missing_errors_doc = "allow"
missing_panics_doc = "allow"
-implicit_hasher = "allow"
module_name_repetitions = "allow"
too_many_lines = "allow"
new_without_default = "allow"
result_unit_err = "allow"
+thread_local_initializer_can_be_made_const = "allow"
+implicit_hasher = "allow"
+
+[profile.dev]
+debug = false
diff --git a/README.md b/README.md
index 32b4c50b..1a174c96 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
A JavaScript compiler and TypeScript checker written in Rust with a focus on static analysis and runtime performance.
> [!IMPORTANT]
-> Ezno is in active development and **does not currently support enough features to check existing projects**. Check out the [getting started guide](./checker/documentation/getting-started.md) for experimenting with what it [currently supports](./checker/specification/specification.md).
+> Ezno is in active development and **does not currently support enough features to check existing projects** (see [blocking issues](https://github.com/kaleidawave/ezno/labels/blocking)). Check out the [getting started guide](./checker/documentation/getting-started.md) for experimenting with what it [currently supports](./checker/specification/specification.md).
@@ -21,7 +21,7 @@ What Ezno is not
- Be on parity with TSC or 1:1, it has some different behaviors **but** should work in existing projects using TSC
- Faster as a means to serve large codebases. Cut out bloat and complex code first!
- Smarter as a means to allow more *dynamic patterns*. Keep things simple!
-- A binary executable compiler. It takes in JavaScript (or a TypeScript or Ezno superset) and does similar processes to traditional compilers, but at the end emits JavaScript. However, in the future, it _could_ generate a lower level format using its event (side-effect) representation.
+- A binary executable compiler. It takes in JavaScript (or a TypeScript or Ezno superset) and does similar processes to traditional compilers, but at the end emits JavaScript. However, in the future, it *could* generate a lower level format using its event (side-effect) representation.
Read more about Ezno (in chronological order)
- [Introducing Ezno](https://kaleidawave.github.io/posts/introducing-ezno/)
@@ -40,7 +40,6 @@ This project is a workspace consisting of a few crates:
-
## Help contribute
Check out [good first issues]((https://github.com/kaleidawave/ezno/issues?q=is%3Aopen+label%3Agood-first-issue%2Cfeedback-needed)) and comment on discussions! Feel free to ask questions on parts of the code of the checking implementation.
diff --git a/checker/Cargo.toml b/checker/Cargo.toml
index 87e52760..6800f69d 100644
--- a/checker/Cargo.toml
+++ b/checker/Cargo.toml
@@ -48,10 +48,12 @@ tsify = "0.4.5"
# TODO needs unfixed change in source-map
wasm-bindgen = "=0.2.89"
-
[dependencies.parser]
path = "../parser"
optional = true
version = "0.1.5"
features = ["extras"]
package = "ezno-parser"
+
+[dev-dependencies]
+match_deref = "0.1.1"
diff --git a/checker/definitions/internal.ts.d.bin b/checker/definitions/internal.ts.d.bin
index 8cfb2cd1..69dd2cab 100644
Binary files a/checker/definitions/internal.ts.d.bin and b/checker/definitions/internal.ts.d.bin differ
diff --git a/checker/definitions/overrides.d.ts b/checker/definitions/overrides.d.ts
index 83d4736d..abb1be71 100644
--- a/checker/definitions/overrides.d.ts
+++ b/checker/definitions/overrides.d.ts
@@ -1,10 +1,7 @@
-@Constant
-declare function debug_type_independent(t: any): void;
-
// Eventually this will be merged with existing TS es5.d.ts files but for now is the standalone see #121
interface ImportEnv {
- [key: string]: string | undefined;
+ [key: string]: string;
}
interface ImportMeta {
@@ -14,7 +11,7 @@ interface ImportMeta {
}
declare class Array {
- [index: number]: T | undefined;
+ [index: number]: T;
length: number;
@@ -33,8 +30,7 @@ declare class Array {
return undefined
} else {
const value = this[--this.length];
- // TODO this currently breaks value?
- // delete this[this.length];
+ delete this[this.length];
return value
}
}
@@ -101,35 +97,6 @@ declare class Array {
return false
}
- // fill(value: T, start: number = 0, end = this.length): this {
- // // TODO
- // return this
- // }
-
- // reduce(cb: (acc: U, t: T, i?: number) => U, initial?: U): U {
- // const { length } = this;
- // let acc = initial ?? this[0];
- // let i: number = typeof initial === "undefined" ? 1 : 0;;
- // while (i < length) {
- // const value = this[i];
- // acc = cb(acc, value, i++);
- // }
- // return acc;
- // }
-
- // includes(searchElement: T, fromIndex?: number): boolean {
- // const { length } = this;
- // // TODO this is currently broken
- // let i: number = fromIndex ?? 0;
- // while (i < length) {
- // const value = this[i++];
- // if (value === searchElement) {
- // return true
- // }
- // }
- // return false
- // }
-
join(joiner: string = ","): string {
const { length } = this;
let i: number = 1;
@@ -173,22 +140,28 @@ declare class Math {
static sqrt(x: number): number;
@Constant
static cbrt(x: number): number;
+ @Constant
+ static log(x: number): number;
// TODO newer method
@Constant
static trunc(x: number): number;
static PI: 3.141592653589793
+ static E: 2.718281828459045
+
+ @InputOutput
+ static random(): number;
}
@Primitive("string")
declare class String {
- [index: number]: string | undefined;
+ [index: number]: string;
@Constant
- toUpperCase(): string;
+ toUpperCase(this: string): string;
@Constant
- toLowerCase(): string;
+ toLowerCase(this: string): string;
get length(): number;
@@ -294,11 +267,11 @@ declare class SyntaxError extends Error {
declare class JSON {
// TODO any temp
- @Constant("json:parse", SyntaxError)
+ @Constant("JSON:parse", SyntaxError)
static parse(input: string): any;
// TODO any temp
- @Constant("json:stringify")
+ @Constant("JSON:stringify")
static stringify(input: any): string;
}
@@ -308,7 +281,7 @@ declare class Function {
declare class Symbols {
// TODO temp
- iterator: 199
+ static iterator: unique symbol "iterator"
}
declare class Proxy {
@@ -316,6 +289,18 @@ declare class Proxy {
constructor(obj: any, cb: any);
}
+// Copied from `es5.d.ts`. Could this be an or
+// TODO string keys temp because parser broke
+interface PropertyDescriptor {
+ value?: any;
+ ["get" ? (): any;
+ ["set" ? (v: any): void;
+
+ writable?: boolean;
+ configurable?: boolean;
+ enumerable?: boolean;
+}
+
declare class Object {
@Constant
static setPrototypeOf(on: object, to: object): object;
@@ -323,11 +308,19 @@ declare class Object {
@Constant
static getPrototypeOf(on: object): object | null;
- // static create(prototype: object): object {
- // const n = {};
- // Object.setProtoTypeOf(n, prototype);
- // return n
- // }
+ @Constant
+ static freeze(on: object): object;
+
+ @Constant
+ static isFrozen(on: object): boolean;
+
+ // TODO defineProperties via body (not constant)
+ @Constant
+ static defineProperty(on: object, property: string, discriminator: PropertyDescriptor): boolean;
+
+ // TODO getOwnPropertyDescriptors via body (not constant)
+ @Constant
+ static getOwnPropertyDescriptor(on: object, property: string): PropertyDescriptor;
static keys(on: { [s: string]: any }): Array {
const keys: Array = [];
@@ -353,6 +346,14 @@ declare class Object {
return entries
}
+ // TODO multiple arguments
+ static assign(target: object, source: object): object {
+ for (const key in source) {
+ target[key] = source[key]
+ }
+ return target
+ }
+
// static fromEntries(iterator: any): object {
// const obj = {};
// for (const item of iterator) {
@@ -415,6 +416,8 @@ declare function debug_context(): void;
declare function context_id(): void;
@Constant
declare function context_id_chain(): void;
+@Constant
+declare function debug_type_independent(t: any): void;
// A function, as it should be!
@Constant
diff --git a/checker/definitions/simple.d.ts b/checker/definitions/simple.d.ts
index deb1120b..d5814b3d 100644
--- a/checker/definitions/simple.d.ts
+++ b/checker/definitions/simple.d.ts
@@ -5,6 +5,8 @@ declare function print_type(...args: Array): void
@Constant
declare function debug_type(...args: Array): void
@Constant
+declare function debug_type_independent(...args: Array): void
+@Constant
declare function print_and_debug_type(...args: Array): void
@Constant
declare function print_constraint(t: any): void
@@ -38,7 +40,7 @@ declare function satisfies(t: T): T
declare function compile_type_to_object(): any
interface ImportEnv {
- [key: string]: string | undefined;
+ [key: string]: string;
}
interface ImportMeta {
@@ -48,13 +50,14 @@ interface ImportMeta {
}
declare class Array {
- [index: number]: T | undefined;
+ [index: number]: T;
length: number;
push(item: T) {
- this[this.length] = item;
- return ++this.length
+ let at = this.length++;
+ this[at] = item;
+ return at + 1
}
pop(): T | undefined {
@@ -62,62 +65,83 @@ declare class Array {
return undefined
} else {
const value = this[--this.length];
- // TODO this breaks things
- // delete this[this.length];
- // debug_type_rust_independent(value);
+ delete this[this.length];
return value
}
}
- // // TODO this argument
+ // map(cb: (t: T, i?: number) => U): Array {
+ // TODO this argument
map(cb: (t: T, i?: number) => U): Array {
const { length } = this, mapped: Array = [];
let i: number = 0;
while (i < length) {
- const value = this[i];
- mapped.push(cb(value, i++))
+ const value = this?.[i];
+ const newValue = cb(value, i++);
+ mapped.push(newValue)
}
return mapped;
}
+ // copy(): Array {
+ // const { length } = this, mapped: Array = [];
+ // let i: number = 0;
+ // while (i < length) {
+ // mapped.push(this?.[i])
+ // }
+ // return mapped;
+ // }
+
+ // // // TODO any is debatable
+ // filter(cb: (t: T, i?: number) => any): Array {
+ // const { length } = this, filtered: Array = [];
+ // let i: number = 0;
+ // while (i < length) {
+ // const value = this[i];
+ // if (cb(value, i++)) {
+ // filtered.push(value)
+ // }
+ // }
+ // return filtered;
+ // }
+
// // TODO any is debatable
- filter(cb: (t: T, i?: number) => any): Array {
- const { length } = this, filtered: Array = [];
- let i: number = 0;
- while (i < length) {
- const value = this[i];
- if (cb(value, i++)) {
- filtered.push(value)
- }
- }
- return filtered;
- }
+ // find(cb: (t: T, i?: number) => any): T | undefined {
+ // const { length } = this;
+ // let i: number = 0;
+ // while (i < length) {
+ // const value = this[i];
+ // if (cb(value, i++)) {
+ // return value
+ // }
+ // }
+ // }
- // TODO any is debatable
- find(cb: (t: T, i?: number) => any): T | undefined {
- const { length } = this;
- let i: number = 0;
- while (i < length) {
- const value = this[i];
- if (cb(value, i++)) {
- return value
- }
- }
- }
+ // // TODO any is debatable
+ // every(cb: (t: T, i?: number) => any): boolean {
+ // const { length } = this;
+ // let i: number = 0;
+ // while (i < length) {
+ // const value = this[i];
+ // if (!cb(value, i++)) {
+ // return false
+ // }
+ // }
+ // // Vacuous truth
+ // return true
+ // }
- // TODO any is debatable
- every(cb: (t: T, i?: number) => any): boolean {
- const { length } = this;
- let i: number = 0;
- while (i < length) {
- const value = this[i];
- if (!cb(value, i++)) {
- return false
- }
- }
- // Vacuous truth
- return true
- }
+ // includes(looking_for: T): boolean {
+ // const { length } = this;
+ // let i: number = 0;
+ // while (i < length) {
+ // const value = this[i++];
+ // if (value === looking_for) {
+ // return true
+ // }
+ // }
+ // return false
+ // }
// some(cb: (t: T, i?: number) => any): boolean {
// const { length } = this;
@@ -131,35 +155,66 @@ declare class Array {
// return false
// }
- join(joiner: string = ","): string {
- const { length } = this;
- let i: number = 1;
- if (length === 0) {
- return ""
- }
- let s: string = "" + this[0];
- while (i < length) {
- s += joiner;
- s += this[i++];
- }
- return s
- }
+ // join(joiner: string = ","): string {
+ // const { length } = this;
+ // let i: number = 1;
+ // if (length === 0) {
+ // return ""
+ // }
+ // let s: string = "" + this[0];
+ // while (i < length) {
+ // s += joiner;
+ // s += this[i++];
+ // }
+ // return s
+ // }
- at(index: number) {
- if (index < 0) {
- return this[index + this.length]
- } else {
- return this[index]
- }
+ // at(index: number) {
+ // if (index < 0) {
+ // return this[index + this.length]
+ // } else {
+ // return this[index]
+ // }
+ // }
+}
+
+declare class Map {
+ #keys: Array;
+ #values: Array;
+
+ constructor() {
+ this.#keys = []
+ this.#values = []
}
+
+ // get(key: K): V | undefined {
+ // // return this.#keys;
+ // const { length } = this.#keys;
+ // for (let i = 0; i < length; i++) {
+ // const s = length - 1 - i;
+ // if (this.#keys[s] === key) {
+ // return this.#values[s]
+ // }
+ // }
+ // }
+
+ // set(key: K, value: V) {
+ // this.#keys.push(key);
+ // this.#values.push(value);
+ // }
}
type Record = { [P in K]: T }
-declare class Map {
- #keys: Array = [];
- #value: Array = [];
-}
+/**
+ * Exclude from T those types that are assignable to U
+ */
+type Exclude = T extends U ? never : T;
+
+/**
+ * Extract from T those types that are assignable to U
+ */
+type Extract = T extends U ? T : never;
declare class Math {
@Constant
@@ -174,22 +229,28 @@ declare class Math {
static sqrt(x: number): number;
@Constant
static cbrt(x: number): number;
+ @Constant
+ static log(x: number): number;
// TODO newer method
@Constant
static trunc(x: number): number;
static PI: 3.141592653589793
+ static E: 2.718281828459045
+
+ @InputOutput
+ static random(): number;
}
@Primitive("string")
declare class String {
- [index: number]: string | undefined;
+ [index: number]: string;
@Constant
- toUpperCase(): string;
+ toUpperCase(this: string): string;
@Constant
- toLowerCase(): string;
+ toLowerCase(this: string): string;
get length(): number;
@@ -233,16 +294,18 @@ declare class Error {
}
declare class SyntaxError extends Error {
- constructor() { super("syntax error") }
+ constructor() {
+ super("syntax error");
+ }
}
declare class JSON {
// TODO any temp
- @Constant("json:parse", SyntaxError)
+ @Constant("JSON:parse", SyntaxError)
static parse(input: string): any;
// TODO any temp
- @Constant("json:stringify")
+ @Constant("JSON:stringify")
static stringify(input: any): string;
}
@@ -250,9 +313,9 @@ declare class Function {
bind(this_ty: any): Function;
}
-declare class Symbols {
+declare class Symbol {
// TODO temp
- iterator: 199
+ static iterator: unique symbol "iterator"
}
declare class Proxy {
@@ -260,6 +323,18 @@ declare class Proxy {
constructor(obj: any, cb: any);
}
+// Copied from `es5.d.ts`. Could this be an or
+// TODO string keys temp because parser broke
+interface PropertyDescriptor {
+ value?: any;
+ ["get" ? (): any;
+ ["set" ? (v: any): void;
+
+ writable?: boolean;
+ configurable?: boolean;
+ enumerable?: boolean;
+}
+
declare class Object {
@Constant
static setPrototypeOf(on: object, to: object): object;
@@ -267,46 +342,37 @@ declare class Object {
@Constant
static getPrototypeOf(on: object): object | null;
+ @Constant
+ static freeze(on: object): object;
+
+ @Constant
+ static isFrozen(on: object): boolean;
+
+ // TODO defineProperties via body (not constant)
+ @Constant
+ static defineProperty(on: object, property: string, discriminator: PropertyDescriptor): boolean;
+
+ // TODO getOwnPropertyDescriptors via body (not constant)
+ @Constant
+ static getOwnPropertyDescriptor(on: object, property: string): PropertyDescriptor;
+
// static create(prototype: object): object {
// const n = {};
// Object.setProtoTypeOf(n, prototype);
// return n
// }
+}
- static keys(on: { [s: string]: any }): Array {
- const keys: Array = [];
- for (const key in on) {
- keys.push(key);
- }
- return keys
- }
-
- static values(on: { [s: string]: any }): Array {
- const values: Array = [];
- for (const key in on) {
- values.push(on[key]);
- }
- return values
- }
-
- static entries(on: { [s: string]: any }): Array<[string, any]> {
- const entries: Array<[string, any]> = [];
- for (const key in on) {
- entries.push([key, on[key]]);
- }
- return entries
- }
-
- // static fromEntries(iterator: any): object {
- // const obj = {};
- // for (const item of iterator) {
- // const { 0: key, 1: value } = item;
- // obj[key] = value;
- // }
- // return obj
- // }
+declare class RegExp {
+ @Constant("RegExp:constructor")
+ constructor(s: string)
}
+// WIP
+// interface SymbolImplementations {
+// [Symbol.iterator]: () => { next(): { value: any, done: boolean } }
+// }
+
// TODO wip
declare function JSXH(tag: string, attributes: any, children?: any) {
return { tag, attributes, children }
diff --git a/checker/documentation/checking.md b/checker/documentation/checking.md
index 3e8ac1c8..45f18617 100644
--- a/checker/documentation/checking.md
+++ b/checker/documentation/checking.md
@@ -23,12 +23,20 @@ Rather than checking whether types are subtypes of some base type, these are con
## Checking a program
Starts with a list of statements.
-Before any of the statements are checked, they are first *hoisted*.
+Before any of the statements and declarations are checked, they are first *hoisted*.
+
+After that the checker performs synthesis and checking. It only visits expressions once, the binding happens at the same time as checking.
### Hoisting
Both types and variables can be used before their declaration position in the source. Before checking statements, a group of passes 'lifts' certain information.
-There are three stages to hoisting.
+There are three stages to hoisting an item
+
+- Declaring an item
+- Registering it
+- Finishing it?
+
+There are four stages to
(1) Firstly
- Interface and type alias **names** are declared
diff --git a/checker/examples/calculate_subtypes.rs b/checker/examples/calculate_subtypes.rs
index 6ef83f4d..66270eef 100644
--- a/checker/examples/calculate_subtypes.rs
+++ b/checker/examples/calculate_subtypes.rs
@@ -16,7 +16,7 @@ fn main() {
let mut types = TypeStore::default();
basics(&mut environment, &mut types);
- contributions(&mut environment, &mut types)
+ contributions(&mut environment, &mut types);
}
fn basics(environment: &mut Environment, types: &mut TypeStore) {
@@ -29,22 +29,22 @@ fn basics(environment: &mut Environment, types: &mut TypeStore) {
{
let result = type_is_subtype_object(TypeId::NUMBER_TYPE, five, environment, types);
- eprintln!("number :> 5 {result:?}")
+ eprintln!("number :> 5 {result:?}");
}
{
let result =
type_is_subtype_object(TypeId::NUMBER_TYPE, TypeId::STRING_TYPE, environment, types);
- eprintln!("number :> string {result:?}")
+ eprintln!("number :> string {result:?}");
}
{
let result =
type_is_subtype_object(string_or_number, TypeId::STRING_TYPE, environment, types);
- eprintln!("string | number :> string {result:?}")
+ eprintln!("string | number :> string {result:?}");
}
- eprintln!("--------------\n")
+ eprintln!("--------------\n");
}
fn contributions(environment: &mut Environment, types: &mut TypeStore) {
@@ -60,7 +60,6 @@ fn contributions(environment: &mut Environment, types: &mut TypeStore) {
Publicity::Public,
inner.clone(),
PropertyValue::Value(generic_parameter.type_id),
- false,
source_map::SpanWithSource::NULL,
);
@@ -77,11 +76,11 @@ fn contributions(environment: &mut Environment, types: &mut TypeStore) {
&mut environment.info,
);
basis.append(
- environment,
Publicity::Public,
inner,
PropertyValue::Value(five),
source_map::SpanWithSource::NULL,
+ &mut environment.info,
);
basis.build_object()
};
@@ -96,7 +95,7 @@ fn contributions(environment: &mut Environment, types: &mut TypeStore) {
others: SubTypingOptions::default(),
object_constraints: None,
};
- let result = type_is_subtype(parameter, five_obj, &mut state, &environment, &types);
+ let result = type_is_subtype(parameter, five_obj, &mut state, environment, types);
let contributions = state.contributions.as_ref().unwrap();
diff --git a/checker/examples/generate_cache.rs b/checker/examples/generate_cache.rs
index 27f4bba0..d7ccc711 100644
--- a/checker/examples/generate_cache.rs
+++ b/checker/examples/generate_cache.rs
@@ -17,5 +17,5 @@ fn main() {
let cache = generate_cache::<_, EznoParser>(PathBuf::from(input).as_path(), &reader, ());
write(output, cache).unwrap();
- eprintln!("Cache generated 🏧💵✅")
+ eprintln!("Cache generated 🏧💵✅");
}
diff --git a/checker/examples/run-checker.rs b/checker/examples/run_checker.rs
similarity index 79%
rename from checker/examples/run-checker.rs
rename to checker/examples/run_checker.rs
index 89e816a1..eee0dc63 100644
--- a/checker/examples/run-checker.rs
+++ b/checker/examples/run_checker.rs
@@ -1,7 +1,7 @@
#[cfg(feature = "ezno-parser")]
fn main() {
use ezno_checker::{check_project, synthesis, Diagnostic, TypeCheckOptions};
- use std::{collections::HashSet, fs, path::Path, time::Instant};
+ use std::{fs, path::Path, time::Instant};
let default_path = Path::new("private").join("tocheck").join("aaa.tsx");
let simple_dts_path = Path::new("checker").join("definitions").join("simple.d.ts");
@@ -12,34 +12,43 @@ fn main() {
let path = args
.first()
.and_then(|arg| (!arg.starts_with("--")).then_some(arg))
- .map(Path::new)
- .unwrap_or(default_path.as_path());
+ .map_or(default_path.as_path(), Path::new);
let use_simple = args.iter().any(|item| item == "--simple-dts");
let no_cache = args.iter().any(|item| item == "--no-cache");
let debug_types = args.iter().any(|item| item == "--debug-types");
+ let no_lib = args.iter().any(|item| item == "--no-lib");
+ let debug_dts = args.iter().any(|item| item == "--debug-dts");
let now = Instant::now();
let resolver = |path: &std::path::Path| fs::read(path).ok();
- let definition_file = if use_simple {
- simple_dts_path.to_path_buf()
- } else if no_cache {
- overrides_dts_path.to_path_buf()
+ let type_definition_files = if no_lib {
+ Vec::new()
} else {
- ezno_checker::INTERNAL_DEFINITION_FILE_PATH.into()
+ let definition_file = if use_simple {
+ simple_dts_path.clone()
+ } else if no_cache {
+ overrides_dts_path.clone()
+ } else {
+ ezno_checker::INTERNAL_DEFINITION_FILE_PATH.into()
+ };
+ vec![definition_file]
};
- let type_definition_files = HashSet::from_iter([definition_file]);
+
+ let entry_points = vec![path.to_path_buf()];
let options = TypeCheckOptions {
debug_types,
record_all_assignments_and_reads: true,
+ max_inline_count: 600,
+ debug_dts,
..Default::default()
};
let result = check_project::<_, synthesis::EznoParser>(
- vec![path.to_path_buf()],
+ entry_points,
type_definition_files,
resolver,
options,
@@ -49,7 +58,8 @@ fn main() {
if args.iter().any(|arg| arg == "--types") {
eprintln!("Types:");
- for (type_id, item) in result.types.into_vec_temp() {
+ let types = result.types.into_vec_temp();
+ for (type_id, item) in &types[types.len().saturating_sub(60)..] {
eprintln!("\t{type_id:?}: {item:?}");
}
}
diff --git a/checker/specification/README.md b/checker/specification/README.md
index a2482f1b..17f841f6 100644
--- a/checker/specification/README.md
+++ b/checker/specification/README.md
@@ -9,6 +9,10 @@ These can all be ran as tests using the markdown to Rust test code transpiler in
- Comments can be in block quotes to explain additional details in the tests
- Sections are at level three headings (`###`), tests are at level four headings (`####`), the tested code goes a code block with the language tag `ts` and errors in a bullet list after in order
- Blocks can be split into files with a `// in file.ts` comment, below which all code is in the `file.ts` file. Default is `main.tsx`
+- **Code in blocks is indented with tabs not spaces**
---
Other tests unrelated to specific checking features can be found in `checker/tests`
+
+> [!IMPORTANT]
+> Any tests that throw or produce unreachables must be contained as it breaks the total concatenation.
diff --git a/checker/specification/build.rs b/checker/specification/build.rs
index b860a076..e31236a4 100644
--- a/checker/specification/build.rs
+++ b/checker/specification/build.rs
@@ -134,7 +134,8 @@ fn markdown_lines_append_test_to_rust(
fn heading_to_rust_identifier(heading: &str) -> String {
heading
- .replace([' ', '-', '/', '&', '.', '+'], "_")
- .replace(['*', '\'', '`', '"', '!', '(', ')', ','], "")
+ .replace("...", "")
+ .replace([' ', '-', '/', '.', '+'], "_")
+ .replace(['*', '\'', '`', '"', '&', '!', '(', ')', ','], "")
.to_lowercase()
}
diff --git a/checker/specification/specification.md b/checker/specification/specification.md
index f7838ac9..fc791100 100644
--- a/checker/specification/specification.md
+++ b/checker/specification/specification.md
@@ -46,10 +46,11 @@ a satisfies number
#### Variable references does not exist
```ts
-const a = c
+const exists = 2;
+nexists
```
-- Could not find variable 'c' in scope. Did you mean a?
+- Could not find variable 'nexists' in scope
#### Assigning before declaration
@@ -107,6 +108,34 @@ b satisfies string;
- Expected string, found undefined
+#### TDZ in statements
+
+```ts
+let first = second;
+let second = 2;
+```
+
+- Variable 'second' used before declaration
+
+#### `var` can be reregistered
+
+```ts
+{
+ // Fine
+ var x = 2;
+ x satisfies 2;
+ var x = 3;
+ x satisfies 3;
+}
+
+{
+ let b = 2;
+ var b = 2;
+}
+```
+
+- Cannot redeclare variable 'b'
+
### Properties
#### Property exists
@@ -117,7 +146,7 @@ const a = my_obj.a
const b = my_obj.b
```
-- No property 'b' on { a: 3 }. Did you mean a?
+- No property 'b' on { a: 3 }
#### Reading property (via accessor)
@@ -149,7 +178,7 @@ const my_obj: { b: 3 } = { b: 4 }
#### Getters
```ts
-let global = 0;
+let global: number = 0;
const object = {
// This getter has an impure side effect
get value() {
@@ -185,7 +214,7 @@ object.value satisfies string
let a = 2;
const obj = {
x: 5,
- set value(v) {
+ set value(this: { x: number }, v) {
this.x = v;
}
}
@@ -200,13 +229,15 @@ obj.x satisfies 5;
```ts
const obj = {
- set value(v: string) { }
+ set value(v: string) { }
}
obj.value = 5;
```
-- Type 5 does not meet property constraint string
+> TODO could the error be better?
+
+- Argument of type 5 is not assignable to parameter of type string (in setter)
#### Setter side effect
@@ -253,9 +284,9 @@ obj2.a satisfies boolean;
#### Set property with key
```ts
-const obj = { a: 2 }
+const obj: { a?: number, b?: number } = { a: 2 }
-function setProperty(key: string, value) {
+function setProperty(key: "a" | "b", value: number) {
obj[key] = value;
}
@@ -273,7 +304,7 @@ delete x.b;
const b = x.b;
```
-- No property 'b' on { a: 2 }. Did you mean a?
+- No property 'b' on { a: 2 }
#### `Object.keys`, `Object.values`, `Object.entries`
@@ -291,11 +322,11 @@ Object.entries({ a: 1, b: 2 }) satisfies boolean;
declare let condition: boolean;
const obj = {
- foo: 1,
- ...(condition ? {
- bar: 2,
- non_existent: 3,
- } : {}),
+ foo: 1,
+ ...(condition ? {
+ bar: 2,
+ non_existent: 3,
+ } : {}),
};
obj.foo satisfies number;
@@ -304,7 +335,305 @@ obj.bar satisfies string;
- Expected string, found 2 | undefined
-### Excess property
+#### And on properties
+
+> Note that it keeps it as a `and`. It does not join the properties into a single typ
+
+```ts
+declare type U = { a: 2 } & { b: 3 }
+declare let x: U;
+x.b satisfies 3;
+
+({ a: 2, b: 3 } satisfies U);
+({ b: 3 } satisfies U);
+```
+
+- Expected U, found { b: 3 }
+
+#### Properties on or
+
+```ts
+declare let key: "a" | "b";
+
+const object = { a: "apple", b: "banana" };
+object[key] satisfies boolean;
+```
+
+- Expected boolean, found "apple" | "banana"
+
+#### Properties on big or
+
+> TODO this creates a fat or type
+
+```ts
+const array = [1, 2, 3];
+array[Math.random()] satisfies string;
+```
+
+- Expected string, found 1 | 2 | 3 | undefined
+
+#### Properties matched against continous type become conditional
+
+> I think this is TSC behavior under indexed access
+
+```ts
+declare let strings: { [a: string]: number };
+declare let record: Record;
+declare let d: { [a: "a" | "b"]: number };
+
+strings.a satisfies number | undefined;
+record.a satisfies boolean;
+d.a satisfies number;
+```
+
+- Expected boolean, found number | undefined
+
+#### Un-delete-able property
+
+> TODO in a function as well
+
+```ts
+const x: { a?: number } = { a: 4 };
+// Fine
+delete x.a;
+
+const y: { a: number } = { a: 4 };
+// Bad
+delete y.a;
+
+const z = {};
+Object.defineProperty(z, "a", { value: 4 });
+delete z.a;
+```
+
+- Cannot delete from object constrained to { a: number }
+- Cannot delete from non-configurable property
+
+#### Order of numerical properties
+
+> TODO test could be better using `for in` or `Object.keys` etc
+
+```ts
+let x = {}; x.something = null; x[4] = null; x["eight"] = null; x["2"] = null;
+x satisfies string;
+```
+
+- Expected string, found { 2: null, 4: null, something: null, eight: null }
+
+#### Order of properties after assignment
+
+> TODO this is because setting properties are simply appended. There are two straightforward fixes, but I am unsure which one is better...
+
+```ts
+const obj = { a: 1, b: 2 };
+obj.a = 2; obj.c = 6; obj.b = 4;
+obj satisfies boolean;
+```
+
+- Expected boolean, found { a: 2, b: 4, c: 6 }
+
+#### Assigning to getter
+
+```ts
+const obj = { get prop() { return 2 } };
+obj.prop = "hi";
+obj.prop satisfies 2;
+```
+
+- Cannot write to property 'prop' as it is a getter
+
+#### Assinging to non existent property
+
+> Allowing this could break objects passed to functions
+
+```ts
+const obj = { prop: 1 };
+// Fine
+obj.non_existent = 6;
+
+function func(param: { prop: number }) {
+ param.notProp = 5;
+}
+```
+
+- Cannot write to non-existent property 'notProp'
+
+#### Function and class name
+
+> TODO should also check that it is readonly
+
+```ts
+function a() { }
+class B { }
+let c = class { }
+
+a.name satisfies "a"
+B.name satisfies "B"
+c.name satisfies "sea"
+```
+
+- Expected "sea", found "c"
+
+#### Getters AND setter
+
+> This involves property lookup skipping setter and getters
+
+```ts
+let global: number = 0;
+const object = {
+ get value() {
+ return global
+ },
+ set value(newValue: number) {
+ global = newValue;
+ }
+}
+
+object.value satisfies string;
+object.value = 10;
+object.value satisfies 10;
+global satisfies 10
+```
+
+- Expected string, found 0
+
+#### Getters AND setter can be type via `Object.defineProperty`
+
+> TODO parameter checking as well
+
+```ts
+function func(get: () => number) {
+ const obj = {};
+ Object.defineProperty(obj, "value", { get });
+ obj.value satisfies string
+}
+```
+
+- Expected string, found number
+
+#### `enumerable` in for in
+
+```ts
+const obj = { n: 1, b: 2 };
+Object.defineProperty(obj, "c", { value: 3, enumerable: false });
+Object.defineProperty(obj, "d", { value: 4, enumerable: true });
+
+let keys: string = "";
+for (const key in obj) {
+ keys += key;
+}
+keys satisfies boolean
+```
+
+- Expected boolean, found "nbd"
+
+#### `Object.freeze`
+
+> TODO seal & preventExtensions
+
+```ts
+const obj = {}
+let result = Object.freeze(obj);
+(obj === result) satisfies true;
+obj.property = 2;
+Object.isFrozen(obj) satisfies true;
+```
+
+> TODO maybe error should say that whole object is frozen
+
+- Cannot write to property 'property'
+
+#### `Object.defineProperty` writable
+
+> TODO defineProperties
+
+```ts
+const obj = {};
+Object.defineProperty(obj, 'property', {
+ value: 42,
+ writable: false,
+});
+obj.property satisfies string;
+obj.property = 70;
+```
+
+- Expected string, found 42
+- Cannot write to property 'property'
+
+#### Descriptor carries across assignments
+
+> TODO defineProperties
+
+```ts
+const obj = {};
+Object.defineProperty(obj, 'property', {
+ value: 42,
+ enumerable: false,
+ // needed as all properties default to false
+ writable: true,
+});
+obj.property = 70;
+Object.getOwnPropertyDescriptor(obj, 'property') satisfies string;
+```
+
+- Expected string, found { value: 70, writable: true, enumerable: false, configurable: false }
+
+#### `Object.defineProperty` getter and setter
+
+> TODO setter
+
+```ts
+const obj = {};
+let b = 0;
+Object.defineProperty(obj, 'property', {
+ get: () => b,
+});
+obj.property satisfies 0;
+b++;
+obj.property satisfies string;
+```
+
+- Expected string, found 1
+
+#### `Object.defineProperty` configurable
+
+```ts
+const obj = {};
+Object.defineProperty(obj, 'property', { value: 6 });
+Object.defineProperty(obj, 'property', { value: "hi" });
+```
+
+- Property 'property' not configurable
+
+#### `Object.getOwnPropertyDescriptor`
+
+> TODO getOwnPropertyDescriptors
+
+```ts
+const obj = { a: "something" };
+Object.defineProperty(obj, 'b', { value: 42 });
+
+Object.getOwnPropertyDescriptor(obj, 'a') satisfies string;
+Object.getOwnPropertyDescriptor(obj, 'b').writable satisfies false;
+```
+
+> Order is also important
+
+- Expected string, found { value: "something", writable: true, enumerable: true, configurable: true }
+
+#### `Object.assign`
+
+> TODO multiple RHS
+
+```ts
+const obj = { a: 1 };
+Object.assign(obj, { b: 2, c: 3 });
+obj satisfies string;
+```
+
+- Expected string, found { a: 1, b: 2, c: 3 }
+s
+### Excess properties
> The following work through the same mechanism as forward inference
> Thanks to pull request: #139
@@ -327,9 +656,14 @@ interface MyObject { property: string }
function process(param: MyObject) {}
process({ property: "hello", another: 2 });
+
+function func(a: T) { }
+
+func({ property: "hello", "something else": 2 })
```
- 'another' is not a property of MyObject
+- 'something else' is not a property of MyObject
#### Excess property at return type
@@ -349,21 +683,21 @@ function returnNewObject(): MyObject {
type MyObject = { foo: number; bar?: number };
const b: MyObject = {
- foo: 1,
- ...{
- bar: 2,
- invalid: 3,
- },
+ foo: 1,
+ ...{
+ bar: 2,
+ invalid: 3,
+ },
};
declare let condition: boolean;
const c: MyObject = {
- foo: 1,
- ...(condition ? {
- bar: 2,
- non_existent: 3,
- } : {}),
+ foo: 1,
+ ...(condition ? {
+ bar: 2,
+ non_existent: 3,
+ } : {}),
};
```
@@ -494,8 +828,8 @@ function func(a: number) {
```ts
function outer(a: number) {
- function inner(b: string = Math.floor(a)) {
- }
+ function inner(b: string = Math.floor(a)) {
+ }
}
```
@@ -511,30 +845,6 @@ function func(): string {
- Cannot return 2 because the function is expected to return string
-#### *Inferred* return type
-
-```ts
-function func() {
- return 2
-}
-func satisfies () => string
-```
-
-- Expected () => string, found () => 2
-
-#### Set property on dependent observed
-
-```ts
-function add_property(obj: { prop: number }) {
- obj.prop = 2;
- (obj.prop satisfies 4);
-}
-```
-
-> Not number
-
-- Expected 4, found 2
-
#### Type checking basic function types
```ts
@@ -549,18 +859,6 @@ func satisfies (a: number, b: number) => boolean;
- Expected (a: string, b: number) => string, found (a: string, b: number) => boolean
- Expected (a: number, b: number) => boolean, found (a: string, b: number) => boolean
-#### Function that throws returns never
-
-```ts
-function myThrow() {
- throw "err!"
-}
-
-myThrow satisfies string;
-```
-
-- Expected string, found () => never
-
#### Return generics mismatch
```ts
@@ -611,7 +909,7 @@ function createObject2(a: T, b: U): { a: U, b: U } {
const x: (a: string) => number = a => a.to;
```
-- No property 'to' on string.
+- No property 'to' on string
#### Expected argument from parameter declaration
@@ -623,7 +921,7 @@ function map(a: (a: number) => number) {}
map(a => a.t)
```
-- No property 't' on number.
+- No property 't' on number
#### Assignment to parameter
@@ -678,11 +976,11 @@ myFunction({ a: 6 }) satisfies string;
```ts
function getNumber1(): number {
- return 4
+ return 4
}
function getNumber2() {
- return 6
+ return 6
}
getNumber1 satisfies () => 4;
@@ -701,20 +999,106 @@ getNumber2() satisfies 6;
```ts
function x() {
- getString(3)
+ getString(3)
}
function y() {
- getString("something") satisfies string;
+ getString("something") satisfies string;
}
function getString(param: string): string {
- return "hi"
+ return "hi"
}
```
- Argument of type 3 is not assignable to parameter of type string
+### Inferred return types
+
+#### Simple
+
+```ts
+function id(a: number) {
+ return a
+}
+
+function simple() {
+ return "hello world"
+}
+
+id satisfies (n: number) => number;
+simple satisfies () => number;
+```
+
+- Expected () => number, found () => "hello world"
+
+#### Conditional
+
+```ts
+function func(condition: T) {
+ if (condition) {
+ return 4
+ } else {
+ return 3
+ }
+}
+
+func satisfies string;
+```
+
+> There are some issues around printing here, when to include the generic etc
+
+- Expected string, found \(condition: T) => T ? 4 : 3
+
+#### Early return
+
+```ts
+function func(value: number) {
+ if (value === 3) {
+ return "is three"
+ }
+ console.log("hi")
+ return "another"
+}
+
+loop satisfies (a: number) => "is three" | "another";
+
+function loop(value: number) {
+ for (let i = 0; i < 10; i++) {
+ if (value === i) {
+ return "something"
+ }
+ }
+ return "another"
+}
+
+loop satisfies (a: number) => "something" | "another";
+
+function sometimes(a: boolean) {
+ if (a) {
+ return "sometimes"
+ }
+}
+
+sometimes satisfies (a: boolean) => string;
+```
+
+- Expected (a: boolean) => string, found (a: boolean) => "sometimes" | undefined
+
+#### `throw` in body
+
+```ts
+function throwSomething() {
+ throw "to implement!"
+}
+
+throwSomething satisfies string;
+```
+
+- Expected string, found () => never
+
+> #TODO try-catch, Promise
+
### Function calling
#### Argument type against parameter
@@ -913,13 +1297,13 @@ const m = new MyClass();
```ts
function myRestFunction(...r: string[]) {
- return r[0] + r[1]
+ return r
}
myRestFunction("hello ", "world") satisfies number;
```
-- Expected number, found "hello world"
+- Expected number, found ["hello ", "world"]
#### Default parameter
@@ -938,11 +1322,11 @@ withDefault(3) satisfies 3;
```ts
let b: number = 0
-function doThing(a = (b += 2)) {
+function doThing(a: number = (b += 2)) {
return a
}
-doThing("hello");
+doThing(7);
b satisfies 0;
doThing();
b satisfies 1;
@@ -956,7 +1340,7 @@ b satisfies 1;
```ts
function optionally(p?: number) {
- p satisfies string;
+ p satisfies string;
}
```
@@ -966,7 +1350,7 @@ function optionally(p?: number) {
```ts
function optionally(p?: number) {
- return p
+ return p
}
// Fine
@@ -978,23 +1362,10 @@ optionally("hello world");
- Argument of type "hello world" is not assignable to parameter of type number | undefined
-#### Tagged template literal
-
-```ts
-function myTag(static_parts: Array, name: string) {
- return static_parts[0] + name
-}
-
-const name = "Ben";
-myTag`${name}Hello ` satisfies "Hi Ben"
-```
-
-- Expected "Hi Ben", found "Hello Ben"
-
#### Default parameter side effect on parameter
```ts
-function doThing(a, b = (a += 2)) {
+function doThing(a: number, b: number = (a += 2)) {
return a
}
@@ -1027,6 +1398,64 @@ myTag`Count is ${"not a number!!"}`;
- Argument of type \"not a number!!\" is not assignable to parameter of type number (in template literal)
+#### Generics on dependent type
+
+> Weird annotation here is to work around [#165](https://github.com/kaleidawave/ezno/issues/165)
+
+```ts
+function createNew(cb: { f(t: T): { a: T }}["f"]) {
+ return cb(4)
+}
+
+createNew satisfies string;
+```
+
+- Expected string, found (cb: \(t: T) => { a: T }) => { a: 4 }
+
+#### Builder pattern
+
+> Testing for `this` returning
+
+```ts
+class StringBuilder {
+ s: string = ""
+
+ append(s: string) {
+ this.s += s;
+ return this
+ }
+
+ finish() {
+ return this.s
+ }
+}
+
+(new StringBuilder).append("Hello ").append("Ben").finish() satisfies number
+```
+
+- Expected number, found "Hello Ben"
+
+#### Dependent operations
+
+```ts
+function isFive(a: number): boolean {
+ return a === 5
+}
+
+isFive(5) satisfies true;
+isFive(6) satisfies string;
+
+function hasPropertyX(obj: object): boolean {
+ return "x" in obj;
+}
+
+hasPropertyX({ a: 2 }) satisfies false;
+hasPropertyX({ x: 5 }) satisfies number;
+```
+
+- Expected string, found false
+- Expected number, found true
+
### Effects
> Side effects of functions. Registered internally as `Event`s
@@ -1130,6 +1559,20 @@ let x: number = 5;
> Not shown in the example but thanks to [#69](https://github.com/kaleidawave/ezno/pull/69) for adding the position of the error
+#### TDZ errors through nested getter
+
+```ts
+function func(obj: { prop: number }) {
+ return obj.prop
+}
+
+func({ get prop() { return b } });
+
+let b: number = 0;
+```
+
+- Variable 'b' used before declaration
+
#### Assignment to union
> Solves the common subtyping issue between read and write properties
@@ -1151,7 +1594,7 @@ setAtoString({ a: 6 });
setAtoString(myObject);
```
-- Invalid assignment to parameter
+- Invalid assignment through parameter
> Error message could be better. Full diagnostic contains labels with more information
> `readA` is allowed, which is disallowed in Hegel, but here is allowed to preserve TSC compatibility (and because how structural subtyping is implemented)
@@ -1201,6 +1644,118 @@ const c = a;
- Expected false, found true
- Expected string, found false
+#### Unconditional throw warning
+
+```ts
+function safeDivide(num: number, denom: number) {
+ if (denom === 0) {
+ throw new Error("Cannot divide by zero");
+ }
+ return num / denom
+}
+
+function func() {
+ safeDivide(8, 4) satisfies 2;
+ // ahh
+ safeDivide(10, 0);
+}
+```
+
+- Conditional '[Error] { message: \"Cannot divide by zero\" }' was thrown in function
+
+#### Unreachable statement
+
+```ts
+function throwGreeting() {
+ throw "Hello";
+ return "Unreachable!"
+}
+
+function doSomething() {
+ throwGreeting()
+ const unreachable = 2;
+}
+```
+
+> One is for `return 5` the other is for `const x = 2;`
+
+- Unreachable statement
+- Unreachable statement
+
+#### `throw` short-circuit
+
+```ts
+let x: number = 2;
+
+function func(cb: () => void) {
+ try {
+ cb();
+ x = 10;
+ return "not-thrown"
+ } catch {
+ return "thrown"
+ }
+}
+
+func(() => { throw "error" }) satisfies "thrown";
+x satisfies string;
+```
+
+- Expected string, found 2
+
+#### `delete` as an effect
+
+```ts
+function dewete(param: { prop?: string }) {
+ const { prop } = param;
+ delete param.prop;
+ return prop
+}
+
+const obj = { prop: "hi" };
+dewete(obj);
+
+obj.prop;
+```
+
+> This error *should* be from the last statement
+
+- No property 'prop' on {}
+
+#### Optional effect key
+
+```ts
+let i: number = 0;
+({ a: true})?.[i++, "a"] satisfies true;
+i satisfies 1;
+
+null?.[i++, "a"];
+i satisfies string;
+```
+
+- Expression is always false
+- Expression is always true
+- Expected string, found 1
+
+#### Effects across functions
+
+```ts
+let value: number = 2;
+function a() { value = 8; }
+function b() { a() }
+
+let func = () => {};
+
+function c() { b() }
+function d(newCb: () => void, then: () => void) { func = newCb; then() }
+
+value satisfies 2;
+d(a, c);
+value satisfies boolean;
+```
+
+- Expected boolean, found 8
+
### Closures
#### Reading variable
@@ -1268,6 +1823,46 @@ value.getValue() satisfies 6
- Expected 6, found 10
+#### Class constructors
+
+```ts
+function func(a: number, b: number) {
+ return class {
+ value: number;
+
+ constructor() {
+ this.value = a;
+ }
+
+ plusB() {
+ return this.value + b
+ }
+ }
+}
+
+const c1 = new (func(1, 2));
+c1.plusB() satisfies 3;
+
+const c2 = new (func(6, 8));
+c2.plusB() satisfies string;
+```
+
+- Expected string, found 14
+
+#### Getters closures
+
+```ts
+function Closure(n: string) {
+ return { get value() { return n }, set value(newValue: string) { n = newValue; } };
+}
+
+let b = Closure("hi");
+b.value = "something";
+b.value satisfies number;
+```
+
+- Expected number, found "something"
+
### Control flow
#### Resolving conditional
@@ -1363,9 +1958,9 @@ b satisfies string;
let i = 0;
declare let b: boolean;
if (b) {
- i = 1
+ i = 1
} else {
- i = 2
+ i = 2
}
i satisfies string;
@@ -1379,10 +1974,10 @@ i satisfies string;
declare let string: string;
function stringIsHi(s: string) {
- if (s === "hi") {
- return true
- }
- return false
+ if (s === "hi") {
+ return true
+ }
+ return false
}
stringIsHi(string) satisfies number;
@@ -1395,8 +1990,7 @@ stringIsHi(string) satisfies number;
#### While loop unrolling
```ts
-let a = 1;
-let i = 0;
+let a: number = 1, i: number = 0;
while (i < 5) {
a *= 2;
i++;
@@ -1410,8 +2004,7 @@ a satisfies 8;
#### While loop event in the condition
```ts
-let a = 1;
-let i = 0;
+let a: number = 1, i: number = 0;
while (i++ < 5) {
a *= 2;
}
@@ -1424,7 +2017,7 @@ a satisfies 8;
#### Do while loop
```ts
-let a = 0;
+let a: number = 0;
do {
a++
} while (a < 3)
@@ -1497,8 +2090,7 @@ loop(10, "!") satisfies number;
#### Break in a while loop
```ts
-let a = 2;
-let i = 0;
+let a: number = 2, i: number = 0;
while (i++ < 10) {
a *= 2;
if (a > 5) {
@@ -1516,8 +2108,7 @@ a satisfies 2;
> With the continue the update to `a` only happens on even runs (5 times)
```ts
-let a = 2;
-let i = 0;
+let a: number = 2, i: number = 0;
while (i++ < 10) {
if (i % 2) {
continue;
@@ -1601,7 +2192,7 @@ console.log("Error caught!")
try {
throw 3
} catch (err: string) {
- console.log(err)
+ console.log(err)
}
console.log("Error caught!")
@@ -1613,11 +2204,11 @@ console.log("Error caught!")
```ts
function exceptionToResult(cb: () => number) {
- try {
- return cb()
- } catch (e) {
- return e
- }
+ try {
+ return cb()
+ } catch (e) {
+ return e
+ }
}
exceptionToResult(() => 6) satisfies 6;
@@ -1631,11 +2222,11 @@ console.log("Error caught!")
```ts
function exceptionToResult(cb: () => number) {
- try {
- cb()
- } catch (e: number) {
- return e
- }
+ try {
+ cb()
+ } catch (e: number) {
+ return e
+ }
}
exceptionToResult(() => { throw "not a number" });
@@ -1648,17 +2239,58 @@ console.log("Error caught!")
```ts
function exceptionToResult(s: string) {
- try {
- return JSON.parse(s)
- } catch (e: number) {
- return e
- }
+ try {
+ return JSON.parse(s)
+ } catch (e: number) {
+ return e
+ }
}
console.log("Error caught!")
```
- Cannot catch type number because the try block throws SyntaxError
+#### Conditional throw
+
+> This emits a warning if a throw was created in a conditional branch
+
+```ts
+// no complex numbers :(
+function checkedLn(x: number) {
+ if (x > 0) {
+ return Math.log(x)
+ } else {
+ throw new Error("Cannot log")
+ }
+}
+
+// Fine
+try { checkedLn(Math.E ** 3) satisfies 3 } catch {}
+// Will throw
+try { checkedLn(-5) } catch {}
+```
+
+- Conditional '[Error] { message: \"Cannot log\" }' was thrown in function
+
+#### Throw through internal callback
+
+```ts
+try {
+ [1, 2, 3].map((x: number) => {
+ if (x === 2) {
+ throw "error"
+ }
+ });
+ console.log("unreachable")
+} catch (e) {
+ e satisfies number;
+}
+```
+
+- Conditional '"error"' was thrown in function
+- Unreachable statement
+- Expected number, found "error"
+
### Collections
> Some of these are built of exiting features.
@@ -1667,9 +2299,9 @@ console.log("Error caught!")
#### Array push
```ts
-const x = [1]
-x.push("hi")
-x[1] satisfies 3
+const x = [1];
+x.push("hi");
+x[1] satisfies 3;
x.length satisfies 4;
```
@@ -1695,18 +2327,31 @@ x.push("hi");
- Argument of type \"hi\" is not assignable to parameter of type number
-#### `map` and `filter`
+#### Array map
> TODO other arguments (index and `this`)
```ts
[6, 8, 10].map(x => x + 1) satisfies [7, 8, 11];
-
-[1, 2, 3].filter(x => x % 2 === 0) satisfies [2];
```
- Expected [7, 8, 11], found [7, 9, 11]
+#### Mutation
+
+> This is part of [assignment mismatch](https://github.com/kaleidawave/ezno/issues/18)
+
+```ts
+function fakeRead(a: Array) {
+ a.push(2)
+}
+
+const array1: Array = []
+fakeRead(array1)
+```
+
+- Invalid assignment through parameter
+
### Statements, declarations and expressions
> Some of these are part of synthesis, rather than checking
@@ -1856,7 +2501,7 @@ y.not_a_key
```
- Expected 3, found 7
-- No property 'not_a_key' on { ezno: 7 }.
+- No property 'not_a_key' on { ezno: 7 }
#### Shorthand object literal
@@ -1925,15 +2570,34 @@ type X = { a: string }
- Type { b: "NaN" } is not assignable to type X
-#### TDZ in statements
+#### As casts
+
+> Disabled normally, allowed for these tests. Provides TSC compatibility and because narrowing not implemented (including secret feature)
```ts
-let first = second;
+declare let global: any;
-let second = 2;
+5 as boolean;
+global satisfies boolean;
+(global as string) satisfies number;
```
-- Variable 'second' used before declaration
+- Cannot cast 5 to boolean
+- Expected boolean, found any
+- Expected number, found string
+
+#### Non-null assertions
+
+> TODO this currently only works on conditionals
+
+```ts
+declare const global: { property?: string };
+
+global.property satisfies string | undefined;
+global.property! satisfies number;
+```
+
+- Expected number, found string
#### `typeof` operator
@@ -1968,16 +2632,16 @@ s satisfies number;
```ts
const o = { a: 1, b: { c: 3 } };
-let a, b, c;
+let a, b, d;
({
- c = o.a++,
- b: { c: b = 7 },
- a,
+ d = o.a++,
+ b: { c: b = 7 },
+ a,
} = o);
a satisfies string;
b satisfies boolean;
-c satisfies 3;
+d satisfies 3;
```
- Expected string, found 2
@@ -1986,7 +2650,7 @@ c satisfies 3;
#### `import.meta`
-> Unfortunately because of bundling `url` and `resolve` cannot have known results so just `string`.
+> Unfortunately because of bundling `url` and `resolve` cannot have known results so just `string`
```ts
import.meta.url satisfies number;
@@ -2054,9 +2718,9 @@ x.value satisfies string
```ts
class X {
- method() {
- return this;
- }
+ method() {
+ return this;
+ }
}
const { method } = new X();
@@ -2178,15 +2842,26 @@ class X { }
- Variable 'X' used before declaration
+#### Called without new
+
+> Declared same as `let` and `const`
+
+```ts
+class X { }
+const x = X();
+```
+
+- Class constructor must be called with new
+
#### Class type `extends`
```ts
class BaseClass {
- b: boolean = false
+ b: boolean = false
}
class Class extends BaseClass {
- a: number = 2
+ a: number = 2
}
new Class().b satisfies 5
@@ -2198,11 +2873,11 @@ new Class().b satisfies 5
```ts
class X {
- static x = 2;
+ static x = 2;
- static {
- const property: 4 = ++this.x;
- }
+ static {
+ const property: 4 = ++this.x;
+ }
}
X.x satisfies 3;
@@ -2214,19 +2889,19 @@ X.x satisfies 3;
```ts
function doThingWithClass(instance: Class) {
- instance.prop satisfies string;
- instance.parent_prop satisfies boolean;
- instance.method(4);
+ instance.prop satisfies string;
+ instance.parent_prop satisfies boolean;
+ instance.method(4);
}
class BaseClass {
- parent_prop: number
+ parent_prop: number
}
class Class extends BaseClass {
- prop: number
+ prop: number
- method(s: string) {}
+ method(s: string) {}
}
```
@@ -2238,16 +2913,16 @@ class Class extends BaseClass {
```ts
function doThingWithClass(instance: Class) {
- instance.a satisfies number;
- instance.b satisfies string;
+ instance.a satisfies number;
+ instance.b satisfies string;
}
class BaseClass {
- b: boolean
+ b: boolean
}
class Class extends BaseClass {
- a: number
+ a: number
}
```
@@ -2258,16 +2933,16 @@ class Class extends BaseClass {
```ts
let b: number = 0;
class Y {
- constructor(a) {
- this.a = a;
- b++;
- }
+ constructor(a) {
+ this.a = a;
+ b++;
+ }
}
class X extends Y {
- constructor(a) {
- super(a);
- }
+ constructor(a) {
+ super(a);
+ }
}
const x = new X("hi");
@@ -2300,7 +2975,7 @@ type X = number;
const a: Y = 2;
```
-- Cannot find type Y. Did you mean T, U or X?
+- Could not find type 'Y'
#### Type shadowing
@@ -2348,14 +3023,16 @@ const b: X = 2;
#### Property on an or type
```ts
-function getProp(obj: { prop: 3 } | { prop: 2 }) {
+function getProp(obj: { prop: 3, prop2: 6 } | { prop: 2 }) {
+ obj.prop2;
return obj.prop
}
getProp satisfies string
```
-- Expected string, found (obj: { prop: 3 } | { prop: 2 }) => 3 | 2
+- No property 'prop2' on { prop: 3, prop2: 6 } | { prop: 2 }
+- Expected string, found (obj: { prop: 3, prop2: 6 } | { prop: 2 }) => 3 | 2
#### Generic extends
@@ -2384,10 +3061,23 @@ const y: (a: number | string) => string = (p: number) => "hi"
> I think reasons contains more information
+#### Function parameter excess allowed
+
+```ts
+// Perfectly fine
+const x: (a: number, b: string) => string = (p: number) => "hi"
+// Bad
+const y: (a: string) => string = (p: number, q: string) => "hi"
+```
+
+- Type (p: number, q: string) => "hi" is not assignable to type (a: string) => string
+
+> I think reasons contains more information
+
#### Function return type subtyping
```ts
-const x: (a: number) => number = p => 4
+const x: (a: number) => number = p => 4;
const y: (a: number) => number = p => "a number"
```
@@ -2468,22 +3158,6 @@ getSecondCharacter("string") satisfies "b";
- Expected boolean, found (s: string) => string | undefined
- Expected "b", found "t"
-#### As casts
-
-> Disabled normally, allowed for these tests. Provides TSC compatibility and because narrowing not implemented (including secret feature)
-
-```ts
-declare let global: any;
-
-5 as boolean;
-global satisfies boolean;
-(global as string) satisfies number;
-```
-
-- Cannot cast 5 to boolean
-- Expected boolean, found any
-- Expected number, found string
-
#### Symmetric or
```ts
@@ -2531,13 +3205,13 @@ function get(obj: {a: 2} | { b: 3 }) {
> `Cannot read property "a" from { b: 3 }`
-- No property 'a' on { a: 2 } | { b: 3 }.
+- No property 'a' on { a: 2 } | { b: 3 }
#### Optional interface member
```ts
interface Optional {
- a?: "hi"
+ a?: "hi"
}
const op1: Optional = {}
@@ -2556,19 +3230,43 @@ type Y = string & number;
- No intersection between types 2 and "hi"
- No intersection between types string and number
+#### Cyclic type alias
+
+```ts
+type Node = { parent: Node, value: T } | null;
+
+null satisfies Node;
+({ parent: { parent: { parent: null, value: 2 }, value: 6 }, value: 2 } satisfies Node);
+({ parent: { parent: { parent: null, value: "hi" }, value: 6 }, value: "hi" } satisfies Node);
+```
+
+- Expected { parent: Node\, value: string } | null, found { parent: { parent: { parent: null, value: "hi" }, value: 6 }, value: "hi" }
+
+#### Cyclic type alias check
+
+```ts
+type X = Y;
+type Y = X;
+
+// test usage doesn't blow up subtyping
+const x: X = 2;
+```
+
+- Circular type reference
+
#### Interface extends
```ts
interface X {
- a: string
+ a: string
}
interface Y {
- b: string
+ b: string
}
interface Z extends X, Y {
- c: string
+ c: string
}
({ a: "", b: "", c: "hello" }) satisfies Z;
@@ -2579,6 +3277,18 @@ interface Z extends X, Y {
- Expected Z, found { a: "", b: 4, c: "hello" }
- Expected Z, found { c: "hi" }
+#### `never` subtyping
+
+```ts
+function getSpecialNumber(): number {
+ throw "to implement!"
+}
+
+getSpecialNumber satisfies string;
+```
+
+- Expected string, found () => number
+
#### Specialisation of return for declare functions
```ts
@@ -2632,14 +3342,14 @@ func() satisfies boolean;
```ts
type ElementOf = T extends Array ? U : never;
-declare let y: ElementOf>;
-declare let z: ElementOf | string>;
+declare let elementOfNumber: ElementOf>;
+declare let elementOfNumberOrString: ElementOf<"not array" | Array>;
-y satisfies number;
-z satisfies string;
+elementOfNumber satisfies number;
+elementOfNumberOrString satisfies string;
declare let n: never;
-n satisfies ElementOf;
+n satisfies ElementOf<"not array">;
```
- Expected string, found number
@@ -2648,13 +3358,11 @@ n satisfies ElementOf;
```ts
interface X {
- a: string,
- b: string
+ a: string,
+ b: string
}
-"a" satisfies keyof X;
-"b" satisfies keyof X;
-"c" satisfies keyof X;
+"a" satisfies keyof X; "b" satisfies keyof X; "c" satisfies keyof X;
```
- Expected keyof X, found "c"
@@ -2673,11 +3381,67 @@ const second: Introduction = "Hi Ben";
#### Assigning to types as keys
```ts
-const x: { [s: string]: number } = { a: 1, b: 2, c: 3 }
-const y: { [s: string]: boolean } = { a: 1, b: 2, c: 3 };
+const obj = { a: 1, b: 2, c: 3 };
+obj satisfies { [s: string]: number };
+obj satisfies { [s: string]: boolean };
+```
+
+- Expected { [string]: boolean }, found { a: 1, b: 2, c: 3 }
+
+#### String slice matching pattern
+
+```ts
+type GetPrefix = S extends `${infer T} ${End}` ? T : false;
+
+4 satisfies GetPrefix<"Hello Ben", "Ben">;
+```
+
+- Expected "Hello", found 4
+
+#### `infer ... extends ...`
+
+```ts
+type X = T extends { a: infer I extends string } ? I : string;
+
+declare let a: X<{ a: 4 }>;
+declare let b: X<{ a: "hello" }>;
+
+a satisfies number;
+b satisfies "hello";
+```
+
+- Expected number, found string
+
+#### TSC string intrinsics
+
+```ts
+const a: Uppercase<"something" |"hi"> = "HI";
+const b: Uppercase = "hi"
+```
+
+- Type \"hi\" is not assignable to type Uppercase\
+
+#### `NoInfer`
+
+```ts
+declare function func(a: T, b: NoInfer): T;
+
+func("hi", "hello") satisfies number;
+```
+
+> but not `| "hello"` !!!
+
+- Expected number, found "hi"
+
+#### Subtyping edge cases
+
+```ts
+"hi" satisfies { length: 3 };
+"hi" satisfies { length: 2 };
+(() => {}) satisfies Function;
```
-- Type { a: 1, b: 2, c: 3 } is not assignable to type { [string]: boolean }
+- Expected { length: 3 }, found "hi"
### Generic types
@@ -2688,17 +3452,18 @@ interface Wrapper {
internal: T
}
-const my_wrapped: Wrapper = { internal: "hi" }
+({ internal: "hi" } satisfies Wrapper);
+({ internal: "hi" } satisfies Wrapper);
```
-- Type { internal: "hi" } is not assignable to type Wrapper\
+- Expected Wrapper\, found { internal: \"hi\" }
#### Array property checking
```ts
const numbers1: Array = [1, 2, "3"],
- numbers2: Array = ["hi", "3"],
- numbers3: Array = 4;
+ numbers2: Array = ["hi", "3"],
+ numbers3: Array = 4;
```
- Type [1, 2, "3"] is not assignable to type Array\
@@ -2811,14 +3576,14 @@ myRecord.hello;
```
- Expected string, found number
-- No property 'hello' on { [\"hi\"]: number }.
+- No property 'hello' on { [\"hi\"]: number }
#### Assignment
```ts
const x: Record<"test", boolean> = { no: false },
- y: Record<"test", boolean> = { test: 6 },
- z: Record<"test", boolean> = { test: false };
+ y: Record<"test", boolean> = { test: 6 },
+ z: Record<"test", boolean> = { test: false };
```
- 'no' is not a property of { [\"test\"]: boolean }
@@ -2834,14 +3599,149 @@ obj1.hi satisfies boolean;
obj1.hello satisfies boolean;
obj1.bye;
+```
+
+- No property 'bye' on { ["hi" | "hello"]: boolean }
+
+#### Readonly and optionality carries through
+
+```ts
+type Mapped = {
+ [P in keyof T]: T[P]
+}
+
+interface Y { readonly a: string, b?: number }
+declare let x: Mapped;
+
+x.a = "hi";
+x.b satisfies number;
+```
+
+- Cannot write to property 'a'
+- Expected number, found number | undefined
+
+#### Specialisation
+
+```ts
+type Pick = {
+ [P in K]: T[P];
+};
+
+interface X { a: number, b: string, c: string }
+
+({ a: 5 } satisfies Pick);
+({ a: 5 } satisfies Pick);
+({ a: 5, b: "hi" } satisfies Pick);
+({ a: 5, b: 6 } satisfies Pick);
+
+declare let y: Pick;
+y.b satisfies string;
+y.a;
+```
+
+- Expected { ["a" | "b"]: X["a" | "b"] }, found { a: 5 }
+- Expected { ["a" | "b"]: X["a" | "b"] }, found { a: 5, b: 6 }
+- No property 'a' on { ["b" | "c"]: X["b" | "c"] }
+
+#### Optional
+
+```ts
+type Partial = {
+ [P in keyof T]?: T[P];
+};
+
+({ a: 3 } satisfies Partial<{ a: number, b: string }>);
+({ a: "hi" } satisfies Partial<{ a: number, b: string }>)
+```
+
+- Expected { [keyof { a: number, b: string }]?: { a: number, b: string }[keyof { a: number, b: string }] }, found { a: \"hi\" }
+
+#### Negated optionality
+
+```ts
+type Required = {
+ [P in keyof T]-?: T[P];
+};
+
+({ a: 3 } satisfies Required<{ a?: number }>);
+// Bad
+({ } satisfies Required<{ a?: number }>);
+```
+
+- Expected { [keyof { a?: number }]: { a?: number }[keyof { a?: number }] }, found {}
+
+#### Readonly
+
+```ts
+type Mutable = {
+ readonly [P in keyof T]: T[P];
+};
-declare let obj2: Record;
-obj2.fine satisfies boolean;
-obj2[2];
+interface Y { a: string }
+declare let x: Mutable;
+x.a = "hi";
```
-- No property 'bye' on { ["hi" | "hello"]: boolean }.
-- No property '2' on { [string]: boolean }.
+- Cannot write to property 'a'
+
+#### Negated readonly
+
+```ts
+type Mutable = {
+ -readonly [P in keyof T]: T[P];
+};
+
+interface Y { readonly a: string }
+declare let x: Mutable;
+x.a = "hi";
+x.a = 4;
+```
+
+- Type 4 does not meet property constraint "hi"
+
+#### `as` rewrite
+
+```ts
+type PrefixKeys = {
+ [P in ((keyof T) & string) as `property_${P}`]: T[P];
+};
+
+interface X { a: number };
+
+declare let x: PrefixKeys;
+x.property_a satisfies number;
+x.property_b
+```
+
+- No property 'property_b' on { [string]: X[keyof X & string] }
+
+### Readonly and `as const`
+
+> TODO constrained inference
+
+#### Readonly parameter
+
+```ts
+function x(p: readonly { a: string }) {
+ p.a = "hi";
+}
+```
+
+- Cannot write to property 'a'
+
+#### Readonly to readonly
+
+```ts
+function func1(p: { a: string, b: string }) {
+ func2(p)
+}
+function func2(p: readonly { a: string }) { }
+
+const obj = Object.freeze({ a: "hi" });
+func2(obj)
+```
+
+- Argument of type { a: string, b: string } is not assignable to parameter of type Readonly<{ a: string }>
### Forward inference
@@ -2853,13 +3753,13 @@ obj2[2];
```ts
interface MyObject {
- a(b: string): any;
+ a(b: string): any;
}
const obj: MyObject = {
- a(b) {
- b satisfies number;
- }
+ a(b) {
+ b satisfies number;
+ }
}
```
@@ -2869,11 +3769,11 @@ const obj: MyObject = {
```ts
function callFunction(fn: (p: T) => void) {
- // ...
+ // ...
}
callFunction(a => {
- a satisfies number;
+ a satisfies number;
})
```
@@ -2917,20 +3817,20 @@ obj1.a = "hello";
```ts
{
- const obj = { a: true, b: false };
- const x: { a: boolean } = obj, y: { b: boolean } = obj;
+ const obj = { a: true, b: false };
+ const x: { a: boolean } = obj, y: { b: boolean } = obj;
- obj.a = "yo";
- obj.b = "wassup";
+ obj.a = "yo";
+ obj.b = "wassup";
}
{
- // and in the same assignment through a cycle
- const obj = { a: 2, b: 3 }; obj.c = obj;
- const something: { a: number, c: { b: number } } = obj;
+ // and in the same assignment through a cycle
+ const obj = { a: 2, b: 3 }; obj.c = obj;
+ const something: { a: number, c: { b: number } } = obj;
- obj.a = "hi";
- obj.b = "hello";
+ obj.a = "hi";
+ obj.b = "hello";
}
```
@@ -2996,6 +3896,88 @@ const p_of_x = Object.getPrototypeOf(x);
- Expected string, found true
+### `Proxy`
+
+#### Proxy get
+
+```ts
+const proxy1 = new Proxy({ a: 2 }, { get(target: { a: number }, prop: string, receiver) {
+ if (prop === "a") {
+ return target["a"] + 1
+ }
+} } );
+
+proxy1.a satisfies string;
+```
+
+- Expected string, found 3
+
+#### Proxy set
+
+```ts
+let lastSet: string = "";
+const proxy1 = new Proxy({ a: 2 }, {
+ set(target: { a: number }, prop: string, value: number, receiver) {
+ lastSet = prop;
+ }
+});
+
+proxy1.a = 6;
+lastSet satisfies boolean;
+```
+
+- Expected boolean, found "a"
+
+#### Proxy handler fallthrough
+
+```ts
+const obj = { a: 2 };
+const proxy1 = new Proxy(obj, { });
+
+proxy1.a = 6;
+obj.a satisfies 6;
+proxy1.a satisfies string;
+```
+
+- Expected string, found 6
+
+#### Proxy subtyping
+
+```ts
+const proxy1 = new Proxy({}, { get(_target, prop, _recivier) { return prop } });
+
+proxy1 satisfies { a: "a", b: "b" };
+proxy1 satisfies { c: "d" };
+```
+
+- Expected { c: "d" }, found Proxy [ {}, { get: (_target: any, prop: any, _recivier: any) => any } ]
+
+#### Proxy across functions
+
+```ts
+function makeObservable(obj, cb: (kind: string, prop: string, value: any) => void) {
+ return new Proxy(obj, {
+ get(on, prop: string, _rec) {
+ cb("get", prop, on[prop])
+ },
+ set(on, prop: string, _value, _rec) {
+ cb("set", prop, on[prop])
+ },
+ })
+}
+
+let r = null;
+const value = makeObservable({ a: 1 }, (k, p, v) => {
+ r = { k, p, v };
+});
+
+r satisfies null;
+value.a = 2;
+r satisfies string;
+```
+
+- Expected string, found { k: "set", p: "a", v: 1 }
+
### Imports and exports
#### Import and export named
@@ -3086,7 +4068,7 @@ incrementCounter();
counter satisfies string;
// in mutable.ts
-export let counter = 2;
+export let counter: number = 2;
export function incrementCounter() {
counter++
}
@@ -3119,7 +4101,7 @@ console.log(a.prop);
export const a = 2;
```
-- Cannot find ./two.
+- Cannot find ./two
#### Import where export does not exist
@@ -3190,7 +4172,7 @@ export const x = 2;
const y = "122LH"
```
-- Could not find variable 'y' in scope. Did you mean x?
+- Could not find variable 'y' in scope
#### Import side effect
@@ -3227,7 +4209,7 @@ mean_gravity satisfies 2;
// in node_modules/earth/package.json
{
- "main": "constants.js"
+ "main": "constants.js"
}
// in node_modules/earth/constants.js
@@ -3236,9 +4218,9 @@ export const mean_gravity = 9.806;
- Expected 2, found 9.806
-### Extras
+### Stability
-> This contains new features. Most are WIP
+> What to do in the occurance of a type error
#### Use type annotation in the presence of error
@@ -3249,7 +4231,7 @@ const x: string = 5;
const y: string = h;
function getString(a: number): string {
- return a
+ return a
}
x satisfies string;
@@ -3260,50 +4242,36 @@ const z: number = getString(2);
- Cannot return number because the function is expected to return string
- Type 5 is not assignable to type string
-- Could not find variable 'h' in scope. Did you mean x, y or z?
+- Could not find variable 'h' in scope
- Type (error) string is not assignable to type number
-#### Unconditional throw
-
-```ts
-function safeDivide(num: number, denom: number) {
- if (denom === 0) {
- throw new Error("Cannot divide by zero");
- }
- return num / denom
-}
-
-safeDivide(8, 4) satisfies 2;
-
-safeDivide(10, 0);
-```
-
-- Conditional '[Error] { message: \"Cannot divide by zero\" }' was thrown in function
+#### Errors carries
-#### Unreachable statement
+> Note only one error raised. This prevents the compiler presenting loads of errors if an origin is invalid
```ts
-function throwGreeting() {
- throw "Hello";
- return "Unreachable!"
-}
+const obj = { prop: 2 };
+console.log(obj.a.b.c);
-function doSomething() {
- throwGreeting()
- const unreachable = 2;
+function x() {
+ return y
}
+
+x().nothing
```
-> One is for `return 5` the other is for `const x = 2;`
+- Could not find variable 'y' in scope
+- No property 'a' on { prop: 2 }
-- Unreachable statement
-- Unreachable statement
+### Extras
+
+> This contains new features. Most are WIP
#### JSX type
```tsx
function JSXH(tag_name: string, attributes: any, children: any) {
- return { tag_name, attributes, children }
+ return { tag_name, attributes, children }
}
const x = Hello World
satisfies string;
@@ -3315,7 +4283,7 @@ const x = Hello World
satisfies string;
```ts
function x(a /** string */) {
- a satisfies number
+ a satisfies number
}
const c /** number */ = "hello"
@@ -3328,7 +4296,7 @@ const c /** number */ = "hello"
```ts
function register(a: Literal) {
- // ...
+ // ...
}
register("something")
@@ -3338,20 +4306,68 @@ register(document.title)
- Argument of type string is not assignable to parameter of type Literal\
-#### Errors carries
+#### Number intrinsics
-> Note only one error raised. This prevents the compiler presenting loads of errors if an origin is invalid
+```ts
+5 satisfies MultipleOf<2>;
+4 satisfies MultipleOf<2>;
+
+6 satisfies GreaterThan<2>;
+-4 satisfies GreaterThan<2>;
+
+6 satisfies LessThan<2>;
+-4 satisfies LessThan<2>;
+```
+
+- Expected MultipleOf\<2\>, found 5
+- Expected GreaterThan\<2\>, found -4
+- Expected LessThan\<2\>, found 6
+
+#### `Not`
```ts
-const obj = { prop: 2 };
-console.log(obj.a.b.c);
+declare let a: number;
+4 satisfies Not<4>;
+6 satisfies Not<4>;
+a satisfies Not<8>;
+2 satisfies Not;
+"hi" satisfies Not;
-function x() {
- return y
-}
+declare let b: Not<5> & number;
+b satisfies number;
+b satisfies string;
+b satisfies 5;
+```
-x().nothing
+- Expected Not<4>, found 4
+- Expected Not<8>, found number
+- Expected Not\, found "hi"
+- Expected string, found Not<5> & number
+- Expected 5, found Not<5> & number
+
+#### `Exclusive`
+
+```ts
+interface X { a: number }
+const x = { a: 1, b: 2 };
+
+x satisfies Exclusive;
+({ a: 6 } satisfies Exclusive);
+```
+
+- Expected Exclusive\, found { a: 1, b: 2 }
+
+#### `CaseInsensitive`
+
+```ts
+"Hi" satisfies CaseInsensitive<"hi">;
+"Hello" satisfies CaseInsensitive<"hi">;
+
+// yeah
+type CIWord = "WORD" extends Uppercase ? T : never;
+"wOrd" satisfies CIWord;
+"wood" satisfies CIWord;
```
-- Could not find variable 'y' in scope. Did you mean x?
-- No property 'a' on { prop: 2 }.
+- Expected CaseInsensitive<"hi">, found "Hello"
+- Expected CIWord, found "wood"
diff --git a/checker/specification/test.rs b/checker/specification/test.rs
index c597d974..85166a18 100644
--- a/checker/specification/test.rs
+++ b/checker/specification/test.rs
@@ -34,6 +34,8 @@ mod specification {
// const SIMPLE_DTS: Option<&str> = Some(include_str!("../definitions/simple.d.ts"));
const SIMPLE_DTS: Option<&str> = None;
+const IN_CI: bool = option_env!("CI").is_some();
+
/// Called by each test
fn check_errors(
heading: &'static str,
@@ -63,31 +65,37 @@ fn check_errors(
// eprintln!("{:?}", code);
// let result = panic::catch_unwind(|| {
- eprintln!("{:?}", std::env::current_dir());
+
+ if IN_CI {
+ eprintln!("::group::Running {heading}");
+ }
+
let definition_file_name: PathBuf = if SIMPLE_DTS.is_some() {
"./checker/definitions/simple.d.ts".into()
} else {
checker::INTERNAL_DEFINITION_FILE_PATH.into()
};
- let type_definition_files = std::iter::once(definition_file_name.clone()).collect();
+ let type_definition_files = vec![definition_file_name.clone()];
+
+ let resolver = |path: &Path| -> Option> {
+ if path == definition_file_name.as_path() {
+ Some(SIMPLE_DTS.unwrap().to_owned().into_bytes())
+ } else if code.len() == 1 {
+ Some(code[0].1.to_owned().into())
+ } else {
+ code.iter()
+ .find_map(|(code_path, content)| {
+ (std::path::Path::new(code_path) == path)
+ .then_some(content.to_owned().to_owned())
+ })
+ .map(Into::into)
+ }
+ };
let result = checker::check_project::<_, EznoParser>(
vec![PathBuf::from("main.tsx")],
type_definition_files,
- |path: &Path| -> Option> {
- if path == definition_file_name.as_path() {
- Some(SIMPLE_DTS.unwrap().to_owned().into_bytes())
- } else if code.len() == 1 {
- Some(code[0].1.to_owned().into())
- } else {
- code.iter()
- .find_map(|(code_path, content)| {
- (std::path::Path::new(code_path) == path)
- .then_some(content.to_owned().to_owned())
- })
- .map(Into::into)
- }
- },
+ resolver,
type_check_options,
(),
None,
@@ -111,6 +119,10 @@ fn check_errors(
})
.collect();
+ if IN_CI {
+ eprintln!("::endgroup::");
+ }
+
if diagnostics != expected_diagnostics {
panic!(
"{}",
diff --git a/checker/specification/to_implement.md b/checker/specification/to_implement.md
index a4bbfa75..2cd6dc0f 100644
--- a/checker/specification/to_implement.md
+++ b/checker/specification/to_implement.md
@@ -3,19 +3,18 @@
### Types
-#### Resolving value by property on dependent
+#### Array slice matching pattern
```ts
-function getProperty(property: "a" | "b" | "c") {
- return { a: 1, b: 2, c: 3 }[property]
-}
+type Head = T extends [infer H, ...Array] ? H : never;
+
+type Tail = T extends [any, ...infer Tail] ? Tail : [];
-getProperty("d")
-getProperty("c") satisfies 2
+1 satisfies Head<[1, 2, 3, 4, 5]>;
+[2, 3, 4, 5] satisfies Tail<[1, 2, 3, 4, 5]>;
```
-- Expected "a" | "b" | "c" found "d"
-- Expected 2 found 3
+- Expected 1, found 2
#### Generic type argument restriction
@@ -56,21 +55,31 @@ print_type(mapper)
- TODO
-#### Function calling
+#### Calling or
-#### Calling on or type
+> *Calling* is distributive `(A | B)()` -> `A() | B()`
```ts
-type Func1 = () => 3;
-type Func2 = () => 2;
-function callFunc(func: (() => T) | (() => U)): 3 | 2 {
- return func()
+function a(p: string) { return 2 }
+function b(p: string) { return 4 }
+
+function c(c: boolean) {
+ const func = c ? a : b;
+ const result = func()
+ result satisfies string;
+ return result
}
+```
+
+- Expected string, found 2 | 4
-print_type(callFunc)
+#### Setter or
+
+```ts
+TODO
```
-- Expected "a" | "b" | "c" found "d"
+- Expected string, found 2 | 4
#### Getter and setter through function
@@ -93,6 +102,29 @@ x({ get b() { return 2 } })
- Expected string, found 5
+#### Spread object constraint
+
+```ts
+let { a, ...x }: { a: number, b: string } = { a: "hi", b: "hello" };
+x = 1;
+```
+
+- 1 is not assignable to { b: string }
+
+#### Known symbol inference & checking
+
+> #TODO extra
+
+```ts
+class X {
+ [Symbol.iterator]() {
+ return {}
+ }
+}
+```
+
+- ?
+
### Imports
#### Import package with definition file
@@ -177,22 +209,6 @@ function func(array: Array) {
- Expected number found string
-#### Order of properties
-
-> TODO this is because setting properties are simply appended. There are two straightforward fixes, but I am unsure which one is better...
-
-```ts
-const obj = { a: 1, b: 2 };
-obj.a = 2; obj.c = 6; obj.b = 4;
-let properties: string = "";
-for (const property in obj) {
- properties += property;
-}
-properties satisfies boolean;
-```
-
-- Expected boolean, found "abc"
-
### Inference
#### Parameter property
@@ -334,10 +350,6 @@ function x*() {
- TODO
-### `Proxy` and `Object`
-
-> TODO effects, different traps and `Object.defineProperty`
-
### Collections
#### `Array.fill`
@@ -367,6 +379,144 @@ declare let aNumber: number;
- Expected string, found boolean
+#### Array filter
+
+```ts
+[1, 2, 3].filter(x => x % 2 == 0) satisfies [2];
+```
+
+- ?
+
+#### Array slice
+
+```ts
+[1, 2, 3, 4, 5].slice(3) satisfies something;
+```
+
+- ?
+
+#### Array splice
+
+```ts
+const array = [1, 2, 3, 4, 5];
+array.splice(2);
+array satisfies something;
+```
+
+- ?
+
+#### Array shift and unshift
+
+```ts
+const array = [1, 2, 3];
+array.shift() satisfies not1;
+```
+
+- ?
+
+#### Array copying methods
+
+```ts
+// toReversed
+// with
+```
+
+- ?
+
+#### Array find & index of
+
+```ts
+// indexOf
+// lastIndexOf
+// find
+// findIndexOf
+```
+
+- ?
+
+#### Array constructor
+
+```ts
+new Array({ length: 5 }, (_, i) => i) satisfies string
+```
+
+- ?
+
+#### Array `concat` and spread push
+
+```ts
+// concat()
+// push(...x)
+```
+
+- ?
+
+#### Array values and entries
+
+> Iterators
+
+```ts
+const x =[1, 2, 3].values();
+```
+
+- ?
+
+#### Array flat
+
+```ts
+// flatten
+// flatMap
+```
+
+- ?
+
+#### Array reducers
+
+> May be hard bc reversed generics order
+
+```ts
+// reduce
+// reduceRight
+```
+
+- ?
+
+#### Map `set` and `get`
+
+```ts
+const x = new Map();
+x.set(4, 2);
+x.set(4, 3);
+x.get(4) satisfies 2;
+x.get(2) satisfies string;
+```
+
+- Expected 2, found 3
+- Expected string, found undefined
+
+#### Map `items`
+
+```ts
+const x = new Map();
+x.items()
+```
+
+- ?
+
+#### Map generics
+
+```ts
+const x: Map = new Map();
+x.set(4, false);
+
+const y = new Map();
+y.set(6, 2);
+y.set(4, "hi");
+y satisfies string;
+```
+
+- Expected string, found Map<6 | 4, 2 | "hi">
+
### Expressions
#### Bad arithmetic operator
@@ -417,33 +567,6 @@ declare const global: { a?: string };
- Expected string, found boolean
- Expected 2, found string | undefined
-#### Delete from required propertied
-
-```ts
-declare let global: { a?: string, b: string };
-
-// Fine
-delete global.a;
-// Bad
-delete global.b;
-```
-
-- Cannot delete property "b" off { a?: string, b: string }
-
-#### Optional property access
-
-```ts
-interface X {
- possibly?: string
-}
-
-declare let x: X;
-
-x?.possibly satisfies number;
-```
-
-- Expected string, found string | undefined
-
### Runtime
#### Free variable + anytime calls
@@ -517,6 +640,21 @@ class Rectangle implements Draw {
- Class "MyNumber", does not implement draw
- Expected string, found CanvasRenderingContext2D
+#### Via effect
+
+```ts
+function newClass(property, value) {
+ return class {
+ [property] = value
+ }
+}
+
+new (newClass("hello", 2)).hello satisfies 2;
+new (newClass("hi", 6)).hi satisfies string;
+```
+
+- Expected string, found 6
+
### Recursion
#### Application
@@ -571,18 +709,6 @@ a satisfies 3; b satisfies string;
### Functions
-#### No generics
-
-```ts
-function id(a) { return a }
-
-id<5>(4)
-```
-
-- Cannot pass generic arguments to function without generic arguments
-
-> Or at least explicit generic arguments
-
#### Method overloading
```ts
@@ -671,63 +797,173 @@ if (b === "hi") {
- Expected "hello", found "hi"
-### Mapped types
+#### Optional property access
-#### Specialisation
+```ts
+interface X {
+ a: string
+ b: string
+}
+
+declare let x: X | null;
+
+x.a;
+x?.b satisfies number;
+```
+
+- Cannot get 'a' on null
+- Expected number, found string
+
+### Generics
+
+#### Out of order generics
```ts
-type Pick = {
- [P in K]: T[P];
-};
+function func(cb: (t: T) => number, value: T) { }
-interface X { a: number, b: string, c: string }
+func(cb => { cb satisfies boolean }, "hi")
+```
-const x: Pick = { a: 5 };
+- Expected boolean, found "hi"
-({ b: "string" }) satisfies Pick;
+### Broken
+
+> Was working, now broken (or removed)
+
+#### Readonly property
+
+> Should be working but parser current wraps `a` as `Readonly` :(
+
+```ts
+function x(p: { readonly a: string, b: string }) {
+ p.a = "hi";
+ p.b = "hi";
+}
```
-- TODO
+- Cannot write to property 'a'
-#### Optional
+#### Destructuring using iterator
```ts
-type Partial = {
- [P in keyof T]?: T[P];
-};
+const [a, b, c] = {
+ [Symbol.iterator]() {
+ return {
+ count: 0,
+ next(this: { count: number }) {
+ return { value: this.count++, done: false }
+ }
+ }
+ }
+}
-const x: Partial<{ a: number, b: string }> = { a: 3 },
- y: Partial<{ a: number, b: string }> = { a: "hi" }
+a satisfies 0; b satisfies string;
```
-- Cannot assign { a: "hi" }
+- Expected string, found 1
-#### Negated
+#### Always known math
```ts
-type Required = {
- [P in keyof T]-?: T[P];
-};
+function func(a: number) { return a ** 0 }
+
+print_type(func)
-const x: Required<{ a?: number }> = { a: 3 },
- y: Required<{ a?: number }> = { };
+declare let x: NotNotANumber;
+
+print_type(x ** 1 === x)
```
-- Cannot assign { } to required
+- Expected string, found 1
+- True
-### Readonly and `as const`
+#### Less than checks
-> TODO constrained inference
+```ts
+function x(a: GreaterThan<4>) {
+ (a < 3) satisfies false;
+ (a < 10) satisfies string;
+}
+```
-#### Readonly parameter
+- Expected string, found boolean
+
+#### Tagged template literal
+
+> Waiting for parser definition updated to make this easier
+
+```ts
+function myTag(static_parts: Array, other: string) {
+ return { static_parts, other }
+}
+
+const name = "Ben";
+myTag`${name}Hello ` satisfies string
+```
+
+- Expected string, found { static_parts: ["", "Hello "], other: "Ben" }
+
+### Control flow
+
+#### Conditional break
+
+```ts
+function getNumber(a: number) {
+ for (let i = 0; i < 10; i++) {
+ if (i === a) {
+ return "found"
+ }
+ }
+ return "not-found"
+}
+
+getNumber(4) satisfies "found";
+getNumber(100) satisfies boolean;
+```
+
+- Expected boolean, found "not-found"
+
+#### *Inconclusive* conditional update
```ts
-function x(p: readonly { a: string }) {
- p.a = 5;
+declare var value: string;
+let a: string | number = 0;
+
+function conditional(v: string) {
+ if (v === "value") {
+ a = "hi"
+ }
}
+conditional(value);
+a satisfies string;
```
-- Cannot assign to immutable property
+- Expected string, found "hi" | 0
+
+#### Break with label
+
+> Note the numbers here, if they are larger they break over the `max_inline` limit and get different results below
+
+```ts
+let a: number = 0;
+let result;
+
+top: while (a++ < 8) {
+ let b: number = 0;
+ while (b++ < 8) {
+ if (a === 3 && b === 2) {
+ result = a * b;
+ break top
+ }
+ }
+}
+
+a satisfies string;
+result satisfies boolean;
+```
+
+- Expected string, found 3
+- Expected boolean, found 6
### Closures
@@ -802,97 +1038,46 @@ x.a = "hi"
- Cannot assign. Restricted to number
-### Exceptions and `try-catch-finally`
-
-#### Conditional throw
+### Others
-> This emits a warning if a throw was created in a conditional branch
+#### Pure getter assignment
```ts
-// no complex numbers :(
-function checkedLn(x: number) {
- if (x > 0) {
- return Math.log(x)
- } else {
- throw new Exception("Cannot log long string")
- }
+function func(p: Pure<{ prop: number }>) {
+ p.prop = 2;
+ p.prop satisfies string;
}
-
-// Fine
-try { checkedLn(Math.E ** 3) satisfies 3 } catch {}
-// Will throw
-try { checkedLn(-5) } catch {}
```
-- Conditional 'Exception' was thrown in function
-
-### Broken
-
-> Was working, now not
+- Expected string, found 2
-#### `find` and `includes`
+#### Deep readonly
-> TODO other arguments (index and `this`). and poly
+> TODO implement use mapped type `T & { [P in keyof T]: Readonly }`
```ts
-[1, 2, 3].find(x => x % 2 === 0) satisfies 4;
-
-// [1, 2, 3].includes(6) satisfies string;
+declare const obj: DeepReadonly<{ a: { b: { c: 2 } } };
+obj.a.b.c = 2;
```
-- Expected 4, found 2
-
+- Cannot assign to readonly
-#### Conditional return type inference
+#### Conditionality destructuring from poly
```ts
-function func(a: boolean) {
- if (a) {
- return 2
- }
-}
-
-func satisfies (a: boolean) => 5;
+declare let x: { a?: 1 }; // also { a: 1 } | { b: 2 }
+let { a = 2 } = x;
+a satisfies 3;
```
-- Expected (a: boolean) => 5, found (a: boolean) => 2 | undefined
+- Expected 3, found 1 | 2
-#### *Inconclusive* conditional update
-
-```ts
-declare var value: string;
-let a: string | number = 0;
-
-function conditional(v: string) {
- if (v === "value") {
- a = "hi"
- }
-}
-conditional(value);
-a satisfies string;
-```
-
-- Expected string, found "hi" | 0
+### RegExp
-#### Break with label
+#### Regexp patterns
```ts
-let a: number = 0;
-let result;
-
-top: while (a++ < 10) {
- let b: number = 0;
- while (b++ < 10) {
- if (a === 3 && b === 2) {
- result = a * b;
- break top
- }
- }
-}
-
-a satisfies string;
-result satisfies boolean;
+new RegExp("x").group.string
```
-- Expected string, found 3
-- Expected boolean, found 6
+- ?
diff --git a/checker/src/context/control_flow.rs b/checker/src/context/control_flow.rs
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/checker/src/context/control_flow.rs
@@ -0,0 +1 @@
+
diff --git a/checker/src/context/environment.rs b/checker/src/context/environment.rs
index b2a5743e..c7d019be 100644
--- a/checker/src/context/environment.rs
+++ b/checker/src/context/environment.rs
@@ -2,19 +2,19 @@ use source_map::{SourceId, Span, SpanWithSource};
use std::collections::{HashMap, HashSet};
use crate::{
- context::get_on_ctx,
+ context::{get_on_ctx, information::ReturnState},
diagnostics::{
- NotInLoopOrCouldNotFindLabel, PropertyRepresentation, TypeCheckError,
+ NotInLoopOrCouldNotFindLabel, PropertyKeyRepresentation, TypeCheckError,
TypeStringRepresentation, TDZ,
},
events::{Event, FinalEvent, RootReference},
features::{
assignments::{
Assignable, AssignableArrayDestructuringField, AssignableObjectDestructuringField,
- AssignmentKind, Reference,
+ AssignmentKind, AssignmentReturnStatus, IncrementOrDecrement, Reference,
},
modules::Exported,
- objects::SpecialObjects,
+ objects::SpecialObject,
operations::{
evaluate_logical_operation_with_expression,
evaluate_pure_binary_operation_handle_errors, MathematicalAndBitwise,
@@ -23,24 +23,31 @@ use crate::{
},
subtyping::{type_is_subtype, type_is_subtype_object, State, SubTypeResult, SubTypingOptions},
types::{
- printing,
properties::{
- get_property_key_names_on_a_single_type, PropertyKey, PropertyKind, PropertyValue,
+ get_property_key_names_on_a_single_type, AccessMode, PropertyKey, PropertyKind,
Publicity,
},
PolyNature, Type, TypeStore,
},
- CheckingData, Instance, RootContext, TypeCheckOptions, TypeId,
+ CheckingData, Instance, RootContext, TypeId,
};
use super::{
- get_value_of_variable, information::InformationChain, invocation::CheckThings, AssignmentError,
- ClosedOverReferencesInScope, Context, ContextType, Environment, GeneralContext,
- SetPropertyError,
+ get_value_of_variable, invocation::CheckThings, AssignmentError, ClosedOverReferencesInScope,
+ Context, ContextType, Environment, GeneralContext, InformationChain,
};
+/// For WIP contextual access of certain APIs
pub type ContextLocation = Option;
+/// Error type for something already being defined
+pub struct AlreadyExists;
+
+pub enum DeclareInterfaceResult {
+ Merging { ty: TypeId, in_same_context: bool },
+ New(TypeId),
+}
+
#[derive(Debug)]
pub struct Syntax<'a> {
pub scope: Scope,
@@ -239,19 +246,13 @@ impl<'a> Environment<'a> {
/// Handles all assignments, including updates and destructuring
///
/// Will evaluate the expression with the right timing and conditions, including never if short circuit
- ///
- /// TODO finish operator. Unify increment and decrement. The RHS span should be fine with [`Span::NULL ...?`] Maybe RHS type could be None to accommodate
- pub fn assign_to_assignable_handle_errors<
- 'b,
- T: crate::ReadFromFS,
- A: crate::ASTImplementation,
- >(
+ pub fn assign_handle_errors<'b, T: crate::ReadFromFS, A: crate::ASTImplementation>(
&mut self,
lhs: Assignable,
operator: AssignmentKind,
// Can be `None` for increment and decrement
expression: Option<&'b A::Expression<'b>>,
- assignment_span: Span,
+ assignment_position: Span,
checking_data: &mut CheckingData,
) -> TypeId {
match lhs {
@@ -265,17 +266,24 @@ impl<'a> Environment<'a> {
checking_data,
);
- self.assign_to_reference_assign_handle_errors(
+ let assignment_position =
+ assignment_position.with_source(self.get_source());
+ self.set_reference_handle_errors(
reference,
rhs,
+ assignment_position,
checking_data,
- assignment_span,
- )
+ );
+ rhs
}
AssignmentKind::PureUpdate(operator) => {
// Order matters here
let reference_position = reference.get_position();
- let existing = self.get_reference(reference.clone(), checking_data, true);
+ let existing = self.get_reference(
+ reference.clone(),
+ checking_data,
+ AccessMode::Regular,
+ );
let expression = expression.unwrap();
let expression_pos =
@@ -294,39 +302,33 @@ impl<'a> Environment<'a> {
checking_data,
self,
);
- let result = self.set_reference(reference, new, checking_data);
- match result {
- Ok(ty) => ty,
- Err(error) => {
- let error = set_property_error_to_type_check_error(
- self,
- error,
- assignment_span.with_source(self.get_source()),
- &checking_data.types,
- new,
- );
- checking_data.diagnostics_container.add_error(error);
- TypeId::ERROR_TYPE
- }
- }
+ let assignment_position =
+ assignment_position.with_source(self.get_source());
+ self.set_reference_handle_errors(
+ reference,
+ new,
+ assignment_position,
+ checking_data,
+ );
+ new
}
AssignmentKind::IncrementOrDecrement(direction, return_kind) => {
// let value =
- // self.get_variable_or_error(&name, &assignment_span, checking_data);
- let span = reference.get_position();
- let existing = self.get_reference(reference.clone(), checking_data, true);
+ // self.get_variable_or_error(&name, &assignment_position, checking_data);
+ let position = reference.get_position();
+ let existing = self.get_reference(
+ reference.clone(),
+ checking_data,
+ AccessMode::Regular,
+ );
// TODO existing needs to be cast to number!!
let new = evaluate_pure_binary_operation_handle_errors(
- (existing, span),
+ (existing, position),
match direction {
- crate::features::assignments::IncrementOrDecrement::Increment => {
- MathematicalAndBitwise::Add
- }
- crate::features::assignments::IncrementOrDecrement::Decrement => {
- MathematicalAndBitwise::Subtract
- }
+ IncrementOrDecrement::Increment => MathematicalAndBitwise::Add,
+ IncrementOrDecrement::Decrement => MathematicalAndBitwise::Subtract,
}
.into(),
(TypeId::ONE, source_map::Nullable::NULL),
@@ -334,30 +336,26 @@ impl<'a> Environment<'a> {
self,
);
- let result = self.set_reference(reference, new, checking_data);
+ let assignment_position =
+ assignment_position.with_source(self.get_source());
+ self.set_reference_handle_errors(
+ reference,
+ new,
+ assignment_position,
+ checking_data,
+ );
- match result {
- Ok(new) => match return_kind {
- crate::features::assignments::AssignmentReturnStatus::Previous => {
- existing
- }
- crate::features::assignments::AssignmentReturnStatus::New => new,
- },
- Err(error) => {
- let error = set_property_error_to_type_check_error(
- self,
- error,
- assignment_span.with_source(self.get_source()),
- &checking_data.types,
- new,
- );
- checking_data.diagnostics_container.add_error(error);
- TypeId::ERROR_TYPE
- }
+ match return_kind {
+ AssignmentReturnStatus::Previous => existing,
+ AssignmentReturnStatus::New => new,
}
}
AssignmentKind::ConditionalUpdate(operator) => {
- let existing = self.get_reference(reference.clone(), checking_data, true);
+ let existing = self.get_reference(
+ reference.clone(),
+ checking_data,
+ AccessMode::Regular,
+ );
let expression = expression.unwrap();
let new = evaluate_logical_operation_with_expression(
(existing, reference.get_position().without_source()),
@@ -365,25 +363,19 @@ impl<'a> Environment<'a> {
expression,
checking_data,
self,
+ TypeId::ANY_TYPE,
)
.unwrap();
- let result = self.set_reference(reference, new, checking_data);
-
- match result {
- Ok(new) => new,
- Err(error) => {
- let error = set_property_error_to_type_check_error(
- self,
- error,
- assignment_span.with_source(self.get_source()),
- &checking_data.types,
- new,
- );
- checking_data.diagnostics_container.add_error(error);
- TypeId::ERROR_TYPE
- }
- }
+ let assignment_position =
+ assignment_position.with_source(self.get_source());
+ self.set_reference_handle_errors(
+ reference,
+ new,
+ assignment_position,
+ checking_data,
+ );
+ new
}
}
}
@@ -400,9 +392,11 @@ impl<'a> Environment<'a> {
self.assign_to_object_destructure_handle_errors(
members,
rhs,
- assignment_span,
+ assignment_position.with_source(self.get_source()),
checking_data,
- )
+ );
+
+ rhs
}
Assignable::ArrayDestructuring(members, _spread) => {
debug_assert!(matches!(operator, AssignmentKind::Assign));
@@ -417,67 +411,43 @@ impl<'a> Environment<'a> {
self.assign_to_array_destructure_handle_errors(
members,
rhs,
- assignment_span,
+ assignment_position.with_source(self.get_source()),
checking_data,
- )
- }
- }
- }
-
- fn assign_to_reference_assign_handle_errors<
- T: crate::ReadFromFS,
- A: crate::ASTImplementation,
- >(
- &mut self,
- reference: Reference,
- rhs: TypeId,
- checking_data: &mut CheckingData,
- assignment_span: source_map::BaseSpan<()>,
- ) -> TypeId {
- let result = self.set_reference(reference, rhs, checking_data);
-
- match result {
- Ok(ty) => ty,
- Err(error) => {
- let error = set_property_error_to_type_check_error(
- self,
- error,
- assignment_span.with_source(self.get_source()),
- &checking_data.types,
- rhs,
);
- checking_data.diagnostics_container.add_error(error);
- TypeId::ERROR_TYPE
+
+ rhs
}
}
}
- fn assign_to_assign_only_handle_errors(
+ fn assign_to_assignable_handle_errors(
&mut self,
lhs: Assignable,
rhs: TypeId,
- assignment_span: Span,
+ assignment_position: SpanWithSource,
checking_data: &mut CheckingData,
- ) -> TypeId {
+ ) {
match lhs {
- Assignable::Reference(reference) => self.assign_to_reference_assign_handle_errors(
- reference,
- rhs,
- checking_data,
- assignment_span,
- ),
+ Assignable::Reference(reference) => {
+ self.set_reference_handle_errors(
+ reference,
+ rhs,
+ assignment_position,
+ checking_data,
+ );
+ }
Assignable::ObjectDestructuring(assignments, _spread) => self
.assign_to_object_destructure_handle_errors(
assignments,
rhs,
- assignment_span,
+ assignment_position,
checking_data,
),
Assignable::ArrayDestructuring(assignments, _spread) => self
.assign_to_array_destructure_handle_errors(
assignments,
rhs,
- assignment_span,
+ assignment_position,
checking_data,
),
}
@@ -490,29 +460,38 @@ impl<'a> Environment<'a> {
&mut self,
assignments: Vec>,
rhs: TypeId,
- assignment_span: Span,
+ assignment_position: SpanWithSource,
checking_data: &mut CheckingData,
- ) -> TypeId {
+ ) {
for assignment in assignments {
match assignment {
AssignableObjectDestructuringField::Mapped {
- on,
+ key,
name,
default_value,
position,
} => {
- let value = self.get_property(
+ // TODO conditionaly
+ let mut diagnostics = Default::default();
+ let result = crate::types::properties::get_property(
rhs,
Publicity::Public,
- &on,
+ &key,
+ self,
+ (
+ &mut CheckThings { debug_types: checking_data.options.debug_types },
+ &mut diagnostics,
+ ),
&mut checking_data.types,
- None,
position,
- &checking_data.options,
- false,
+ AccessMode::DoNotBindThis,
+ );
+ diagnostics.append_to(
+ crate::types::calling::CallingContext::Getter,
+ &mut checking_data.diagnostics_container,
);
- let rhs_value = if let Some((_, value)) = value {
+ let rhs_value = if let Some((_, value)) = result {
value
} else if let Some(default_value) = default_value {
A::synthesise_expression(
@@ -522,51 +501,55 @@ impl<'a> Environment<'a> {
checking_data,
)
} else {
+ let keys;
+ let possibles = if let PropertyKey::String(s) = &key {
+ keys = get_property_key_names_on_a_single_type(
+ rhs,
+ &checking_data.types,
+ self,
+ );
+ let mut possibles =
+ crate::get_closest(keys.iter().map(AsRef::as_ref), s)
+ .unwrap_or(vec![]);
+ possibles.sort_unstable();
+ possibles
+ } else {
+ Vec::new()
+ };
checking_data.diagnostics_container.add_error(
TypeCheckError::PropertyDoesNotExist {
- property: match on {
- PropertyKey::String(s) => {
- PropertyRepresentation::StringKey(s.to_string())
- }
- PropertyKey::Type(t) => PropertyRepresentation::Type(
- printing::print_type(t, &checking_data.types, self, false),
- ),
- },
+ property: PropertyKeyRepresentation::new(
+ &key,
+ self,
+ &checking_data.types,
+ ),
on: TypeStringRepresentation::from_type_id(
rhs,
self,
&checking_data.types,
false,
),
- site: position,
- possibles: get_property_key_names_on_a_single_type(
- rhs,
- &mut checking_data.types,
- self,
- )
- .iter()
- .map(AsRef::as_ref)
- .collect::>(),
+ position,
+ possibles,
},
);
TypeId::ERROR_TYPE
};
- self.assign_to_assign_only_handle_errors(
+ self.assign_to_assignable_handle_errors(
name,
rhs_value,
- assignment_span,
+ assignment_position,
checking_data,
);
}
}
}
-
- rhs
}
#[allow(clippy::needless_pass_by_value)]
+ #[allow(clippy::unused_self)]
fn assign_to_array_destructure_handle_errors<
T: crate::ReadFromFS,
A: crate::ASTImplementation,
@@ -574,35 +557,31 @@ impl<'a> Environment<'a> {
&mut self,
_assignments: Vec>,
_rhs: TypeId,
- assignment_span: Span,
+ assignment_position: SpanWithSource,
checking_data: &mut CheckingData,
- ) -> TypeId {
- checking_data.raise_unimplemented_error(
- "destructuring array (needs iterator)",
- assignment_span.with_source(self.get_source()),
- );
-
- TypeId::ERROR_TYPE
+ ) {
+ checking_data
+ .raise_unimplemented_error("destructuring array (needs iterator)", assignment_position);
}
fn get_reference(
&mut self,
reference: Reference,
checking_data: &mut CheckingData,
- bind_this: bool,
+ mode: AccessMode,
) -> TypeId {
match reference {
Reference::Variable(name, position) => {
self.get_variable_handle_error(&name, position, checking_data).unwrap().1
}
- Reference::Property { on, with, publicity, span } => {
+ Reference::Property { on, with, publicity, position } => {
let get_property_handle_errors = self.get_property_handle_errors(
on,
publicity,
&with,
checking_data,
- span,
- bind_this,
+ position,
+ mode,
);
match get_property_handle_errors {
Ok(i) => i.get_value(),
@@ -612,30 +591,20 @@ impl<'a> Environment<'a> {
}
}
- fn set_reference(
+ fn set_reference_handle_errors(
&mut self,
reference: Reference,
rhs: TypeId,
+ _position: SpanWithSource,
checking_data: &mut CheckingData,
- ) -> Result {
+ ) {
match reference {
- Reference::Variable(name, position) => Ok(self.assign_to_variable_handle_errors(
- name.as_str(),
- position,
- rhs,
- checking_data,
- )),
- Reference::Property { on, with, publicity, span } => Ok(self
- .set_property(
- on,
- publicity,
- &with,
- rhs,
- &mut checking_data.types,
- span,
- &checking_data.options,
- )?
- .unwrap_or(rhs)),
+ Reference::Variable(name, position) => {
+ self.assign_to_variable_handle_errors(name.as_str(), position, rhs, checking_data);
+ }
+ Reference::Property { on, with, publicity, position } => {
+ self.set_property_handle_errors(on, publicity, &with, rhs, position, checking_data);
+ }
}
}
@@ -645,7 +614,7 @@ impl<'a> Environment<'a> {
assignment_position: SpanWithSource,
new_type: TypeId,
checking_data: &mut CheckingData,
- ) -> TypeId {
+ ) {
let result = self.assign_to_variable(
variable_name,
assignment_position,
@@ -653,12 +622,11 @@ impl<'a> Environment<'a> {
&mut checking_data.types,
);
match result {
- Ok(ok) => ok,
+ Ok(()) => {}
Err(error) => {
checking_data
.diagnostics_container
.add_error(TypeCheckError::AssignmentError(error));
- TypeId::ERROR_TYPE
}
}
}
@@ -670,68 +638,70 @@ impl<'a> Environment<'a> {
assignment_position: SpanWithSource,
new_type: TypeId,
types: &mut TypeStore,
- ) -> Result {
+ ) -> Result<(), AssignmentError> {
// Get without the effects
let variable_in_map = self.get_variable_unbound(variable_name);
if let Some((_, boundary, variable)) = variable_in_map {
match variable {
- VariableOrImport::Variable { mutability, declared_at, context: _ } => {
- match mutability {
- VariableMutability::Constant => {
- Err(AssignmentError::Constant(*declared_at))
+ VariableOrImport::Variable {
+ mutability,
+ declared_at,
+ context: _,
+ allow_reregistration: _,
+ } => match mutability {
+ VariableMutability::Constant => Err(AssignmentError::Constant(*declared_at)),
+ VariableMutability::Mutable { reassignment_constraint } => {
+ let variable = variable.clone();
+ let variable_position = *declared_at;
+ let variable_id = variable.get_id();
+
+ if boundary.is_none()
+ && !self
+ .get_chain_of_info()
+ .any(|info| info.variable_current_value.contains_key(&variable_id))
+ {
+ return Err(AssignmentError::TDZ(TDZ {
+ position: assignment_position,
+ variable_name: variable_name.to_owned(),
+ }));
}
- VariableMutability::Mutable { reassignment_constraint } => {
- let variable = variable.clone();
- let variable_site = *declared_at;
- let variable_id = variable.get_id();
-
- if boundary.is_none()
- && !self.get_chain_of_info().any(|info| {
- info.variable_current_value.contains_key(&variable_id)
- }) {
- return Err(AssignmentError::TDZ(TDZ {
- position: assignment_position,
- variable_name: variable_name.to_owned(),
- }));
- }
-
- if let Some(reassignment_constraint) = *reassignment_constraint {
- let result = type_is_subtype_object(
- reassignment_constraint,
- new_type,
- self,
- types,
- );
- if let SubTypeResult::IsNotSubType(_mismatches) = result {
- return Err(AssignmentError::DoesNotMeetConstraint {
- variable_type: TypeStringRepresentation::from_type_id(
- reassignment_constraint,
- self,
- types,
- false,
- ),
- value_type: TypeStringRepresentation::from_type_id(
- new_type, self, types, false,
- ),
- variable_site,
- value_site: assignment_position,
- });
- }
+ if let Some(reassignment_constraint) = *reassignment_constraint {
+ let result = type_is_subtype_object(
+ reassignment_constraint,
+ new_type,
+ self,
+ types,
+ );
+
+ if let SubTypeResult::IsNotSubType(_mismatches) = result {
+ return Err(AssignmentError::DoesNotMeetConstraint {
+ variable_type: TypeStringRepresentation::from_type_id(
+ reassignment_constraint,
+ self,
+ types,
+ false,
+ ),
+ value_type: TypeStringRepresentation::from_type_id(
+ new_type, self, types, false,
+ ),
+ variable_position,
+ value_position: assignment_position,
+ });
}
+ }
- self.info.events.push(Event::SetsVariable(
- variable_id,
- new_type,
- assignment_position,
- ));
- self.info.variable_current_value.insert(variable_id, new_type);
+ self.info.events.push(Event::SetsVariable(
+ variable_id,
+ new_type,
+ assignment_position,
+ ));
+ self.info.variable_current_value.insert(variable_id, new_type);
- Ok(new_type)
- }
+ Ok(())
}
- }
+ },
VariableOrImport::MutableImport { .. }
| VariableOrImport::ConstantImport { .. } => {
Err(AssignmentError::Constant(assignment_position))
@@ -770,51 +740,6 @@ impl<'a> Environment<'a> {
self.context_type.requests.extend(requests);
}
- /// TODO decidable & private?
- #[must_use]
- pub fn property_in(&self, on: TypeId, property: &PropertyKey) -> bool {
- self.get_chain_of_info().any(|info| match info.current_properties.get(&on) {
- Some(v) => {
- v.iter().any(
- |(_, p, v)| if let PropertyValue::Deleted = v { false } else { p == property },
- )
- }
- None => false,
- })
- }
-
- /// TODO decidable & private?
- pub fn delete_property(
- &mut self,
- on: TypeId,
- property: &PropertyKey,
- position: SpanWithSource,
- ) -> bool {
- let existing = self.property_in(on, property);
-
- let under = property.into_owned();
-
- // on_default() okay because might be in a nested context.
- // entry empty does not mean no properties, just no properties set on this level
- self.info.current_properties.entry(on).or_default().push((
- Publicity::Public,
- under.clone(),
- PropertyValue::Deleted,
- ));
-
- // TODO Event::Delete. Dependent result based on in
- self.info.events.push(Event::Setter {
- on,
- under,
- new: PropertyValue::Deleted,
- initialization: false,
- publicity: Publicity::Public,
- position,
- });
-
- existing
- }
-
pub(crate) fn get_parent(&self) -> GeneralContext {
match self.context_type.parent {
GeneralContext::Syntax(syn) => GeneralContext::Syntax(syn),
@@ -822,51 +747,51 @@ impl<'a> Environment<'a> {
}
}
- #[allow(clippy::too_many_arguments)]
- pub fn get_property(
- &mut self,
- on: TypeId,
- publicity: Publicity,
- property: &PropertyKey,
- types: &mut TypeStore,
- with: Option,
- position: SpanWithSource,
- options: &TypeCheckOptions,
- bind_this: bool,
- ) -> Option<(PropertyKind, TypeId)> {
- crate::types::properties::get_property(
- on,
- publicity,
- property,
- with,
- self,
- &mut CheckThings { debug_types: options.debug_types },
- types,
- position,
- bind_this,
- )
- }
-
pub fn get_property_handle_errors(
&mut self,
on: TypeId,
publicity: Publicity,
- key: &PropertyKey,
+ under: &PropertyKey,
checking_data: &mut CheckingData,
- site: SpanWithSource,
- bind_this: bool,
+ position: SpanWithSource,
+ mode: AccessMode,
) -> Result {
- let get_property = self.get_property(
+ // let get_property = if let AccessMode::Optional = mode {
+ // let is_lhs_null = evaluate_equality_inequality_operation(
+ // lhs.0,
+ // &EqualityAndInequality::StrictEqual,
+ // TypeId::NULL_TYPE,
+ // &mut checking_data.types,
+ // checking_data.options.strict_casts,
+ // )?;
+ // Ok(new_conditional_context(
+ // environment,
+ // (is_lhs_null, lhs.1),
+ // |env: &mut Environment, data: &mut CheckingData| {
+ // A::synthesise_expression(rhs, TypeId::ANY_TYPE, env, data)
+ // },
+ // Some(|_env: &mut Environment, _data: &mut CheckingData| lhs.0),
+ // checking_data,
+ // ))
+ // } else {
+ let mut diagnostics = Default::default();
+ let get_property = crate::types::properties::get_property(
on,
publicity,
- key,
+ under,
+ self,
+ (&mut CheckThings { debug_types: checking_data.options.debug_types }, &mut diagnostics),
&mut checking_data.types,
- None,
- site,
- &checking_data.options,
- bind_this,
+ position,
+ mode,
+ );
+ diagnostics.append_to(
+ crate::types::calling::CallingContext::Getter,
+ &mut checking_data.diagnostics_container,
);
+ // };
+
if let Some((kind, result)) = get_property {
Ok(match kind {
PropertyKind::Getter => Instance::GValue(result),
@@ -876,32 +801,26 @@ impl<'a> Environment<'a> {
}
})
} else {
+ let keys;
+ let possibles = if let PropertyKey::String(s) = under {
+ keys = get_property_key_names_on_a_single_type(on, &checking_data.types, self);
+ let mut possibles =
+ crate::get_closest(keys.iter().map(AsRef::as_ref), s).unwrap_or(vec![]);
+ possibles.sort_unstable();
+ possibles
+ } else {
+ Vec::new()
+ };
checking_data.diagnostics_container.add_error(TypeCheckError::PropertyDoesNotExist {
- // TODO printing temp
- property: match key {
- PropertyKey::String(s) => PropertyRepresentation::StringKey(s.to_string()),
- PropertyKey::Type(t) => PropertyRepresentation::Type(printing::print_type(
- *t,
- &checking_data.types,
- self,
- false,
- )),
- },
+ property: PropertyKeyRepresentation::new(under, self, &checking_data.types),
on: crate::diagnostics::TypeStringRepresentation::from_type_id(
on,
self,
&checking_data.types,
false,
),
- site,
- possibles: get_property_key_names_on_a_single_type(
- on,
- &mut checking_data.types,
- self,
- )
- .iter()
- .map(AsRef::as_ref)
- .collect::>(),
+ position,
+ possibles,
});
Err(())
}
@@ -919,12 +838,14 @@ impl<'a> Environment<'a> {
if let Some((in_root, crossed_boundary, og_var)) = variable_information {
(in_root, crossed_boundary, og_var.clone())
} else {
+ let possibles = {
+ let mut possibles =
+ crate::get_closest(self.get_all_variable_names(), name).unwrap_or(vec![]);
+ possibles.sort_unstable();
+ possibles
+ };
checking_data.diagnostics_container.add_error(
- TypeCheckError::CouldNotFindVariable {
- variable: name,
- possibles: self.get_all_variable_names(),
- position,
- },
+ TypeCheckError::CouldNotFindVariable { variable: name, possibles, position },
);
return Err(TypeId::ERROR_TYPE);
}
@@ -985,7 +906,7 @@ impl<'a> Environment<'a> {
let ty = checking_data.types.get_type_by_id(current_value);
// TODO temp
- if let Type::SpecialObject(SpecialObjects::Function(..)) = ty {
+ if let Type::SpecialObject(SpecialObject::Function(..)) = ty {
return Ok(VariableWithValue(og_var.clone(), current_value));
} else if let Type::RootPolyType(PolyNature::Open(_)) = ty {
crate::utilities::notify!(
@@ -1124,8 +1045,18 @@ impl<'a> Environment<'a> {
}
}
- pub fn throw_value(&mut self, thrown: TypeId, position: SpanWithSource) {
+ pub fn throw_value(&mut self, thrown: TypeId, position: SpanWithSource, types: &mut TypeStore) {
let final_event = FinalEvent::Throw { thrown, position };
+
+ // WIP
+ let final_return =
+ if let ReturnState::Rolling { under, returned: rolling_returning } = self.info.state {
+ types.new_conditional_type(under, rolling_returning, TypeId::NEVER_TYPE)
+ } else {
+ TypeId::NEVER_TYPE
+ };
+ self.info.state = ReturnState::Finished(final_return);
+
self.info.events.push(final_event.into());
}
@@ -1193,7 +1124,6 @@ impl<'a> Environment<'a> {
// Add the expected return type instead here
// if it fell through to another then it could be bad
- crate::utilities::notify!("Here {:?}", expected);
let expected_return = checking_data.types.new_error_type(expected);
let final_event = FinalEvent::Return {
returned: expected_return,
@@ -1205,8 +1135,20 @@ impl<'a> Environment<'a> {
}
}
- let final_event = FinalEvent::Return { returned, position: returned_position };
- self.info.events.push(final_event.into());
+ {
+ let final_event = FinalEvent::Return { returned, position: returned_position };
+ self.info.events.push(final_event.into());
+
+ // WIP
+ let final_return = if let ReturnState::Rolling { under, returned: rolling_returning } =
+ self.info.state
+ {
+ checking_data.types.new_conditional_type(under, rolling_returning, returned)
+ } else {
+ returned
+ };
+ self.info.state = ReturnState::Finished(final_return);
+ }
}
pub fn add_continue(
@@ -1251,26 +1193,38 @@ impl<'a> Environment<'a> {
///
/// Returns the result of the setter... TODO could return new else
#[allow(clippy::too_many_arguments)]
- pub fn set_property(
+ pub fn set_property_handle_errors(
&mut self,
on: TypeId,
publicity: Publicity,
under: &PropertyKey,
new: TypeId,
- types: &mut TypeStore,
setter_position: SpanWithSource,
- options: &TypeCheckOptions,
- ) -> Result