Skip to content

Commit

Permalink
Improvements to checker (#78)
Browse files Browse the repository at this point in the history
PropertyKey change, basic class synthesis and other fixes
  • Loading branch information
kaleidawave authored Nov 20, 2023
1 parent 0b5f688 commit c10b772
Show file tree
Hide file tree
Showing 62 changed files with 4,821 additions and 3,399 deletions.
45 changes: 40 additions & 5 deletions checker/definitions/internal.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,23 @@ interface nominal Array<T> {
}
}

last() performs {
return this[this.length - 1]
}
// last() performs {
// return this[this.length - 1]
// }
}

interface Math {
sin(x: number): number performs const sin;
cos(x: number): number performs const cos;
tan(x: number): number performs const tan;
trunc(x: number): number performs const trunc;
floor(x: number): number performs const floor;
sqrt(x: number): number performs const sqrt;
cbrt(x: number): number performs const cbrt;

// TODO newer method
trunc(x: number): number performs const trunc;

PI: 3.141592653589793
}

interface nominal string {
Expand All @@ -69,10 +74,40 @@ interface JSON {
stringify(input: any): string;
}

interface Function {
bind(this_ty: any): Function performs const bind;
}

interface Object {
setPrototypeOf(on: object, to: object): object performs const set_prototype;

getPrototypeOf(on: object): object | null performs const get_prototype;

// create(prototype: object): object performs {
// const n = {};
// Object.setProtoTypeOf(n, prototype);
// return n
// }
}

declare var JSON: JSON;
declare var Math: Math;
declare var console: Console;
declare var Object: Object;

declare function JSXH(tagname: string, attributes: any, children: any) performs {
declare function JSXH(tagname: string, attributes: any, children?: any) performs {
return tagname
}

interface Document {
title: string
}

interface FormData {
}

@client
declare const document: Document;

// @server
// declare function createItem(a: any);
8 changes: 2 additions & 6 deletions checker/docs/architecture tricks.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
> Building a type checker is hard. Here are some tricks that Ezno uses to keep the codebase small and simple
### Specializing generics
### Specialising/substituting generics
Finding the values for generics is done during subtype checking #TODO

### Object keys are types
When `x.t` a `"t"` type is created.

#TODO how does casting work

## Internal #TODO

- `Environment::parents_iter`
- `get_on_ctx!`
- `Logical`
7 changes: 3 additions & 4 deletions checker/docs/checking.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ These happen in a few places

This is implemented in `types/sub-typing`

#### Specializing
This subtype checking also doubles as a way to specialize generic types that were not or could not be specified as a specific arguments. This is implicitly / inferred, so not explicit arguments.
#### Specialising/substituting
This subtype checking also doubles as a way to specialise generic types that were not or could not be specified as a specific arguments. This is implicitly / inferred, so not explicit arguments.

### `type` querying

Rather than checking whether types are subtypes of some base type, these are concerned with trying to find one specific property of a base type
- Property access (and destructuring)
- Is callable (the parameters can be checked)

> These could be implemented using the first method. Property access could be implemented by checking and specializing using `{ [name]: T }`, but doesn't
> These could be implemented using the first method. Property access could be implemented by checking and substituting using `{ [name]: T }`, but doesn't
## Checking a program
Starts with a list of statements.
Expand All @@ -45,7 +45,6 @@ There are three stages to hoisting.
- Return type synthesised

#### Still working out
- Imports, exports?
- Types cannot mutate structure
- Where do callable interfaces go? (need to be functions)
- interface extends (needs to be aliases)
Expand Down
2 changes: 1 addition & 1 deletion checker/docs/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ They reference things in the type system. Their are no values, only type referen
The side effects of a function are bundled into the events in the function. Side effects = events that

### Function synthesis and calling
When synthesizing a function, events are recorded. They are then held on the function type. When calling that function, the events are replayed with the current state of the program.
When synthesising a function, events are recorded. They are then held on the function type. When calling that function, the events are replayed with the current state of the program.

## Using as an IR
The events can be used as an intermediate representation as a a representation of the program in a lowered syntax.
Expand Down
16 changes: 16 additions & 0 deletions checker/docs/not-supported.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
While Ezno is have a high JS compatibility, the following compromise the fundamentals of Ezno and are also advised as bad features to use.

Note all of these are not supported in TSC either

Not supporting in JavaScript:
- dynamic import with a non-constant value. Can introduce unknown and unreliable side effects.
- Also just avoid as they slow down things in the checker
- Want something [this to be added](https://github.com/tc39/proposal-defer-import-eval) for lazy loading that is statically analyzable
- `super` calls in a conditional context of a constructor
- Really difficult to add the checker without adding enormous complexity and overhead
- Means that reference errors
- `eval` just don't
- `with`

Not supporting from TypeScript:
- Function overloading syntax (use generics and matchings)
2 changes: 1 addition & 1 deletion checker/docs/parameters.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
When synthesizing (aka checking) a function a few thing happen:
When synthesising (aka checking) a function a few thing happen:
- Parameters are synthesised
- Ones that are not already generic are made into generics

Expand Down
3 changes: 0 additions & 3 deletions checker/docs/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@
### Open poly types
Poly types retain information and flow of data through the use of higher poly types.

A few differences
- They are not subject to specialization (#TODO explain why)

### Objects
In simple case objects (collections of data) can be considered constant

Expand Down
5 changes: 5 additions & 0 deletions checker/specification/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ publish = false
[workspace]
members = ["."]

[features]
just-staging = []
staging = []
all = ["staging"]

[[test]]
name = "specification_test"
path = "test.rs"
Expand Down
11 changes: 11 additions & 0 deletions checker/specification/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[The specification](./specification.md) is a markdown document that defines what the behavior of Ezno's checker. It is meant to server both as documentation and what is currently supported as well as a test suite to ensure there isn't a regression in the checker.

[specification.md](./specification.md) defines current behavior which works on the main branch. [staging.md](./staging.md) can be used as a well to introduce behavior during the implementation and remains separated from the rest. It can be enabled with the `staging` features and exclusively with `staging-only`. [to_implement.md](./to_implement.md) contains future tests that do not currently passed. They can be viewed as a target for the checker and copied to staging when ready to implement.

These can all be ran as tests using the markdown to Rust test code transpiler in [build.rs](./build.rs).

- The cases should be brief and only test a specific aspect of the language
- Each block contains errors, the list afterwards is the expected errors
- 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.ts`
36 changes: 20 additions & 16 deletions checker/specification/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,28 @@ use std::path::Path;

fn main() -> Result<(), Box<dyn Error>> {
println!("cargo:rerun-if-changed=specification.md");
println!("cargo:rerun-if-changed=staging.md");
println!("cargo:rerun-if-changed=to_implement.md");

let out_path = Path::new(&std::env::var("OUT_DIR")?).join("specification.rs");
let mut out = File::create(out_path)?;

let specification = read_to_string("./specification.md")?;
let mut lines = specification.lines().enumerate();

while let Some((_, line)) = lines.next() {
if line == "## Specification" {
break;
}
if cfg!(not(feature = "just-staging")) {
let specification = read_to_string("./specification.md")?;
markdown_lines_append_test_to_rust(specification.lines().enumerate(), &mut out)?;
}

markdown_lines_append_test_to_rust(lines, &mut out)?;
if cfg!(feature = "staging") {
let staging = read_to_string("./staging.md")?;
writeln!(&mut out, "mod staging {{ use super::check_errors; ").unwrap();
markdown_lines_append_test_to_rust(staging.lines().enumerate(), &mut out)?;
writeln!(&mut out, "}}").unwrap();
}

if cfg!(feature = "all") {
let not_yet_implemented = read_to_string("./to_implement.md")?;
let lines = not_yet_implemented.lines().enumerate();

writeln!(&mut out, "mod todo {{ use super::check_errors; ").unwrap();
markdown_lines_append_test_to_rust(lines, &mut out)?;
let to_implement = read_to_string("./to_implement.md")?;
writeln!(&mut out, "mod to_implement {{ use super::check_errors; ").unwrap();
markdown_lines_append_test_to_rust(to_implement.lines().enumerate(), &mut out)?;
writeln!(&mut out, "}}").unwrap();
}

Expand Down Expand Up @@ -56,6 +56,7 @@ fn markdown_lines_append_test_to_rust(
if !line.starts_with("#### ") {
continue;
}

let heading = line.strip_prefix("####").unwrap().trim_start();
let test_title = heading_to_rust_name(heading);

Expand Down Expand Up @@ -98,7 +99,8 @@ fn markdown_lines_append_test_to_rust(
break;
}
} else {
let error = line.strip_prefix("- ").unwrap().replace('"', "\\\"");
let error =
line.strip_prefix("- ").unwrap().replace('\\', "").replace('"', "\\\"");
errors.push(format!("\"{}\"", error))
}
}
Expand All @@ -122,14 +124,16 @@ fn markdown_lines_append_test_to_rust(
}}",
)?;
}
writeln!(out, "}}").unwrap();
if !first_section {
writeln!(out, "}}").unwrap();
}

Ok(())
}

fn heading_to_rust_name(heading: &str) -> String {
heading
.replace([' ', '-', '&'], "_")
.replace(['*', '\'', '!', '(', ')', ','], "")
.replace(['*', '\'', '`', '"', '!', '(', ')', ','], "")
.to_lowercase()
}
Loading

0 comments on commit c10b772

Please sign in to comment.