├── .gitignore ├── src ├── typescript.rs ├── main.rs ├── typescript │ ├── typechecker │ │ └── context.rs │ ├── lexer.rs │ ├── ast.rs │ ├── parser.rs │ ├── ast │ │ └── type.rs │ └── typechecker.rs └── lib.rs ├── test ├── lambda.ts ├── expression.ts ├── class.ts ├── any.ts ├── types.ts ├── array.ts ├── test.ts ├── discriminated_union.ts ├── function.ts └── obj.ts ├── Cargo.toml ├── README.md ├── LICENSE └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /src/typescript.rs: -------------------------------------------------------------------------------- 1 | pub mod ast; 2 | pub mod lexer; 3 | pub mod parser; 4 | pub mod typechecker; 5 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use omo_compiler::parse; 4 | 5 | fn main() { 6 | let filename = env::args().nth(1).expect("Expected file argument"); 7 | parse(filename); 8 | } 9 | -------------------------------------------------------------------------------- /test/lambda.ts: -------------------------------------------------------------------------------- 1 | let addOne = (x: number) => x + 1; 2 | 3 | let addNums = (x: number, y: number) => { 4 | return x + y; 5 | }; 6 | 7 | let empty = () => {}; 8 | 9 | let num = ((x: number) => x)(1); 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "omo_compiler" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | ariadne = "0.5.0" 8 | chumsky = { version = "=1.0.0-alpha.7", features = ["label", "pratt"] } 9 | num-traits = "0.2.19" 10 | ordered-float = "4.5.0" 11 | thiserror = "2.0.3" 12 | -------------------------------------------------------------------------------- /test/expression.ts: -------------------------------------------------------------------------------- 1 | let tru: true = true; 2 | let fls: false = false; 3 | let a = tru && tru; 4 | let b = tru && fls; 5 | let c = tru || true; 6 | let d = fls || false; 7 | 8 | let f: 2 = 2; 9 | let g = (f = f = 2); 10 | let h = true ? 2 : false ? a : a; 11 | let i = true ? a : 3; 12 | let j = false ? f : tru; 13 | -------------------------------------------------------------------------------- /test/class.ts: -------------------------------------------------------------------------------- 1 | class Foo { 2 | x: number = 0; 3 | y = this.add(2); 4 | constructor(x: number) { 5 | type a = typeof this; 6 | type b = Foo; 7 | this.x = x; 8 | return this; 9 | } 10 | add(y: number) { 11 | return this.sub(y); 12 | } 13 | sub(y: number) { 14 | return this.add(y); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/any.ts: -------------------------------------------------------------------------------- 1 | let object = { x: 1 }; 2 | let record: Record = {}; 3 | let fixedArray: ["hello", "there"] = ["hello", "there"]; 4 | let array: string[] = ["hello"]; 5 | let a: any = null; 6 | let b = a + 2; 7 | let c = a || true; 8 | let d = a && true; 9 | let e = a(); 10 | let f = record[a]; 11 | let g = fixedArray[a]; 12 | let h = array[a]; 13 | let i = object[a]; 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # omo 2 | 3 | A spiritual successor to [komodo](https://github.com/unknown/komodo), Omo is a TypeScript type-checker. Omo is far from supporting all of TypeScript, but it supports a useful subset of it including custom type definitions, discriminated unions, and classes. 4 | 5 | ## Getting started 6 | 7 | Run `cargo run -- ` to run Omo on a file. For example, the following will print out a type annotated version of the file `test/test.ts`: 8 | 9 | ```sh 10 | $ cargo run -- test/test.ts 11 | ``` 12 | -------------------------------------------------------------------------------- /test/types.ts: -------------------------------------------------------------------------------- 1 | type a = 1 | ("hello" | 1); 2 | type b = (number & 2) | (string & ""); 3 | type c = a & b; 4 | type d = a | b | c; 5 | 6 | function x2(): d { 7 | if (true) { 8 | return 2; 9 | } else { 10 | return "hello"; 11 | } 12 | } 13 | 14 | type e = { 15 | a: number; 16 | b: number; 17 | }; 18 | type f = { 19 | a: number; 20 | b: 3; 21 | c: 3; 22 | }; 23 | type g = e & f; 24 | 25 | let a1: g = { 26 | a: 2, 27 | b: 3, 28 | c: 3, 29 | }; 30 | 31 | type h = (a: number) => 3; 32 | type i = (a: 3) => number; 33 | type j = h & i; 34 | 35 | function y(a: number): 3 { 36 | return 3; 37 | } 38 | let b1: j = y; 39 | 40 | type k = (1 | 2) & (2 | 3); 41 | type m = boolean | number | string | undefined | null | void; 42 | -------------------------------------------------------------------------------- /test/array.ts: -------------------------------------------------------------------------------- 1 | let arr1: [1, "hello", null] = [1, "hello", null]; 2 | let arr2: (1 | "hello" | null)[] = [1, "hello", null]; 3 | type t1 = number[] & [2]; 4 | let x: t1 = [2]; 5 | type t2 = number[] & [1, boolean]; 6 | type t3 = [number] & [boolean]; 7 | 8 | let a = arr1[0]; 9 | let b = arr1[1]; 10 | let c = arr1[2]; 11 | 12 | function f(x: [0, 1, 2], index: 0 | 1) { 13 | return x[index]; 14 | } 15 | 16 | let length1 = arr1.length; 17 | let length2 = arr2.length; 18 | 19 | arr1.forEach((element: 1 | "hello" | null, index: number) => { 20 | type a = typeof element; 21 | type b = typeof index; 22 | }); 23 | 24 | arr2.map((element: 1 | "hello" | null, index: number) => { 25 | type a = typeof element; 26 | type b = typeof index; 27 | }); 28 | -------------------------------------------------------------------------------- /test/test.ts: -------------------------------------------------------------------------------- 1 | let a: number = 1; 2 | let b: "hello" = "hello"; 3 | let c = { a: 1, b: "2" }; 4 | if (true) { 5 | let d = 4; 6 | a = a + 1; 7 | } else { 8 | c.a = c.a * 2; 9 | } 10 | if (false) { 11 | c.b = "bye"; 12 | } 13 | a; 14 | 15 | if (true) { 16 | } else { 17 | } 18 | 19 | let d = true && false; 20 | let e = false && true; 21 | 22 | function f(x: number) { 23 | function g(y: number, z: number) { 24 | return x + y + z; 25 | } 26 | return g(3, 2); 27 | } 28 | 29 | let res = f(2); 30 | 31 | function g(x: number) { 32 | if (x === 3 && x === x) { 33 | type a = typeof x; 34 | return x; 35 | } else { 36 | type b = typeof x; 37 | return x; 38 | } 39 | } 40 | 41 | let h: 1 = 1; 42 | if (h !== 1) { 43 | type a = typeof h; 44 | } 45 | -------------------------------------------------------------------------------- /test/discriminated_union.ts: -------------------------------------------------------------------------------- 1 | type OkResult = { 2 | status: "ok"; 3 | result: number; 4 | }; 5 | 6 | type PendingResult = { 7 | status: "pending"; 8 | result: boolean; 9 | }; 10 | 11 | type ErrResult = { 12 | status: "err"; 13 | }; 14 | 15 | type Result = OkResult | PendingResult | ErrResult; 16 | 17 | function getResult(result: Result) { 18 | if (result.status === "ok") { 19 | type a = typeof result.status; 20 | return result.result; 21 | } else { 22 | if (result.status !== "pending") { 23 | type b = typeof result.status; 24 | return null; 25 | } else { 26 | type c = typeof result.status; 27 | return result.result; 28 | } 29 | } 30 | } 31 | 32 | function isErr(result: Result) { 33 | if (result.status !== "err") { 34 | type a = typeof result.status; 35 | return true; 36 | } else { 37 | type b = typeof result.status; 38 | return false; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 David Mo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/function.ts: -------------------------------------------------------------------------------- 1 | function f(x: number) { 2 | if (x === 1) { 3 | return true; 4 | } 5 | return true; 6 | } 7 | 8 | function g(x: number): boolean | undefined { 9 | if (x === 1) { 10 | return true; 11 | } 12 | } 13 | 14 | function h() {} 15 | 16 | function count1(x: number) { 17 | if (x === 0) { 18 | return 0; 19 | } 20 | return count1(x - 1); 21 | } 22 | 23 | function count2(x: number) { 24 | if (x === 0) { 25 | return 0; 26 | } 27 | let result = count2(x - 1); 28 | type a = typeof result; 29 | return result; 30 | } 31 | 32 | function count3(x: number) { 33 | if (x === 0) { 34 | return 0; 35 | } 36 | let result: number = count3(x - 1); 37 | type a = typeof result; 38 | return result; 39 | } 40 | 41 | function count4(x: number) { 42 | if (x === 0) { 43 | return 0; 44 | } 45 | let result: boolean = true; 46 | type a = typeof result; 47 | return result; 48 | } 49 | 50 | function loop() { 51 | return loop(); 52 | } 53 | 54 | let lambda = (x: number): number => x; 55 | 56 | function forEach(arr: any[], lambda: (x: any, i: number) => void) {} 57 | 58 | forEach([], lambda); 59 | -------------------------------------------------------------------------------- /test/obj.ts: -------------------------------------------------------------------------------- 1 | let a: { x: 1; 1: "x" } = { x: 1, 1: "x" }; 2 | function fun(a: { x: number }) { 3 | a.x = 2; 4 | } 5 | fun(a); 6 | 7 | let index = 3; 8 | 9 | let b = a[1]; 10 | let c = a["x"]; 11 | let d = a.x; 12 | let e = a[2]; 13 | 14 | let record1: Record = { 15 | hello: "world", 16 | foo: "bar", 17 | }; 18 | 19 | let f = record1["foo"]; 20 | let g = record1.foo; 21 | 22 | let record2: Record< 23 | string, 24 | Record 25 | > = { 26 | hello: { 27 | foo: { status: "ok" }, 28 | }, 29 | }; 30 | 31 | if (record2.hello.foo.status === "ok") { 32 | type a = typeof record2; 33 | } 34 | 35 | if (record2["hello"]["foo"]["status"] === "ok") { 36 | type a = typeof record2; 37 | } 38 | 39 | let record3: Record | Record> = 40 | { 41 | hello: { 42 | foo: 1, 43 | }, 44 | world: { 45 | bar: true, 46 | }, 47 | }; 48 | 49 | function fun2( 50 | record: { 51 | status: string; 52 | data: Record | Record; 53 | }, 54 | field: string, 55 | ) { 56 | if (record[field].foo === 1) { 57 | let xd = record[field].foo; 58 | type a = typeof record; 59 | } 60 | } 61 | 62 | type a = Record & Record; 63 | type b = Record & { hello: string; foo: string }; 64 | -------------------------------------------------------------------------------- /src/typescript/typechecker/context.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::typescript::ast::r#type::{join, meet, Type}; 4 | 5 | #[derive(Clone, Debug, Default)] 6 | pub struct Context<'src> { 7 | pub variables: HashMap<&'src str, Type<'src>>, 8 | pub types: HashMap<&'src str, Type<'src>>, 9 | } 10 | 11 | pub fn join_contexts<'src>(context1: &Context<'src>, context2: &Context<'src>) -> Context<'src> { 12 | let mut result = context1.clone(); 13 | for (v, t2) in context2.variables.iter() { 14 | match result.variables.get(v) { 15 | Some(t1) => result.variables.insert(v, join(t1, t2)), 16 | None => result.variables.insert(v, t2.clone()), 17 | }; 18 | } 19 | result 20 | } 21 | 22 | pub fn meet_contexts<'src>(context1: &Context<'src>, context2: &Context<'src>) -> Context<'src> { 23 | let mut result = context1.clone(); 24 | for (v, t2) in context2.variables.iter() { 25 | match result.variables.get(v) { 26 | Some(t1) => result.variables.insert(v, meet(t1, t2)), 27 | None => result.variables.insert(v, t2.clone()), 28 | }; 29 | } 30 | result 31 | } 32 | 33 | pub fn overlay_contexts<'src>(context1: &Context<'src>, context2: &Context<'src>) -> Context<'src> { 34 | let mut result = context1.clone(); 35 | for (v, t2) in context2.variables.iter() { 36 | result.variables.insert(v, t2.clone()); 37 | } 38 | result 39 | } 40 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | 3 | use ariadne::{sources, Color, Label, Report, ReportKind}; 4 | use chumsky::prelude::*; 5 | use typescript::{lexer::lexer, parser::parser, typechecker::typecheck}; 6 | 7 | mod typescript; 8 | 9 | pub fn parse(filename: String) { 10 | let src = fs::read_to_string(&filename).expect("Failed to read file"); 11 | 12 | let (mut tokens, errs) = lexer().parse(src.as_str()).into_output_errors(); 13 | 14 | let (mut ast, parse_errs) = tokens.as_mut().map_or_else( 15 | || (None, Vec::new()), 16 | |tokens| { 17 | parser() 18 | .parse(tokens.as_slice().spanned((src.len()..src.len()).into())) 19 | .into_output_errors() 20 | }, 21 | ); 22 | 23 | errs.into_iter() 24 | .map(|e| e.map_token(|c| c.to_string())) 25 | .chain( 26 | parse_errs 27 | .into_iter() 28 | .map(|e| e.map_token(|token| token.to_string())), 29 | ) 30 | .for_each(|e| { 31 | Report::build(ReportKind::Error, (filename.clone(), e.span().into_range())) 32 | .with_message(e.to_string()) 33 | .with_label( 34 | Label::new((filename.clone(), e.span().into_range())) 35 | .with_message(e.reason().to_string()) 36 | .with_color(Color::Red), 37 | ) 38 | .with_labels(e.contexts().map(|(label, span)| { 39 | Label::new((filename.clone(), span.into_range())) 40 | .with_message(format!("while parsing {}", label)) 41 | .with_color(Color::Yellow) 42 | })) 43 | .finish() 44 | .print(sources([(filename.clone(), src.clone())])) 45 | .unwrap() 46 | }); 47 | 48 | if let Some(ast) = ast.as_mut() { 49 | if let Err((e, span)) = typecheck(ast) { 50 | Report::build(ReportKind::Error, (filename.clone(), span.into_range())) 51 | .with_message(e.to_string()) 52 | .with_label( 53 | Label::new((filename.clone(), span.into_range())) 54 | .with_message(e.to_string()) 55 | .with_color(Color::Red), 56 | ) 57 | .finish() 58 | .print(sources([(filename.clone(), src.clone())])) 59 | .unwrap(); 60 | } else { 61 | println!("{}", ast.0); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/typescript/lexer.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use chumsky::prelude::*; 4 | use ordered_float::OrderedFloat; 5 | 6 | use super::ast::Spanned; 7 | 8 | #[derive(Clone, Debug, PartialEq)] 9 | pub enum Token<'src> { 10 | Null, 11 | Undefined, 12 | Void, 13 | Bool(bool), 14 | Num(OrderedFloat), 15 | Str(&'src str), 16 | Ident(&'src str), 17 | Op(&'src str), 18 | Ctrl(char), 19 | // Keywords 20 | Let, 21 | If, 22 | Else, 23 | Function, 24 | Return, 25 | TypeOf, 26 | Class, 27 | This, 28 | } 29 | 30 | impl fmt::Display for Token<'_> { 31 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 32 | match self { 33 | Token::Null => write!(f, "null"), 34 | Token::Undefined => write!(f, "undefined"), 35 | Token::Void => write!(f, "void"), 36 | Token::Bool(b) => write!(f, "{}", b), 37 | Token::Num(n) => write!(f, "{}", n), 38 | Token::Str(s) => write!(f, "\"{}\"", s), 39 | Token::Ident(s) => write!(f, "{}", s), 40 | Token::Op(s) => write!(f, "{}", s), 41 | Token::Ctrl(s) => write!(f, "{}", s), 42 | Token::Let => write!(f, "let"), 43 | Token::If => write!(f, "if"), 44 | Token::Else => write!(f, "else"), 45 | Token::Function => write!(f, "function"), 46 | Token::Return => write!(f, "return"), 47 | Token::TypeOf => write!(f, "typeof"), 48 | Token::Class => write!(f, "class"), 49 | Token::This => write!(f, "this"), 50 | } 51 | } 52 | } 53 | 54 | pub fn lexer<'src>( 55 | ) -> impl Parser<'src, &'src str, Vec>>, extra::Err>> 56 | { 57 | let num = text::int(10) 58 | .then(just('.').then(text::digits(10)).or_not()) 59 | .to_slice() 60 | .map(|s: &str| Token::Num(s.parse().unwrap())); 61 | 62 | let string = just('"') 63 | .ignore_then(none_of('"').repeated().to_slice()) 64 | .then_ignore(just('"')) 65 | .map(Token::Str); 66 | 67 | let ident = text::ident().map(|s| match s { 68 | "null" => Token::Null, 69 | "undefined" => Token::Undefined, 70 | "void" => Token::Void, 71 | "true" => Token::Bool(true), 72 | "false" => Token::Bool(false), 73 | "let" => Token::Let, 74 | "if" => Token::If, 75 | "else" => Token::Else, 76 | "function" => Token::Function, 77 | "return" => Token::Return, 78 | "typeof" => Token::TypeOf, 79 | "class" => Token::Class, 80 | "this" => Token::This, 81 | s => Token::Ident(s), 82 | }); 83 | 84 | let op = one_of("+-*/=:.|&!?") 85 | .repeated() 86 | .at_least(1) 87 | .to_slice() 88 | .map(Token::Op); 89 | 90 | let ctrl = one_of("()[]{}<>;,").map(Token::Ctrl); 91 | 92 | let token = choice((num, string, ident, op, ctrl)); 93 | 94 | token 95 | .map_with(|token, e| (token, e.span())) 96 | .padded() 97 | .recover_with(skip_then_retry_until(any().ignored(), end())) 98 | .repeated() 99 | .collect() 100 | } 101 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ahash" 7 | version = "0.8.11" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 10 | dependencies = [ 11 | "cfg-if", 12 | "once_cell", 13 | "version_check", 14 | "zerocopy", 15 | ] 16 | 17 | [[package]] 18 | name = "aho-corasick" 19 | version = "1.1.3" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 22 | dependencies = [ 23 | "memchr", 24 | ] 25 | 26 | [[package]] 27 | name = "allocator-api2" 28 | version = "0.2.19" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "611cc2ae7d2e242c457e4be7f97036b8ad9ca152b499f53faf99b1ed8fc2553f" 31 | 32 | [[package]] 33 | name = "ariadne" 34 | version = "0.5.0" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "31beedec3ce83ae6da3a79592b3d8d7afd146a5b15bb9bb940279aced60faa89" 37 | dependencies = [ 38 | "unicode-width", 39 | "yansi", 40 | ] 41 | 42 | [[package]] 43 | name = "autocfg" 44 | version = "1.4.0" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 47 | 48 | [[package]] 49 | name = "cc" 50 | version = "1.1.37" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "40545c26d092346d8a8dab71ee48e7685a7a9cba76e634790c215b41a4a7b4cf" 53 | dependencies = [ 54 | "shlex", 55 | ] 56 | 57 | [[package]] 58 | name = "cfg-if" 59 | version = "1.0.0" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 62 | 63 | [[package]] 64 | name = "chumsky" 65 | version = "1.0.0-alpha.7" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "c7b80276986f86789dc56ca6542d53bba9cda3c66091ebbe7bd96fc1bdf20f1f" 68 | dependencies = [ 69 | "hashbrown", 70 | "regex-automata", 71 | "serde", 72 | "stacker", 73 | "unicode-ident", 74 | ] 75 | 76 | [[package]] 77 | name = "hashbrown" 78 | version = "0.14.5" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 81 | dependencies = [ 82 | "ahash", 83 | "allocator-api2", 84 | ] 85 | 86 | [[package]] 87 | name = "libc" 88 | version = "0.2.162" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" 91 | 92 | [[package]] 93 | name = "memchr" 94 | version = "2.7.4" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 97 | 98 | [[package]] 99 | name = "num-traits" 100 | version = "0.2.19" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 103 | dependencies = [ 104 | "autocfg", 105 | ] 106 | 107 | [[package]] 108 | name = "omo_compiler" 109 | version = "0.1.0" 110 | dependencies = [ 111 | "ariadne", 112 | "chumsky", 113 | "num-traits", 114 | "ordered-float", 115 | "thiserror", 116 | ] 117 | 118 | [[package]] 119 | name = "once_cell" 120 | version = "1.20.2" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 123 | 124 | [[package]] 125 | name = "ordered-float" 126 | version = "4.5.0" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "c65ee1f9701bf938026630b455d5315f490640234259037edb259798b3bcf85e" 129 | dependencies = [ 130 | "num-traits", 131 | ] 132 | 133 | [[package]] 134 | name = "proc-macro2" 135 | version = "1.0.89" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" 138 | dependencies = [ 139 | "unicode-ident", 140 | ] 141 | 142 | [[package]] 143 | name = "psm" 144 | version = "0.1.23" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "aa37f80ca58604976033fae9515a8a2989fc13797d953f7c04fb8fa36a11f205" 147 | dependencies = [ 148 | "cc", 149 | ] 150 | 151 | [[package]] 152 | name = "quote" 153 | version = "1.0.37" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 156 | dependencies = [ 157 | "proc-macro2", 158 | ] 159 | 160 | [[package]] 161 | name = "regex-automata" 162 | version = "0.3.9" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" 165 | dependencies = [ 166 | "aho-corasick", 167 | "memchr", 168 | "regex-syntax", 169 | ] 170 | 171 | [[package]] 172 | name = "regex-syntax" 173 | version = "0.7.5" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" 176 | 177 | [[package]] 178 | name = "serde" 179 | version = "1.0.214" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" 182 | dependencies = [ 183 | "serde_derive", 184 | ] 185 | 186 | [[package]] 187 | name = "serde_derive" 188 | version = "1.0.214" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" 191 | dependencies = [ 192 | "proc-macro2", 193 | "quote", 194 | "syn", 195 | ] 196 | 197 | [[package]] 198 | name = "shlex" 199 | version = "1.3.0" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 202 | 203 | [[package]] 204 | name = "stacker" 205 | version = "0.1.17" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "799c883d55abdb5e98af1a7b3f23b9b6de8ecada0ecac058672d7635eb48ca7b" 208 | dependencies = [ 209 | "cc", 210 | "cfg-if", 211 | "libc", 212 | "psm", 213 | "windows-sys", 214 | ] 215 | 216 | [[package]] 217 | name = "syn" 218 | version = "2.0.87" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" 221 | dependencies = [ 222 | "proc-macro2", 223 | "quote", 224 | "unicode-ident", 225 | ] 226 | 227 | [[package]] 228 | name = "thiserror" 229 | version = "2.0.3" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" 232 | dependencies = [ 233 | "thiserror-impl", 234 | ] 235 | 236 | [[package]] 237 | name = "thiserror-impl" 238 | version = "2.0.3" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" 241 | dependencies = [ 242 | "proc-macro2", 243 | "quote", 244 | "syn", 245 | ] 246 | 247 | [[package]] 248 | name = "unicode-ident" 249 | version = "1.0.13" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 252 | 253 | [[package]] 254 | name = "unicode-width" 255 | version = "0.1.14" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 258 | 259 | [[package]] 260 | name = "version_check" 261 | version = "0.9.5" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 264 | 265 | [[package]] 266 | name = "windows-sys" 267 | version = "0.59.0" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 270 | dependencies = [ 271 | "windows-targets", 272 | ] 273 | 274 | [[package]] 275 | name = "windows-targets" 276 | version = "0.52.6" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 279 | dependencies = [ 280 | "windows_aarch64_gnullvm", 281 | "windows_aarch64_msvc", 282 | "windows_i686_gnu", 283 | "windows_i686_gnullvm", 284 | "windows_i686_msvc", 285 | "windows_x86_64_gnu", 286 | "windows_x86_64_gnullvm", 287 | "windows_x86_64_msvc", 288 | ] 289 | 290 | [[package]] 291 | name = "windows_aarch64_gnullvm" 292 | version = "0.52.6" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 295 | 296 | [[package]] 297 | name = "windows_aarch64_msvc" 298 | version = "0.52.6" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 301 | 302 | [[package]] 303 | name = "windows_i686_gnu" 304 | version = "0.52.6" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 307 | 308 | [[package]] 309 | name = "windows_i686_gnullvm" 310 | version = "0.52.6" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 313 | 314 | [[package]] 315 | name = "windows_i686_msvc" 316 | version = "0.52.6" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 319 | 320 | [[package]] 321 | name = "windows_x86_64_gnu" 322 | version = "0.52.6" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 325 | 326 | [[package]] 327 | name = "windows_x86_64_gnullvm" 328 | version = "0.52.6" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 331 | 332 | [[package]] 333 | name = "windows_x86_64_msvc" 334 | version = "0.52.6" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 337 | 338 | [[package]] 339 | name = "yansi" 340 | version = "1.0.1" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" 343 | 344 | [[package]] 345 | name = "zerocopy" 346 | version = "0.7.35" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 349 | dependencies = [ 350 | "zerocopy-derive", 351 | ] 352 | 353 | [[package]] 354 | name = "zerocopy-derive" 355 | version = "0.7.35" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 358 | dependencies = [ 359 | "proc-macro2", 360 | "quote", 361 | "syn", 362 | ] 363 | -------------------------------------------------------------------------------- /src/typescript/ast.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, fmt}; 2 | 3 | use chumsky::prelude::*; 4 | use num_traits::{Float, ToPrimitive}; 5 | use ordered_float::OrderedFloat; 6 | use r#type::{Type, TypeExpr}; 7 | 8 | pub mod r#type; 9 | 10 | pub type Spanned = (T, SimpleSpan); 11 | pub type TypeSpanned<'src, T> = (T, Type<'src>, SimpleSpan); 12 | 13 | pub fn create_type_spanned(value: T, type_: Type<'_>) -> TypeSpanned<'_, T> { 14 | (value, type_, SimpleSpan::new(0, 0)) 15 | } 16 | 17 | #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 18 | pub enum Value<'src> { 19 | Bool(bool), 20 | Num(OrderedFloat), 21 | Str(&'src str), 22 | Null, 23 | Undefined, 24 | } 25 | 26 | pub fn float_to_usize(n: &OrderedFloat) -> Option { 27 | if n.abs_sub(n.round()) < Float::epsilon() { 28 | n.to_usize() 29 | } else { 30 | None 31 | } 32 | } 33 | 34 | impl fmt::Display for Value<'_> { 35 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 36 | match self { 37 | Value::Null => write!(f, "null"), 38 | Value::Undefined => write!(f, "undefined"), 39 | Value::Num(n) => write!(f, "{}", n), 40 | Value::Bool(b) => write!(f, "{}", b), 41 | Value::Str(s) => write!(f, "\"{}\"", s), 42 | } 43 | } 44 | } 45 | 46 | #[derive(Clone, Debug, PartialEq)] 47 | pub enum BinaryOp { 48 | Add, 49 | Sub, 50 | Mul, 51 | Div, 52 | And, 53 | Or, 54 | StrictEquality, 55 | StrictInequality, 56 | } 57 | 58 | impl fmt::Display for BinaryOp { 59 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 60 | match self { 61 | BinaryOp::Add => write!(f, "+"), 62 | BinaryOp::Sub => write!(f, "-"), 63 | BinaryOp::Mul => write!(f, "*"), 64 | BinaryOp::Div => write!(f, "/"), 65 | BinaryOp::And => write!(f, "&&"), 66 | BinaryOp::Or => write!(f, "||"), 67 | BinaryOp::StrictEquality => write!(f, "==="), 68 | BinaryOp::StrictInequality => write!(f, "!=="), 69 | } 70 | } 71 | } 72 | 73 | #[derive(Debug, PartialEq)] 74 | pub struct Function<'src> { 75 | pub args: Vec<(&'src str, TypeSpanned<'src, TypeExpr<'src>>)>, 76 | pub body: Box>>, 77 | pub return_type: Option>>>, 78 | } 79 | 80 | #[derive(Debug, PartialEq)] 81 | pub enum Expr<'src> { 82 | Value(Value<'src>), 83 | Object(HashMap, Box>>), 84 | DotProperty(Box>, Spanned<&'src str>), 85 | BracketProperty(Box>, Box>), 86 | ArrowFunction(Function<'src>), 87 | Array(Vec>), 88 | Var(&'src str), 89 | Binary( 90 | Box>, 91 | BinaryOp, 92 | Box>, 93 | ), 94 | Assign(Box>, Box>), 95 | FunctionCall(Box>, Vec>), 96 | If( 97 | Box>, 98 | Box>, 99 | Box>, 100 | ), 101 | } 102 | 103 | impl fmt::Display for Expr<'_> { 104 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 105 | match self { 106 | Expr::Value(v) => write!(f, "{}", v), 107 | Expr::Object(o) => { 108 | if o.is_empty() { 109 | return write!(f, "{{}}"); 110 | } 111 | write!(f, "{{ ")?; 112 | for (i, (k, v)) in o.iter().enumerate() { 113 | write!(f, "{}: {}", k, v.0)?; 114 | if i < o.len() - 1 { 115 | write!(f, ", ")?; 116 | } 117 | } 118 | write!(f, " }}") 119 | } 120 | Expr::DotProperty(obj, prop) => write!(f, "({}.{})", obj.0, prop.0), 121 | Expr::BracketProperty(obj, prop) => write!(f, "({}[{}])", obj.0, prop.0), 122 | Expr::ArrowFunction(func) => { 123 | write!(f, "(")?; 124 | for (i, (arg, arg_type)) in func.args.iter().enumerate() { 125 | write!(f, "{}: {}", arg, arg_type.0)?; 126 | if i < func.args.len() - 1 { 127 | write!(f, ", ")?; 128 | } 129 | } 130 | if let Some(return_type) = &func.return_type { 131 | write!(f, "): {}", return_type.0)?; 132 | } 133 | write!(f, " => ")?; 134 | // TODO: proper level 135 | func.body.0.pretty_print_block(f, 0) 136 | } 137 | Expr::Array(arr) => { 138 | write!(f, "[")?; 139 | for (i, expr) in arr.iter().enumerate() { 140 | write!(f, "{}", expr.0)?; 141 | if i < arr.len() - 1 { 142 | write!(f, ", ")?; 143 | } 144 | } 145 | write!(f, "]") 146 | } 147 | Expr::Var(s) => write!(f, "{}", s), 148 | Expr::Binary(lhs, op, rhs) => write!(f, "({} {} {})", lhs.0, op, rhs.0), 149 | Expr::Assign(lhs, rhs) => write!(f, "({} = {})", lhs.0, rhs.0), 150 | Expr::FunctionCall(func, args) => { 151 | write!(f, "{}(", func.0)?; 152 | for (i, arg) in args.iter().enumerate() { 153 | write!(f, "{}", arg.0)?; 154 | if i < args.len() - 1 { 155 | write!(f, ", ")?; 156 | } 157 | } 158 | write!(f, ")") 159 | } 160 | Expr::If(cond, then, else_) => { 161 | write!(f, "({} ? {} : {})", cond.0, then.0, else_.0) 162 | } 163 | } 164 | } 165 | } 166 | 167 | #[derive(Debug, PartialEq)] 168 | pub enum ClassMember<'src> { 169 | Field { 170 | type_expr: Option>>, 171 | expr: TypeSpanned<'src, Expr<'src>>, 172 | }, 173 | Method(Function<'src>), 174 | } 175 | 176 | #[derive(Debug, PartialEq)] 177 | pub enum Stmt<'src> { 178 | Expr(TypeSpanned<'src, Expr<'src>>), 179 | Decl { 180 | name: &'src str, 181 | type_expr: Option>>, 182 | expr: TypeSpanned<'src, Expr<'src>>, 183 | body: Box>, 184 | }, 185 | TypeDecl { 186 | name: &'src str, 187 | type_expr: TypeSpanned<'src, TypeExpr<'src>>, 188 | body: Box>, 189 | }, 190 | Function(&'src str, Function<'src>), 191 | If( 192 | TypeSpanned<'src, Expr<'src>>, 193 | Box>, 194 | Option>>, 195 | ), 196 | Seq(Box>, Box>), 197 | Return(TypeSpanned<'src, Expr<'src>>), 198 | Class { 199 | name: &'src str, 200 | constructor: Function<'src>, 201 | members: HashMap<&'src str, ClassMember<'src>>, 202 | }, 203 | Noop, 204 | } 205 | 206 | impl<'src> Stmt<'src> { 207 | fn pretty_print_block(&self, f: &mut fmt::Formatter<'_>, level: usize) -> fmt::Result { 208 | if matches!(self, Stmt::Noop) { 209 | return write!(f, "{{}}"); 210 | } 211 | let indent = " ".repeat(level); 212 | writeln!(f, "{{")?; 213 | self.pretty_print(f, level + 1)?; 214 | write!(f, "\n{}}}", indent) 215 | } 216 | 217 | fn pretty_print(&self, f: &mut fmt::Formatter<'_>, level: usize) -> fmt::Result { 218 | let indent = " ".repeat(level); 219 | match self { 220 | Stmt::Expr(expr) => write!(f, "{}{};", indent, expr.0), 221 | Stmt::Decl { 222 | name, 223 | type_expr, 224 | expr, 225 | body, 226 | } => { 227 | let type_ = (type_expr.as_ref()) 228 | .map(|type_| type_.0.to_string()) 229 | .unwrap_or_else(|| expr.1.to_string()); 230 | write!(f, "{}let {}: {} = {};", indent, name, type_, expr.0)?; 231 | match body.0 { 232 | Stmt::Noop => Ok(()), 233 | _ => { 234 | writeln!(f)?; 235 | body.0.pretty_print(f, level) 236 | } 237 | } 238 | } 239 | Stmt::TypeDecl { 240 | name, 241 | type_expr, 242 | body, 243 | } => { 244 | write!( 245 | f, 246 | "{}type {} = {}; // {}", 247 | indent, name, type_expr.0, type_expr.1 248 | )?; 249 | match body.0 { 250 | Stmt::Noop => Ok(()), 251 | _ => { 252 | writeln!(f)?; 253 | body.0.pretty_print(f, level) 254 | } 255 | } 256 | } 257 | Stmt::Function(name, func) => { 258 | write!(f, "{}function {}(", indent, name)?; 259 | for (i, (arg, type_)) in func.args.iter().enumerate() { 260 | write!(f, "{}: {}", arg, type_.0)?; 261 | if i < func.args.len() - 1 { 262 | write!(f, ", ")?; 263 | } 264 | } 265 | let return_type = (func.return_type.as_ref()) 266 | .map(|type_| type_.0.to_string()) 267 | .unwrap_or(Type::Any.to_string()); 268 | write!(f, "): {} ", return_type)?; 269 | func.body.0.pretty_print_block(f, level) 270 | } 271 | Stmt::If(cond, then, Some(else_)) => { 272 | write!(f, "{}if ({}) ", indent, cond.0)?; 273 | then.0.pretty_print_block(f, level)?; 274 | write!(f, " else ")?; 275 | else_.0.pretty_print_block(f, level) 276 | } 277 | Stmt::If(cond, then, None) => { 278 | write!(f, "{}if ({}) ", indent, cond.0)?; 279 | then.0.pretty_print_block(f, level) 280 | } 281 | Stmt::Seq(lhs, rhs) => { 282 | lhs.0.pretty_print(f, level)?; 283 | match rhs.0 { 284 | Stmt::Noop => Ok(()), 285 | _ => { 286 | writeln!(f)?; 287 | rhs.0.pretty_print(f, level) 288 | } 289 | } 290 | } 291 | Stmt::Return(expr) => write!(f, "{}return {};", indent, expr.0), 292 | Stmt::Class { 293 | name, 294 | constructor, 295 | members, 296 | } => { 297 | let member_indent = " ".repeat(level + 1); 298 | writeln!(f, "{}class {} {{", indent, name)?; 299 | write!(f, "{}constructor(", member_indent)?; 300 | for (i, (arg, arg_type)) in constructor.args.iter().enumerate() { 301 | write!(f, "{}: {}", arg, arg_type.0)?; 302 | if i < constructor.args.len() - 1 { 303 | write!(f, ", ")?; 304 | } 305 | } 306 | let return_type: String = (constructor.return_type.as_ref()) 307 | .map(|type_| type_.0.to_string()) 308 | .unwrap_or(Type::Any.to_string()); 309 | write!(f, "): {} ", return_type)?; 310 | constructor.body.0.pretty_print_block(f, level + 1)?; 311 | writeln!(f)?; 312 | for (name, member) in members.iter() { 313 | write!(f, "{}{}", member_indent, name)?; 314 | match member { 315 | ClassMember::Field { type_expr, expr } => { 316 | let type_: String = (type_expr.as_ref()) 317 | .map(|type_| type_.0.to_string()) 318 | .unwrap_or_else(|| expr.1.to_string()); 319 | writeln!(f, ": {} = {};", type_, expr.0)?; 320 | } 321 | ClassMember::Method(func) => { 322 | write!(f, "(")?; 323 | for (i, (arg, arg_type)) in func.args.iter().enumerate() { 324 | write!(f, "{}: {}", arg, arg_type.0)?; 325 | if i < func.args.len() - 1 { 326 | write!(f, ", ")?; 327 | } 328 | } 329 | write!(f, ")")?; 330 | let return_type: String = (func.return_type.as_ref()) 331 | .map(|type_| type_.0.to_string()) 332 | .unwrap_or(Type::Any.to_string()); 333 | write!(f, ": {} ", return_type)?; 334 | func.body.0.pretty_print_block(f, level + 1)?; 335 | writeln!(f)?; 336 | } 337 | } 338 | } 339 | write!(f, "{}}}", indent) 340 | } 341 | Stmt::Noop => Ok(()), 342 | } 343 | } 344 | } 345 | 346 | impl fmt::Display for Stmt<'_> { 347 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 348 | self.pretty_print(f, 0) 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /src/typescript/parser.rs: -------------------------------------------------------------------------------- 1 | use chumsky::{input::*, pratt::*, prelude::*}; 2 | 3 | use super::{ast::r#type::*, ast::*, lexer::Token}; 4 | 5 | fn value_parser<'src, I>( 6 | ) -> impl Parser<'src, I, Value<'src>, extra::Err, SimpleSpan>>> + Clone 7 | where 8 | I: ValueInput<'src, Token = Token<'src>, Span = SimpleSpan>, 9 | { 10 | select! { 11 | Token::Null => Value::Null, 12 | Token::Undefined => Value::Undefined, 13 | Token::Num(n) => Value::Num(n), 14 | Token::Bool(b) => Value::Bool(b), 15 | Token::Str(s) => Value::Str(s), 16 | } 17 | .labelled("value") 18 | } 19 | 20 | fn type_expr_parser<'src, I>() -> impl Parser< 21 | 'src, 22 | I, 23 | TypeSpanned<'src, TypeExpr<'src>>, 24 | extra::Err, SimpleSpan>>, 25 | > + Clone 26 | where 27 | I: ValueInput<'src, Token = Token<'src>, Span = SimpleSpan>, 28 | { 29 | recursive(|type_expr| { 30 | let value = value_parser(); 31 | 32 | let ident = select! { Token::Ident(s) => s }.labelled("identifier"); 33 | 34 | let var = ident 35 | .map(Expr::Var) 36 | .or(just(Token::This).map(|_| Expr::Var("this"))) 37 | .map_with(|expr, e| (expr, Type::Never, e.span())); 38 | 39 | let expr = var.pratt(( 40 | postfix( 41 | 17, 42 | just(Token::Op(".")).ignore_then(ident.map_with(|ident, e| (ident, e.span()))), 43 | |a, b, e: &mut MapExtra<'src, '_, I, _>| { 44 | (Expr::DotProperty(Box::new(a), b), Type::Never, e.span()) 45 | }, 46 | ), 47 | postfix( 48 | 17, 49 | (ident.map(Expr::Var)) 50 | .or(value.clone().map(Expr::Value)) 51 | .map_with(|expr, e| (expr, Type::Never, e.span())) 52 | .delimited_by(just(Token::Ctrl('[')), just(Token::Ctrl(']'))), 53 | |a, b, e: &mut MapExtra<'src, '_, I, _>| { 54 | ( 55 | Expr::BracketProperty(Box::new(a), Box::new(b)), 56 | Type::Never, 57 | e.span(), 58 | ) 59 | }, 60 | ), 61 | )); 62 | 63 | let type_of = just(Token::TypeOf).ignore_then(expr).map(TypeExpr::TypeOf); 64 | 65 | let value_type = value.clone().map(Type::Value).map(TypeExpr::Type); 66 | 67 | let type_literal = select! { 68 | Token::Ident("any") => Type::Any, 69 | Token::Ident("never") => Type::Never, 70 | Token::Ident("boolean") => Type::Bool, 71 | Token::Ident("number") => Type::Num, 72 | Token::Ident("string") => Type::Str, 73 | Token::Void => Type::Void, 74 | } 75 | .map(TypeExpr::Type) 76 | .labelled("type literal"); 77 | 78 | let field = value 79 | .or(ident.map(Value::Str)) 80 | .then_ignore(just(Token::Op(":"))) 81 | .then(type_expr.clone().map(Box::new)); 82 | 83 | let object = field 84 | .separated_by(just(Token::Ctrl(';'))) 85 | .allow_trailing() 86 | .collect() 87 | .delimited_by(just(Token::Ctrl('{')), just(Token::Ctrl('}'))) 88 | .map(TypeExpr::Object) 89 | .boxed(); 90 | 91 | let record = just(Token::Ident("Record")) 92 | .ignore_then(just(Token::Ctrl('<'))) 93 | .ignore_then(type_expr.clone()) 94 | .then_ignore(just(Token::Ctrl(','))) 95 | .then(type_expr.clone()) 96 | .then_ignore(just(Token::Ctrl('>'))) 97 | .map(|(k, v)| TypeExpr::Record((Box::new(k), Box::new(v)))) 98 | .boxed(); 99 | 100 | let arg = ident 101 | .then_ignore(just(Token::Op(":"))) 102 | .then(type_expr.clone().map(Box::new)); 103 | 104 | let args = arg 105 | .separated_by(just(Token::Ctrl(','))) 106 | .allow_trailing() 107 | .collect() 108 | .delimited_by(just(Token::Ctrl('(')), just(Token::Ctrl(')'))); 109 | 110 | let function_type = args 111 | .then_ignore(just(Token::Op("="))) 112 | .then_ignore(just(Token::Ctrl('>'))) 113 | .then(type_expr.clone().map(Box::new)) 114 | .map(|(arg_types, return_type)| TypeExpr::Function { 115 | arg_types, 116 | return_type, 117 | }) 118 | .boxed(); 119 | 120 | let fixed_array = (type_expr.clone()) 121 | .separated_by(just(Token::Ctrl(','))) 122 | .allow_trailing() 123 | .collect() 124 | .delimited_by(just(Token::Ctrl('[')), just(Token::Ctrl(']'))) 125 | .map(TypeExpr::FixedArray) 126 | .boxed(); 127 | 128 | let atom = choice(( 129 | value_type, 130 | type_literal, 131 | type_of, 132 | object, 133 | record, 134 | function_type, 135 | fixed_array, 136 | ident.map(TypeExpr::Var), 137 | )); 138 | 139 | choice(( 140 | atom.map_with(|type_expr, e| (type_expr, Type::Never, e.span())), 141 | type_expr.delimited_by(just(Token::Ctrl('(')), just(Token::Ctrl(')'))), 142 | )) 143 | .pratt(( 144 | postfix( 145 | 17, 146 | just(Token::Ctrl('[')).then(just(Token::Ctrl(']'))), 147 | |a, _, e: &mut MapExtra<'src, '_, I, _>| { 148 | (TypeExpr::Array(Box::new(a)), Type::Never, e.span()) 149 | }, 150 | ), 151 | infix( 152 | left(7), 153 | just(Token::Op("&")), 154 | |a, _, b, e: &mut MapExtra<'src, '_, I, _>| { 155 | ( 156 | TypeExpr::Binary(Box::new(a), TypeBinaryOp::And, Box::new(b)), 157 | Type::Never, 158 | e.span(), 159 | ) 160 | }, 161 | ), 162 | infix( 163 | left(5), 164 | just(Token::Op("|")), 165 | |a, _, b, e: &mut MapExtra<'src, '_, I, _>| { 166 | ( 167 | TypeExpr::Binary(Box::new(a), TypeBinaryOp::Or, Box::new(b)), 168 | Type::Never, 169 | e.span(), 170 | ) 171 | }, 172 | ), 173 | )) 174 | .boxed() 175 | .labelled("type expression") 176 | .as_context() 177 | }) 178 | } 179 | 180 | fn expr_parser<'src, I>( 181 | stmt: impl Parser<'src, I, Spanned>, extra::Err, SimpleSpan>>> 182 | + Clone 183 | + 'src, 184 | ) -> impl Parser< 185 | 'src, 186 | I, 187 | TypeSpanned<'src, Expr<'src>>, 188 | extra::Err, SimpleSpan>>, 189 | > + Clone 190 | where 191 | I: ValueInput<'src, Token = Token<'src>, Span = SimpleSpan>, 192 | { 193 | recursive(|expr| { 194 | let value = value_parser(); 195 | 196 | let ident = select! { Token::Ident(s) => s }.labelled("identifier"); 197 | 198 | let value_expr = value.clone().map(Expr::Value); 199 | 200 | let field = value 201 | .or(ident.map(Value::Str)) 202 | .then_ignore(just(Token::Op(":"))) 203 | .then(expr.clone().map(Box::new)); 204 | 205 | let object = field 206 | .separated_by(just(Token::Ctrl(','))) 207 | .allow_trailing() 208 | .collect() 209 | .delimited_by(just(Token::Ctrl('{')), just(Token::Ctrl('}'))) 210 | .map(Expr::Object) 211 | .boxed(); 212 | 213 | let array = (expr.clone()) 214 | .separated_by(just(Token::Ctrl(','))) 215 | .allow_trailing() 216 | .collect() 217 | .delimited_by(just(Token::Ctrl('[')), just(Token::Ctrl(']'))) 218 | .map(Expr::Array) 219 | .boxed(); 220 | 221 | let var = ident 222 | .map(Expr::Var) 223 | .or(just(Token::This).map(|_| Expr::Var("this"))); 224 | 225 | // expressions without ambiguity 226 | let atom = choice((value_expr, object, array, var)); 227 | 228 | let expr = choice(( 229 | atom.map_with(|expr, e| (expr, Type::Never, e.span())), 230 | expr.clone() 231 | .delimited_by(just(Token::Ctrl('(')), just(Token::Ctrl(')'))), 232 | )) 233 | .pratt(( 234 | postfix( 235 | 17, 236 | just(Token::Op(".")).ignore_then(ident.map_with(|ident, e| (ident, e.span()))), 237 | |a, b, e: &mut MapExtra<'src, '_, I, _>| { 238 | (Expr::DotProperty(Box::new(a), b), Type::Never, e.span()) 239 | }, 240 | ), 241 | postfix( 242 | 17, 243 | expr.clone() 244 | .delimited_by(just(Token::Ctrl('[')), just(Token::Ctrl(']'))), 245 | |a, b, e: &mut MapExtra<'src, '_, I, _>| { 246 | ( 247 | Expr::BracketProperty(Box::new(a), Box::new(b)), 248 | Type::Never, 249 | e.span(), 250 | ) 251 | }, 252 | ), 253 | postfix( 254 | 17, 255 | expr.clone() 256 | .separated_by(just(Token::Ctrl(','))) 257 | .allow_trailing() 258 | .collect() 259 | .delimited_by(just(Token::Ctrl('(')), just(Token::Ctrl(')'))), 260 | |a, b, e: &mut MapExtra<'src, '_, I, _>| { 261 | (Expr::FunctionCall(Box::new(a), b), Type::Never, e.span()) 262 | }, 263 | ), 264 | infix( 265 | left(12), 266 | just(Token::Op("*")) 267 | .to(BinaryOp::Mul) 268 | .or(just(Token::Op("/")).to(BinaryOp::Div)), 269 | |a, op, b, e: &mut MapExtra<'src, '_, I, _>| { 270 | ( 271 | Expr::Binary(Box::new(a), op, Box::new(b)), 272 | Type::Never, 273 | e.span(), 274 | ) 275 | }, 276 | ), 277 | infix( 278 | left(11), 279 | just(Token::Op("+")) 280 | .to(BinaryOp::Add) 281 | .or(just(Token::Op("-")).to(BinaryOp::Sub)), 282 | |a, op, b, e: &mut MapExtra<'src, '_, I, _>| { 283 | ( 284 | Expr::Binary(Box::new(a), op, Box::new(b)), 285 | Type::Never, 286 | e.span(), 287 | ) 288 | }, 289 | ), 290 | infix( 291 | left(8), 292 | just(Token::Op("===")) 293 | .to(BinaryOp::StrictEquality) 294 | .or(just(Token::Op("!==")).to(BinaryOp::StrictInequality)), 295 | |a, op, b, e: &mut MapExtra<'src, '_, I, _>| { 296 | ( 297 | Expr::Binary(Box::new(a), op, Box::new(b)), 298 | Type::Never, 299 | e.span(), 300 | ) 301 | }, 302 | ), 303 | infix( 304 | left(4), 305 | just(Token::Op("&&")), 306 | |a, _, b, e: &mut MapExtra<'src, '_, I, _>| { 307 | ( 308 | Expr::Binary(Box::new(a), BinaryOp::And, Box::new(b)), 309 | Type::Never, 310 | e.span(), 311 | ) 312 | }, 313 | ), 314 | infix( 315 | left(3), 316 | just(Token::Op("||")), 317 | |a, _, b, e: &mut MapExtra<'src, '_, I, _>| { 318 | ( 319 | Expr::Binary(Box::new(a), BinaryOp::Or, Box::new(b)), 320 | Type::Never, 321 | e.span(), 322 | ) 323 | }, 324 | ), 325 | infix( 326 | right(2), 327 | just(Token::Op("=")), 328 | |a, _, b, e: &mut MapExtra<'src, '_, I, _>| { 329 | ( 330 | Expr::Assign(Box::new(a), Box::new(b)), 331 | Type::Never, 332 | e.span(), 333 | ) 334 | }, 335 | ), 336 | postfix( 337 | 2, 338 | just(Token::Op("?")) 339 | .ignore_then(expr.clone()) 340 | .then_ignore(just(Token::Op(":"))) 341 | .then(expr.clone()), 342 | |a, (b, c), e: &mut MapExtra<'src, '_, I, _>| { 343 | ( 344 | Expr::If(Box::new(a), Box::new(b), Box::new(c)), 345 | Type::Never, 346 | e.span(), 347 | ) 348 | }, 349 | ), 350 | )); 351 | 352 | // TODO: this precedence for lambdas is wrong, but chumsky pratt parsers currently don't 353 | // support a mechanism for validating like `try_map` 354 | let type_expr = type_expr_parser(); 355 | 356 | let function_arg = ident 357 | .then(just(Token::Op(":")).ignore_then(type_expr.clone())) 358 | .map(|(name, type_)| (name, type_)); 359 | 360 | let function_args = function_arg 361 | .separated_by(just(Token::Ctrl(','))) 362 | .allow_trailing() 363 | .collect() 364 | .delimited_by(just(Token::Ctrl('(')), just(Token::Ctrl(')'))) 365 | .boxed(); 366 | 367 | let return_type = just(Token::Op(":")) 368 | .ignore_then(type_expr.clone()) 369 | .map(Box::new) 370 | .or_not(); 371 | 372 | let arrow_block = (stmt.or_not()) 373 | .map_with(|stmt, e| stmt.unwrap_or_else(|| (Stmt::Noop, e.span()))) 374 | .delimited_by(just(Token::Ctrl('{')), just(Token::Ctrl('}'))) 375 | .or((expr.clone()).map_with(|expr, e| (Stmt::Return(expr), e.span()))) 376 | .boxed(); 377 | 378 | let arrow_function = function_args 379 | .then(return_type) 380 | .then_ignore(just(Token::Op("="))) 381 | .then_ignore(just(Token::Ctrl('>'))) 382 | .then(arrow_block.clone().map(Box::new)) 383 | .map_with(|((args, return_type), body), e| { 384 | ( 385 | Expr::ArrowFunction(Function { 386 | args, 387 | body, 388 | return_type, 389 | }), 390 | Type::Never, 391 | e.span(), 392 | ) 393 | }) 394 | .boxed(); 395 | 396 | expr.or(arrow_function).labelled("expression").as_context() 397 | }) 398 | } 399 | 400 | fn stmt_parser<'src, I>( 401 | expr: impl Parser< 402 | 'src, 403 | I, 404 | TypeSpanned<'src, Expr<'src>>, 405 | extra::Err, SimpleSpan>>, 406 | > + Clone 407 | + 'src, 408 | ) -> impl Parser<'src, I, Spanned>, extra::Err, SimpleSpan>>> + Clone 409 | where 410 | I: ValueInput<'src, Token = Token<'src>, Span = SimpleSpan>, 411 | { 412 | recursive(|stmt| { 413 | let type_expr = type_expr_parser(); 414 | 415 | let expr_stmt = (expr.clone()) 416 | .then_ignore(just(Token::Ctrl(';'))) 417 | .map(Stmt::Expr); 418 | 419 | let return_ = just(Token::Return) 420 | .ignore_then(expr.clone()) 421 | .then_ignore(just(Token::Ctrl(';'))) 422 | .map(Stmt::Return); 423 | 424 | let ident = select! { Token::Ident(s) => s }.labelled("identifier"); 425 | 426 | let block = (expr_stmt.clone()) 427 | .map_with(|expr, e| (expr, e.span())) 428 | .or((stmt.clone().or_not()) 429 | .map_with(|stmt, e| stmt.unwrap_or_else(|| (Stmt::Noop, e.span()))) 430 | .delimited_by(just(Token::Ctrl('{')), just(Token::Ctrl('}')))) 431 | .map(Box::new) 432 | .boxed(); 433 | 434 | let function_arg = ident 435 | .then(just(Token::Op(":")).ignore_then(type_expr.clone())) 436 | .map(|(name, type_)| (name, type_)); 437 | 438 | let function_args = function_arg 439 | .separated_by(just(Token::Ctrl(','))) 440 | .allow_trailing() 441 | .collect() 442 | .delimited_by(just(Token::Ctrl('(')), just(Token::Ctrl(')'))) 443 | .boxed(); 444 | 445 | let return_type = just(Token::Op(":")) 446 | .ignore_then(type_expr.clone()) 447 | .map(Box::new) 448 | .or_not(); 449 | 450 | let function = just(Token::Function) 451 | .ignore_then(ident) 452 | .then(function_args.clone()) 453 | .then(return_type.clone()) 454 | .then(block.clone()) 455 | .map(|(((name, args), return_type), body)| { 456 | Stmt::Function( 457 | name, 458 | Function { 459 | args, 460 | return_type, 461 | body, 462 | }, 463 | ) 464 | }) 465 | .boxed(); 466 | 467 | let if_ = just(Token::If) 468 | .ignore_then( 469 | expr.clone() 470 | .delimited_by(just(Token::Ctrl('(')), just(Token::Ctrl(')'))), 471 | ) 472 | .then(block.clone()) 473 | .then(just(Token::Else).ignore_then(block.clone()).or_not()) 474 | .map(|((cond, then), else_)| Stmt::If(cond, then, else_)) 475 | .boxed(); 476 | 477 | let decl = just(Token::Let) 478 | .ignore_then(ident) 479 | .then(just(Token::Op(":")).ignore_then(type_expr.clone()).or_not()) 480 | .then_ignore(just(Token::Op("="))) 481 | .then(expr.clone()) 482 | .then_ignore(just(Token::Ctrl(';'))) 483 | .then( 484 | stmt.clone() 485 | .or_not() 486 | .map_with(|body, e| Box::new(body.unwrap_or_else(|| (Stmt::Noop, e.span())))), 487 | ) 488 | .map(|(((name, type_expr), expr), body)| Stmt::Decl { 489 | name, 490 | type_expr, 491 | expr, 492 | body, 493 | }) 494 | .boxed(); 495 | 496 | let type_decl = just(Token::Ident("type")) 497 | .ignore_then(ident) 498 | .then_ignore(just(Token::Op("="))) 499 | .then(type_expr.clone()) 500 | .then_ignore(just(Token::Ctrl(';'))) 501 | .then( 502 | stmt.clone() 503 | .or_not() 504 | .map_with(|body, e| Box::new(body.unwrap_or_else(|| (Stmt::Noop, e.span())))), 505 | ) 506 | .map(|((name, type_expr), body)| Stmt::TypeDecl { 507 | name, 508 | type_expr, 509 | body, 510 | }) 511 | .boxed(); 512 | 513 | let constructor = just(Token::Ident("constructor")) 514 | .ignore_then(function_args.clone()) 515 | .then(block.clone()) 516 | .or_not() 517 | .map_with(|constructor, e| { 518 | let (args, body) = 519 | constructor.unwrap_or((Vec::new(), Box::new((Stmt::Noop, e.span())))); 520 | Function { 521 | args, 522 | body, 523 | return_type: None, 524 | } 525 | }); 526 | 527 | let class_field = just(Token::Op(":")) 528 | .ignore_then(type_expr) 529 | .or_not() 530 | .then(just(Token::Op("=")).ignore_then(expr.clone())) 531 | .then_ignore(just(Token::Ctrl(';'))) 532 | .map(|(type_expr, expr)| ClassMember::Field { type_expr, expr }) 533 | .boxed(); 534 | 535 | let class_method = (function_args.clone()) 536 | .then(return_type.clone()) 537 | .then(block) 538 | .map(|((args, return_type), body)| { 539 | ClassMember::Method(Function { 540 | args, 541 | body, 542 | return_type, 543 | }) 544 | }) 545 | .boxed(); 546 | 547 | let class_member_ident = select! { Token::Ident(s) if s != "constructor" => s }; 548 | 549 | let class_members = class_member_ident 550 | .then(class_field.or(class_method)) 551 | .repeated() 552 | .collect::>(); 553 | 554 | let class_body = (class_members.clone()) 555 | .then(constructor) 556 | .then(class_members) 557 | .map(|((members1, constructor), members2)| { 558 | let members = members1.into_iter().chain(members2).collect(); 559 | (constructor, members) 560 | }) 561 | .delimited_by(just(Token::Ctrl('{')), just(Token::Ctrl('}'))) 562 | .boxed(); 563 | 564 | let class = just(Token::Class) 565 | .ignore_then(ident) 566 | .then(class_body) 567 | .map(|(name, (constructor, members))| Stmt::Class { 568 | name, 569 | constructor, 570 | members, 571 | }) 572 | .boxed(); 573 | 574 | choice((expr_stmt, return_, function, if_, decl, type_decl, class)) 575 | .map_with(|stmt, e| (stmt, e.span())) 576 | .foldl_with(stmt.clone().or_not(), |a, b, e| { 577 | (Stmt::Seq(Box::new(a), Box::new(b)), e.span()) 578 | }) 579 | .labelled("statement") 580 | }) 581 | } 582 | 583 | pub fn parser<'src, I>( 584 | ) -> impl Parser<'src, I, Spanned>, extra::Err, SimpleSpan>>> + Clone 585 | where 586 | I: ValueInput<'src, Token = Token<'src>, Span = SimpleSpan>, 587 | { 588 | let mut stmt = Recursive::declare(); 589 | let mut expr = Recursive::declare(); 590 | 591 | stmt.define(stmt_parser(expr.clone())); 592 | expr.define(expr_parser(stmt.clone())); 593 | 594 | stmt 595 | } 596 | -------------------------------------------------------------------------------- /src/typescript/ast/type.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::{BTreeMap, BTreeSet, HashMap}, 3 | fmt, 4 | iter::zip, 5 | }; 6 | 7 | use thiserror::Error; 8 | 9 | use super::{Expr, TypeSpanned, Value}; 10 | 11 | #[derive(Error, Debug)] 12 | pub enum TypeError<'src> { 13 | #[error("expected {expected}; got {actual}")] 14 | StaticType { 15 | expected: Type<'src>, 16 | actual: Type<'src>, 17 | }, 18 | #[error("undefined variable \"{0}\"")] 19 | UndefinedVariable(String), 20 | #[error("undefined property \"{1}\" for type '{0}'")] 21 | UndefinedProperty(Type<'src>, String), 22 | #[error("undefined type \"{0}\"")] 23 | UndefinedType(String), 24 | #[error("only variables and object properties can be assigned to; got: {0}")] 25 | CannotAssign(Type<'src>), 26 | #[error("cannot call non-function; got: {0}")] 27 | CannotCall(Type<'src>), 28 | #[error("expected {expected} argument(s); got {actual}")] 29 | UnexpectedArguments { expected: usize, actual: usize }, 30 | #[error("missing ending return statement, and return type does not include undefined")] 31 | MissingEndingReturn, 32 | #[error("a return statement is only allowed in a function body")] 33 | UnexpectedReturn, 34 | #[error("type '{type_}' cannot be indexed with '{index}'")] 35 | IndexingType { 36 | type_: Type<'src>, 37 | index: Type<'src>, 38 | }, 39 | #[error("unreachable code")] 40 | Unreachable, 41 | } 42 | 43 | #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] 44 | pub enum Type<'src> { 45 | Guess, 46 | Any, 47 | Never, 48 | Str, 49 | Num, 50 | Bool, 51 | Void, 52 | Value(Value<'src>), 53 | Object(BTreeMap, Box>), 54 | Record((Box, Box)), 55 | FixedArray(Vec), 56 | Array(Box), 57 | Function { 58 | arg_types: Vec<(&'src str, Box)>, 59 | return_type: Box, 60 | }, 61 | Union(BTreeSet>), 62 | } 63 | 64 | impl fmt::Display for Type<'_> { 65 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 66 | match self { 67 | Type::Guess => write!(f, "?"), 68 | Type::Any => write!(f, "any"), 69 | Type::Never => write!(f, "never"), 70 | Type::Str => write!(f, "string"), 71 | Type::Num => write!(f, "number"), 72 | Type::Bool => write!(f, "boolean"), 73 | Type::Void => write!(f, "void"), 74 | Type::Value(v) => write!(f, "{}", v), 75 | Type::Object(o) => { 76 | if o.is_empty() { 77 | return write!(f, "{{}}"); 78 | } 79 | write!(f, "{{ ")?; 80 | for (i, (k, v)) in o.iter().enumerate() { 81 | write!(f, "{}: {}", k, v)?; 82 | if i < o.len() - 1 { 83 | write!(f, "; ")?; 84 | } 85 | } 86 | write!(f, " }}") 87 | } 88 | Type::Record((k, v)) => write!(f, "Record<{}, {}>", k, v), 89 | Type::Array(arr) => write!(f, "{}[]", arr), 90 | Type::FixedArray(types) => { 91 | write!(f, "[")?; 92 | for (i, type_) in types.iter().enumerate() { 93 | write!(f, "{}", type_)?; 94 | if i < types.len() - 1 { 95 | write!(f, ", ")?; 96 | } 97 | } 98 | write!(f, "]") 99 | } 100 | Type::Function { 101 | arg_types, 102 | return_type, 103 | } => { 104 | write!(f, "(")?; 105 | for (i, (arg, arg_type)) in arg_types.iter().enumerate() { 106 | write!(f, "{}: {}", arg, arg_type)?; 107 | if i < arg_types.len() - 1 { 108 | write!(f, ", ")?; 109 | } 110 | } 111 | write!(f, ") => {}", return_type) 112 | } 113 | Type::Union(s) => { 114 | write!(f, "(")?; 115 | for (i, t) in s.iter().enumerate() { 116 | if i < s.len() - 1 { 117 | write!(f, "{} | ", t)?; 118 | } else { 119 | write!(f, "{}", t)?; 120 | } 121 | } 122 | write!(f, ")") 123 | } 124 | } 125 | } 126 | } 127 | 128 | pub fn generalize<'src>(type_: &Type<'src>) -> Type<'src> { 129 | match type_ { 130 | Type::Guess => Type::Any, 131 | Type::Value(Value::Num(_)) => Type::Num, 132 | Type::Value(Value::Bool(_)) => Type::Bool, 133 | Type::Value(Value::Str(_)) => Type::Str, 134 | Type::Object(obj) => Type::Object( 135 | obj.iter() 136 | .map(|(prop, type_)| (prop.clone(), Box::new(generalize(type_)))) 137 | .collect(), 138 | ), 139 | Type::Array(type_) => Type::Array(Box::new(generalize(type_))), 140 | Type::FixedArray(types) => Type::Array(Box::new(generalize( 141 | &types 142 | .iter() 143 | .fold(Type::Never, |acc, type_| join(&acc, type_)), 144 | ))), 145 | type_ => type_.clone(), 146 | } 147 | } 148 | 149 | pub fn join<'src>(t1: &Type<'src>, t2: &Type<'src>) -> Type<'src> { 150 | match (t1, t2) { 151 | (t1, t2) if t1 == t2 => t1.clone(), 152 | (Type::Guess, t) | (t, Type::Guess) => t.clone(), 153 | (Type::Any, _) | (_, Type::Any) => Type::Any, 154 | (Type::Value(Value::Bool(b1)), Type::Value(Value::Bool(b2))) if b1 != b2 => Type::Bool, 155 | (t1, t2 @ Type::Value(_)) if generalize(t2) == *t1 => t1.clone(), 156 | (t1 @ Type::Value(_), t2) if generalize(t1) == *t2 => t2.clone(), 157 | (Type::Never, t) | (t, Type::Never) => t.clone(), 158 | (Type::Union(set1), Type::Union(set2)) => { 159 | let union = set1.union(set2); 160 | union.fold(Type::Never, |acc, type_| join(&acc, type_)) 161 | } 162 | (t @ Type::Union(set), other_t) | (other_t, t @ Type::Union(set)) => { 163 | if set.iter().all(|t| t.as_ref() != &join(t, other_t)) { 164 | let mut set = set.clone(); 165 | set.insert(Box::new(other_t.clone())); 166 | Type::Union(set) 167 | } else { 168 | t.clone() 169 | } 170 | } 171 | (t1, t2) => Type::Union(BTreeSet::from([Box::new(t1.clone()), Box::new(t2.clone())])), 172 | } 173 | } 174 | 175 | pub fn meet<'src>(t1: &Type<'src>, t2: &Type<'src>) -> Type<'src> { 176 | match (t1, t2) { 177 | (t1, t2) if t1 == t2 => t1.clone(), 178 | (Type::Guess, t) | (t, Type::Guess) => t.clone(), 179 | (Type::Any, t) | (t, Type::Any) => t.clone(), 180 | (t1, t2 @ Type::Value(_)) if generalize(t2) == *t1 => t2.clone(), 181 | (t1 @ Type::Value(_), t2) if generalize(t1) == *t2 => t1.clone(), 182 | (Type::Union(set1), Type::Union(set2)) => { 183 | let new_set = set1 184 | .iter() 185 | .flat_map(|t1| set2.iter().map(|t2| meet(t1, t2))); 186 | new_set.fold(Type::Never, |acc, type_| join(&acc, &type_)) 187 | } 188 | (Type::Union(set), other_t) | (other_t, Type::Union(set)) => { 189 | let new_set = set.iter().map(|type_| meet(type_, other_t)); 190 | new_set.fold(Type::Never, |acc, type_| join(&acc, &type_)) 191 | } 192 | (Type::Object(obj1), Type::Object(obj2)) => { 193 | let mut props = obj2.clone(); 194 | for (prop, t1) in obj1.iter() { 195 | match obj2.get(prop) { 196 | Some(t2) => props.insert(prop.clone(), Box::new(meet(t1, t2))), 197 | None => props.insert(prop.clone(), t1.clone()), 198 | }; 199 | } 200 | Type::Object(props) 201 | } 202 | (Type::Object(obj), Type::Record((k, v))) | (Type::Record((k, v)), Type::Object(obj)) => { 203 | let mut props = BTreeMap::new(); 204 | for (prop, type_) in obj.iter() { 205 | match (meet(k, &Type::Value(prop.clone())), meet(v, type_)) { 206 | (Type::Never, _) | (_, Type::Never) => return Type::Never, 207 | (_, v) => { 208 | // Assumes that the result of the meet is always the original type. This is 209 | // currently a safe assumption, but it may not be in the future. 210 | props.insert(prop.clone(), Box::new(v)) 211 | } 212 | }; 213 | } 214 | Type::Object(props) 215 | } 216 | (Type::Record((k1, v1)), Type::Record((k2, v2))) => match (meet(k1, k2), meet(v1, v2)) { 217 | (Type::Never, _) | (_, Type::Never) => Type::Never, 218 | (k, v) => Type::Record((Box::new(k), Box::new(v))), 219 | }, 220 | (Type::Array(t1), Type::Array(t2)) => Type::Array(Box::new(meet(t1, t2))), 221 | (Type::FixedArray(t1), Type::Array(t2)) | (Type::Array(t2), Type::FixedArray(t1)) => { 222 | let mut types = Vec::new(); 223 | for type_ in t1.iter() { 224 | match meet(type_, t2) { 225 | Type::Never => return Type::Never, 226 | type_ => types.push(type_), 227 | }; 228 | } 229 | Type::FixedArray(types) 230 | } 231 | (Type::FixedArray(t1), Type::FixedArray(t2)) => { 232 | if t1.len() != t2.len() { 233 | return Type::Never; 234 | } 235 | let mut types = Vec::new(); 236 | for (t1, t2) in zip(t1.iter(), t2.iter()) { 237 | match meet(t1, t2) { 238 | Type::Never => return Type::Never, 239 | type_ => types.push(type_), 240 | }; 241 | } 242 | Type::FixedArray(types) 243 | } 244 | ( 245 | Type::Function { 246 | arg_types: arg_types1, 247 | return_type: return_type1, 248 | }, 249 | Type::Function { 250 | arg_types: arg_types2, 251 | return_type: return_type2, 252 | }, 253 | ) => { 254 | let arg_types = zip(arg_types1, arg_types2) 255 | .map(|((arg, a), (_, b))| (*arg, Box::new(meet(a, b)))) 256 | .collect(); 257 | let return_type = Box::new(meet(return_type1, return_type2)); 258 | Type::Function { 259 | arg_types, 260 | return_type, 261 | } 262 | } 263 | (_t1, _t2) => Type::Never, 264 | } 265 | } 266 | 267 | pub fn is_subtype<'src>(t1: &Type<'src>, t2: &Type<'src>) -> bool { 268 | match (t1, t2) { 269 | (t1, t2) if t1 == t2 => true, 270 | // never is the lowest type, so it is always a subtype and never a supertype 271 | (Type::Never, _) => true, 272 | (_, Type::Never) => false, 273 | // any can be both a subtype or a supertype 274 | (_, Type::Any) | (Type::Any, _) => true, 275 | // void is always a supertype and never a subtype 276 | (_, Type::Void) => true, 277 | (Type::Void, _) => false, 278 | // nothing is known, so it can be both a subtype or supertype 279 | (Type::Guess, _) | (_, Type::Guess) => true, 280 | (Type::Value(_), t2) if generalize(t1) == *t2 => true, 281 | (Type::Object(obj1), Type::Object(obj2)) => { 282 | // n.b.: Typing objects covariantly makes the type system unsound, 283 | // but this matches TypeScript's behavior. 284 | obj2.iter().all(|(prop, type2)| match obj1.get(prop) { 285 | Some(type1) => is_subtype(type1, type2), 286 | None => false, 287 | }) 288 | } 289 | (Type::Record((k1, v1)), Type::Object(obj)) => obj 290 | .iter() 291 | .all(|(k2, v2)| is_subtype(k1, &Type::Value(k2.clone())) && is_subtype(v1, v2)), 292 | (Type::Object(obj), Type::Record((k2, v2))) => obj 293 | .iter() 294 | .all(|(k1, v1)| is_subtype(&Type::Value(k1.clone()), k2) && is_subtype(v1, v2)), 295 | (Type::Record((k1, v1)), Type::Record((k2, v2))) => { 296 | is_subtype(k1, k2) && is_subtype(v1, v2) 297 | } 298 | (Type::Array(t1), Type::Array(t2)) => is_subtype(t1, t2), 299 | (Type::FixedArray(t1), Type::Array(t2)) => t1.iter().all(|t1| is_subtype(t1, t2)), 300 | (Type::FixedArray(t1), Type::FixedArray(t2)) => { 301 | t1.len() == t2.len() && zip(t1, t2).all(|(t1, t2)| is_subtype(t1, t2)) 302 | } 303 | ( 304 | Type::Function { 305 | arg_types: arg_types1, 306 | return_type: return_type1, 307 | }, 308 | Type::Function { 309 | arg_types: arg_types2, 310 | return_type: return_type2, 311 | }, 312 | ) => { 313 | arg_types1.len() <= arg_types2.len() 314 | && zip(arg_types1, arg_types2) 315 | .all(|((_, type1), (_, type2))| is_subtype(type2, type1)) 316 | && is_subtype(return_type1, return_type2) 317 | } 318 | (Type::Union(s1), t2) => s1.iter().all(|type_| is_subtype(type_, t2)), 319 | (t1, Type::Union(s2)) => s2.iter().any(|type_| is_subtype(t1, type_)), 320 | _ => false, 321 | } 322 | } 323 | 324 | #[derive(Clone, Debug, PartialEq)] 325 | pub enum TypeBinaryOp { 326 | And, 327 | Or, 328 | } 329 | 330 | impl fmt::Display for TypeBinaryOp { 331 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 332 | match self { 333 | TypeBinaryOp::And => write!(f, "&"), 334 | TypeBinaryOp::Or => write!(f, "|"), 335 | } 336 | } 337 | } 338 | 339 | #[derive(Debug, PartialEq)] 340 | pub enum TypeExpr<'src> { 341 | Type(Type<'src>), 342 | TypeOf(TypeSpanned<'src, Expr<'src>>), 343 | Var(&'src str), 344 | Object(HashMap, Box>>), 345 | Record((Box>, Box>)), 346 | Array(Box>), 347 | FixedArray(Vec>), 348 | Function { 349 | arg_types: Vec<(&'src str, Box>)>, 350 | return_type: Box>, 351 | }, 352 | Binary( 353 | Box>, 354 | TypeBinaryOp, 355 | Box>, 356 | ), 357 | } 358 | 359 | impl fmt::Display for TypeExpr<'_> { 360 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 361 | match self { 362 | TypeExpr::Type(t) => write!(f, "{}", t), 363 | TypeExpr::TypeOf(v) => write!(f, "typeof {}", v.0), 364 | TypeExpr::Var(s) => write!(f, "{}", s), 365 | TypeExpr::Object(o) => { 366 | write!(f, "{{ ")?; 367 | for (i, (k, v)) in o.iter().enumerate() { 368 | write!(f, "{}: {}", k, v.0)?; 369 | if i < o.len() - 1 { 370 | write!(f, "; ")?; 371 | } 372 | } 373 | write!(f, " }}") 374 | } 375 | TypeExpr::Record((k, v)) => write!(f, "Record<{}, {}>", k.0, v.0), 376 | TypeExpr::Array(type_expr) => write!(f, "{}[]", type_expr.0), 377 | TypeExpr::FixedArray(types) => { 378 | write!(f, "[")?; 379 | for (i, type_) in types.iter().enumerate() { 380 | write!(f, "{}", type_.0)?; 381 | if i < types.len() - 1 { 382 | write!(f, ", ")?; 383 | } 384 | } 385 | write!(f, "]") 386 | } 387 | TypeExpr::Function { 388 | arg_types, 389 | return_type, 390 | } => { 391 | write!(f, "(")?; 392 | for (i, (arg, arg_type)) in arg_types.iter().enumerate() { 393 | write!(f, "{}: {}", arg, arg_type.0)?; 394 | if i < arg_types.len() - 1 { 395 | write!(f, ", ")?; 396 | } 397 | } 398 | write!(f, ") => {}", return_type.0) 399 | } 400 | TypeExpr::Binary(lhs, op, rhs) => write!(f, "({} {} {})", lhs.0, op, rhs.0), 401 | } 402 | } 403 | } 404 | 405 | #[cfg(test)] 406 | mod tests { 407 | use super::*; 408 | 409 | #[test] 410 | fn test_join_primitive() { 411 | assert_eq!(join(&Type::Bool, &Type::Bool), Type::Bool); 412 | assert_eq!( 413 | join(&Type::Value(Value::Bool(true)), &Type::Bool), 414 | Type::Bool 415 | ); 416 | assert_eq!( 417 | join(&Type::Num, &Type::Bool), 418 | Type::Union(BTreeSet::from([Box::new(Type::Bool), Box::new(Type::Num),])) 419 | ); 420 | } 421 | 422 | #[test] 423 | fn test_join_union() { 424 | let union1 = Type::Union(BTreeSet::from([Box::new(Type::Bool), Box::new(Type::Num)])); 425 | let union2 = Type::Union(BTreeSet::from([Box::new(Type::Bool), Box::new(Type::Str)])); 426 | let union3 = Type::Union(BTreeSet::from([ 427 | Box::new(Type::Bool), 428 | Box::new(Type::Num), 429 | Box::new(Type::Str), 430 | ])); 431 | 432 | assert_eq!(join(&union1, &union2), union3); 433 | assert_eq!(join(&union1, &union3), union3); 434 | assert_eq!(join(&union2, &union3), union3); 435 | } 436 | 437 | #[test] 438 | fn test_meet_primitive() { 439 | assert_eq!(meet(&Type::Bool, &Type::Bool), Type::Bool); 440 | assert_eq!( 441 | meet(&Type::Value(Value::Bool(true)), &Type::Bool), 442 | Type::Value(Value::Bool(true)) 443 | ); 444 | assert_eq!(meet(&Type::Num, &Type::Bool), Type::Never); 445 | } 446 | 447 | #[test] 448 | fn test_meet_union() { 449 | let union1 = Type::Union(BTreeSet::from([Box::new(Type::Bool), Box::new(Type::Num)])); 450 | let union2 = Type::Union(BTreeSet::from([Box::new(Type::Bool), Box::new(Type::Str)])); 451 | let union3 = Type::Union(BTreeSet::from([ 452 | Box::new(Type::Bool), 453 | Box::new(Type::Num), 454 | Box::new(Type::Str), 455 | ])); 456 | 457 | assert_eq!(meet(&union1, &union2), Type::Bool); 458 | assert_eq!(meet(&union1, &union3), union1); 459 | assert_eq!(meet(&union2, &union3), union2); 460 | } 461 | 462 | #[test] 463 | fn test_meet_object() { 464 | let obj1 = Type::Object(BTreeMap::from([( 465 | Value::Str("foo"), 466 | Box::new(Type::Value(Value::Bool(true))), 467 | )])); 468 | let obj2 = Type::Object(BTreeMap::from([(Value::Str("foo"), Box::new(Type::Bool))])); 469 | let record1 = Type::Record(( 470 | Box::new(Type::Str), 471 | Box::new(Type::Value(Value::Bool(true))), 472 | )); 473 | let record2 = Type::Record((Box::new(Type::Str), Box::new(Type::Bool))); 474 | 475 | assert_eq!(meet(&obj1, &obj2), obj1); 476 | assert_eq!(meet(&obj1, &record1), obj1); 477 | assert_eq!(meet(&obj1, &record2), obj1); 478 | 479 | assert_eq!( 480 | meet(&obj2, &record1), 481 | Type::Object(BTreeMap::from([( 482 | Value::Str("foo"), 483 | Box::new(Type::Value(Value::Bool(true))), 484 | )])) 485 | ); 486 | assert_eq!(meet(&obj2, &record2), obj2); 487 | 488 | assert_eq!(meet(&record1, &record2), record1); 489 | } 490 | 491 | #[test] 492 | fn test_meet_array() { 493 | let arr1 = Type::Array(Box::new(Type::Value(Value::Bool(true)))); 494 | let arr2 = Type::Array(Box::new(Type::Bool)); 495 | let arr3 = Type::FixedArray(vec![Type::Value(Value::Bool(true))]); 496 | let arr4 = Type::FixedArray(vec![Type::Bool]); 497 | 498 | assert_eq!(meet(&arr1, &arr2), arr1); 499 | assert_eq!(meet(&arr1, &arr3), arr3); 500 | assert_eq!(meet(&arr1, &arr4), arr3); 501 | 502 | assert_eq!(meet(&arr2, &arr3), arr3); 503 | assert_eq!(meet(&arr2, &arr4), arr4); 504 | 505 | assert_eq!(meet(&arr3, &arr4), arr3); 506 | } 507 | 508 | #[test] 509 | fn test_meet_function() { 510 | let func1 = Type::Function { 511 | arg_types: vec![("x", Box::new(Type::Bool))], 512 | return_type: Box::new(Type::Value(Value::Bool(true))), 513 | }; 514 | let func2 = Type::Function { 515 | arg_types: vec![("x", Box::new(Type::Bool))], 516 | return_type: Box::new(Type::Bool), 517 | }; 518 | let func3 = Type::Function { 519 | arg_types: vec![("x", Box::new(Type::Value(Value::Bool(true))))], 520 | return_type: Box::new(Type::Bool), 521 | }; 522 | let func4 = Type::Function { 523 | arg_types: vec![("x", Box::new(Type::Value(Value::Bool(true))))], 524 | return_type: Box::new(Type::Value(Value::Bool(true))), 525 | }; 526 | 527 | assert_eq!(meet(&func1, &func2), func1); 528 | assert_eq!(meet(&func1, &func3), func4); 529 | assert_eq!(meet(&func1, &func4), func4); 530 | 531 | assert_eq!(meet(&func2, &func3), func3); 532 | assert_eq!(meet(&func2, &func4), func4); 533 | 534 | assert_eq!(meet(&func3, &func4), func4); 535 | } 536 | 537 | #[test] 538 | fn test_is_subtype_primitive() { 539 | assert!(is_subtype(&Type::Bool, &Type::Bool)); 540 | assert!(is_subtype(&Type::Value(Value::Bool(true)), &Type::Bool)); 541 | assert!(!is_subtype(&Type::Num, &Type::Bool)); 542 | } 543 | 544 | #[test] 545 | fn test_is_subtype_object() { 546 | let obj1 = Type::Object(BTreeMap::from([( 547 | Value::Str("foo"), 548 | Box::new(Type::Value(Value::Bool(true))), 549 | )])); 550 | let obj2 = Type::Object(BTreeMap::from([(Value::Str("foo"), Box::new(Type::Bool))])); 551 | let record1 = Type::Record(( 552 | Box::new(Type::Str), 553 | Box::new(Type::Value(Value::Bool(true))), 554 | )); 555 | let record2 = Type::Record((Box::new(Type::Str), Box::new(Type::Bool))); 556 | 557 | assert!(is_subtype(&obj1, &obj2)); 558 | assert!(is_subtype(&obj1, &record1)); 559 | assert!(is_subtype(&obj1, &record2)); 560 | 561 | assert!(!is_subtype(&obj2, &obj1)); 562 | assert!(!is_subtype(&obj2, &record1)); 563 | assert!(is_subtype(&obj2, &record2)); 564 | 565 | assert!(!is_subtype(&record1, &obj1)); 566 | assert!(!is_subtype(&record1, &obj2)); 567 | assert!(is_subtype(&record1, &record2)); 568 | 569 | assert!(!is_subtype(&record2, &obj1)); 570 | assert!(!is_subtype(&record2, &obj2)); 571 | assert!(!is_subtype(&record2, &record1)); 572 | } 573 | 574 | #[test] 575 | fn test_is_subtype_array() { 576 | let arr1 = Type::Array(Box::new(Type::Value(Value::Bool(true)))); 577 | let arr2 = Type::Array(Box::new(Type::Bool)); 578 | let arr3 = Type::FixedArray(vec![Type::Value(Value::Bool(true))]); 579 | let arr4 = Type::FixedArray(vec![Type::Bool]); 580 | 581 | assert!(is_subtype(&arr1, &arr2)); 582 | assert!(!is_subtype(&arr1, &arr3)); 583 | assert!(!is_subtype(&arr1, &arr4)); 584 | 585 | assert!(!is_subtype(&arr2, &arr1)); 586 | assert!(!is_subtype(&arr2, &arr3)); 587 | assert!(!is_subtype(&arr2, &arr4)); 588 | 589 | assert!(is_subtype(&arr3, &arr1)); 590 | assert!(is_subtype(&arr3, &arr2)); 591 | assert!(is_subtype(&arr3, &arr4)); 592 | 593 | assert!(!is_subtype(&arr4, &arr1)); 594 | assert!(is_subtype(&arr4, &arr2)); 595 | assert!(!is_subtype(&arr4, &arr3)); 596 | } 597 | 598 | #[test] 599 | fn test_is_subtype_function() { 600 | let func1 = Type::Function { 601 | arg_types: vec![("x", Box::new(Type::Bool))], 602 | return_type: Box::new(Type::Value(Value::Bool(true))), 603 | }; 604 | let func2 = Type::Function { 605 | arg_types: vec![("x", Box::new(Type::Bool))], 606 | return_type: Box::new(Type::Bool), 607 | }; 608 | let func3 = Type::Function { 609 | arg_types: vec![("x", Box::new(Type::Value(Value::Bool(true))))], 610 | return_type: Box::new(Type::Bool), 611 | }; 612 | let func4 = Type::Function { 613 | arg_types: vec![("x", Box::new(Type::Value(Value::Bool(true))))], 614 | return_type: Box::new(Type::Value(Value::Bool(true))), 615 | }; 616 | 617 | assert!(is_subtype(&func1, &func2)); 618 | assert!(is_subtype(&func1, &func3)); 619 | assert!(is_subtype(&func1, &func4)); 620 | 621 | assert!(!is_subtype(&func2, &func1)); 622 | assert!(is_subtype(&func2, &func3)); 623 | assert!(!is_subtype(&func2, &func4)); 624 | 625 | assert!(!is_subtype(&func3, &func1)); 626 | assert!(!is_subtype(&func3, &func2)); 627 | assert!(!is_subtype(&func3, &func4)); 628 | 629 | assert!(!is_subtype(&func4, &func1)); 630 | assert!(!is_subtype(&func4, &func2)); 631 | assert!(is_subtype(&func4, &func3)); 632 | } 633 | } 634 | -------------------------------------------------------------------------------- /src/typescript/typechecker.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{BTreeMap, BTreeSet, HashMap, VecDeque}; 2 | 3 | use context::{join_contexts, meet_contexts, overlay_contexts, Context}; 4 | use ordered_float::OrderedFloat; 5 | 6 | mod context; 7 | 8 | use super::ast::{ 9 | create_type_spanned, float_to_usize, 10 | r#type::{generalize, is_subtype, join, meet, Type, TypeBinaryOp, TypeError, TypeExpr}, 11 | BinaryOp, ClassMember, Expr, Function, Spanned, Stmt, TypeSpanned, Value, 12 | }; 13 | 14 | /// Evaluate a type expression to a type. 15 | fn eval_type<'src>( 16 | type_expr: &mut TypeSpanned<'src, TypeExpr<'src>>, 17 | context: &Context<'src>, 18 | ) -> Result, Spanned>> { 19 | let type_ = match &mut type_expr.0 { 20 | TypeExpr::Type(type_) => type_.clone(), 21 | TypeExpr::TypeOf(expr) => typecheck_expr(expr, true, context)?, 22 | TypeExpr::Var(var) => match context.types.get(var) { 23 | Some(type_) => type_.clone(), 24 | None => return Err((TypeError::UndefinedType(var.to_string()), type_expr.2)), 25 | }, 26 | TypeExpr::Object(obj) => { 27 | let mut props = BTreeMap::new(); 28 | for (prop, type_) in obj.iter_mut() { 29 | let type_ = eval_type(type_.as_mut(), context)?; 30 | props.insert(prop.clone(), Box::new(type_)); 31 | } 32 | Type::Object(props) 33 | } 34 | TypeExpr::Record((key, val)) => { 35 | let k = eval_type(key, context)?; 36 | let key_constraint = 37 | Type::Union(BTreeSet::from([Box::new(Type::Str), Box::new(Type::Num)])); 38 | if !is_subtype(&k, &key_constraint) { 39 | return Err(( 40 | TypeError::StaticType { 41 | expected: Type::Union(BTreeSet::from([ 42 | Box::new(Type::Str), 43 | Box::new(Type::Num), 44 | ])), 45 | actual: k.clone(), 46 | }, 47 | key.2, 48 | )); 49 | } 50 | let v = eval_type(val, context)?; 51 | Type::Record((Box::new(k), Box::new(v))) 52 | } 53 | TypeExpr::Array(type_expr) => Type::Array(Box::new(eval_type(type_expr, context)?)), 54 | TypeExpr::FixedArray(types) => { 55 | let mut new_types = Vec::new(); 56 | for type_ in types.iter_mut() { 57 | let type_ = eval_type(type_, context)?; 58 | new_types.push(type_); 59 | } 60 | Type::FixedArray(new_types) 61 | } 62 | TypeExpr::Function { 63 | arg_types, 64 | return_type, 65 | } => { 66 | let mut args = Vec::new(); 67 | for (arg, type_expr) in arg_types.iter_mut() { 68 | let type_ = eval_type(type_expr, context)?; 69 | args.push((*arg, Box::new(type_))); 70 | } 71 | Type::Function { 72 | arg_types: args, 73 | return_type: Box::new(eval_type(return_type, context)?), 74 | } 75 | } 76 | TypeExpr::Binary(t1, op, t2) => { 77 | let t1 = &eval_type(t1.as_mut(), context)?; 78 | let t2 = &eval_type(t2.as_mut(), context)?; 79 | match op { 80 | TypeBinaryOp::Or => join(t1, t2), 81 | TypeBinaryOp::And => meet(t1, t2), 82 | } 83 | } 84 | }; 85 | type_expr.1 = type_.clone(); 86 | Ok(type_) 87 | } 88 | 89 | /// Evaluates the type expressions of a function's arguments and return type. 90 | fn eval_function_types<'src>( 91 | func: &mut Function<'src>, 92 | context: &Context<'src>, 93 | ) -> Result<(), Spanned>> { 94 | for (_, type_) in func.args.iter_mut() { 95 | eval_type(type_, context)?; 96 | } 97 | if let Some(return_type) = &mut func.return_type { 98 | eval_type(return_type, context)?; 99 | } 100 | Ok(()) 101 | } 102 | 103 | /// Check that `expr`'s type is a subtype of `expected`. 104 | fn check_subtype<'src>( 105 | expected: &Type<'src>, 106 | expr: &mut TypeSpanned<'src, Expr<'src>>, 107 | as_const: bool, 108 | context: &Context<'src>, 109 | ) -> Result, Spanned>> { 110 | let actual_as_const = typecheck_expr(expr, true, context)?; 111 | if !is_subtype(&actual_as_const, expected) { 112 | return Err(( 113 | TypeError::StaticType { 114 | expected: expected.clone(), 115 | actual: actual_as_const, 116 | }, 117 | expr.2, 118 | )); 119 | } 120 | if as_const { 121 | Ok(actual_as_const) 122 | } else { 123 | typecheck_expr(expr, false, context) 124 | } 125 | } 126 | 127 | /// Narrow the types in `context` based on whether `expr` is true or false, e.g. `x === 3` narrows 128 | /// x to the type `3` in the then branch. 129 | fn narrowed_context<'src>( 130 | expr: &TypeSpanned<'src, Expr<'src>>, 131 | context: Context<'src>, 132 | is_expr_true: bool, 133 | ) -> Context<'src> { 134 | fn attempt_narrow_type<'src>( 135 | type_: &Type<'src>, 136 | constraint_type: &Type<'src>, 137 | is_equality: bool, 138 | ) -> Option> { 139 | if is_equality { 140 | match meet(type_, constraint_type) { 141 | Type::Never => None, 142 | type_ => Some(type_), 143 | } 144 | } else { 145 | match meet(type_, constraint_type) { 146 | Type::Never => Some(type_.clone()), 147 | _type if generalize(constraint_type) == *type_ => Some(type_.clone()), 148 | _type => None, 149 | } 150 | } 151 | } 152 | 153 | fn get_object_var_path<'src>( 154 | expr: &TypeSpanned<'src, Expr<'src>>, 155 | ) -> Option<(&'src str, VecDeque>, Type<'src>)> { 156 | match &expr.0 { 157 | Expr::DotProperty(obj, prop) => { 158 | get_object_var_path(obj).map(|(var, mut path, type_)| { 159 | path.push_back(Type::Value(Value::Str(prop.0))); 160 | (var, path, type_) 161 | }) 162 | } 163 | Expr::BracketProperty(obj, prop) => { 164 | get_object_var_path(obj).map(|(var, mut path, type_)| { 165 | path.push_back(prop.1.clone()); 166 | (var, path, type_) 167 | }) 168 | } 169 | Expr::Var(var) => Some((*var, VecDeque::new(), expr.1.clone())), 170 | _ => None, 171 | } 172 | } 173 | 174 | fn attempt_narrow_object<'src>( 175 | type_: &Type<'src>, 176 | mut path: VecDeque>, 177 | constraint: &Type<'src>, 178 | is_equality: bool, 179 | ) -> Option> { 180 | // upon reaching the leaf type, attempt to narrow it 181 | if path.is_empty() { 182 | return attempt_narrow_type(type_, constraint, is_equality); 183 | } 184 | match type_ { 185 | Type::Union(types) => types 186 | .iter() 187 | .filter_map(|type_| { 188 | attempt_narrow_object(type_, path.clone(), constraint, is_equality) 189 | }) 190 | .reduce(|acc, type_| join(&acc, &type_)), 191 | Type::Object(obj) => path.pop_front().as_ref().and_then(|prop| match prop { 192 | Type::Value(prop) => obj.get(prop).and_then(|type_| { 193 | let prop_type = attempt_narrow_object(type_, path, constraint, is_equality)?; 194 | let mut new_obj = obj.clone(); 195 | new_obj.insert(prop.clone(), Box::new(prop_type)); 196 | Some(Type::Object(new_obj)) 197 | }), 198 | Type::Union(types) => types 199 | .iter() 200 | .filter_map(|type_| { 201 | attempt_narrow_object(type_, path.clone(), constraint, is_equality) 202 | }) 203 | .reduce(|acc, type_| join(&acc, &type_)), 204 | _ => { 205 | // the object cannot be narrowed, but it is still a valid type 206 | Some(type_.clone()) 207 | } 208 | }), 209 | Type::Record((k, v)) => { 210 | // records have a single key type, meaning the key must equal front of the path, so 211 | // it can be ignored 212 | path.pop_front(); 213 | let new_v = attempt_narrow_object(v, path, constraint, is_equality)?; 214 | Some(Type::Record((k.clone(), Box::new(new_v)))) 215 | } 216 | _ => unreachable!(), 217 | } 218 | } 219 | 220 | fn narrow_strict_comparison<'src>( 221 | lhs: &TypeSpanned<'src, Expr<'src>>, 222 | rhs: &TypeSpanned<'src, Expr<'src>>, 223 | expr_true: bool, 224 | ) -> Context<'src> { 225 | let mut context = Context::default(); 226 | match &lhs.0 { 227 | Expr::Var(var) => { 228 | let type_ = attempt_narrow_type(&lhs.1, &rhs.1, expr_true).unwrap_or(Type::Never); 229 | context.variables.insert(var, type_); 230 | } 231 | Expr::DotProperty(_, _) | Expr::BracketProperty(_, _) => { 232 | if let Some((var, path, object_type)) = get_object_var_path(lhs) { 233 | let type_ = attempt_narrow_object(&object_type, path, &rhs.1, expr_true) 234 | .unwrap_or(Type::Never); 235 | context.variables.insert(var, type_); 236 | } 237 | } 238 | _ => {} 239 | } 240 | context 241 | } 242 | 243 | fn narrowed_types<'src>( 244 | expr: &TypeSpanned<'src, Expr<'src>>, 245 | expr_true: bool, 246 | ) -> Context<'src> { 247 | match &expr.0 { 248 | Expr::Binary(lhs, op, rhs) => { 249 | match op { 250 | BinaryOp::StrictEquality | BinaryOp::StrictInequality => { 251 | // flip expr_true for strict inequalities 252 | let expr_true = if matches!(op, BinaryOp::StrictInequality) { 253 | !expr_true 254 | } else { 255 | expr_true 256 | }; 257 | narrow_strict_comparison(lhs, rhs, expr_true) 258 | } 259 | BinaryOp::And => { 260 | let lhs_context = narrowed_types(lhs, expr_true); 261 | let rhs_context = narrowed_types(rhs, expr_true); 262 | if expr_true { 263 | meet_contexts(&lhs_context, &rhs_context) 264 | } else { 265 | join_contexts(&lhs_context, &rhs_context) 266 | } 267 | } 268 | BinaryOp::Or => { 269 | let lhs_context = narrowed_types(lhs, expr_true); 270 | let rhs_context = narrowed_types(rhs, expr_true); 271 | if expr_true { 272 | join_contexts(&lhs_context, &rhs_context) 273 | } else { 274 | meet_contexts(&lhs_context, &rhs_context) 275 | } 276 | } 277 | BinaryOp::Add | BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div => { 278 | Context::default() 279 | } 280 | } 281 | } 282 | _ => Context::default(), 283 | } 284 | } 285 | 286 | overlay_contexts(&context, &narrowed_types(expr, is_expr_true)) 287 | } 288 | 289 | /// Typecheck an expression. 290 | pub fn typecheck_expr<'src>( 291 | expr: &mut TypeSpanned<'src, Expr<'src>>, 292 | as_const: bool, 293 | context: &Context<'src>, 294 | ) -> Result, Spanned>> { 295 | let type_ = match &mut expr.0 { 296 | Expr::Value(v) => { 297 | if as_const { 298 | Type::Value(v.clone()) 299 | } else { 300 | generalize(&Type::Value(v.clone())) 301 | } 302 | } 303 | Expr::Object(obj) => { 304 | let mut props = BTreeMap::new(); 305 | for (prop, expr) in obj.iter_mut() { 306 | let prop_type = typecheck_expr(expr, as_const, context)?; 307 | props.insert(prop.clone(), Box::new(prop_type)); 308 | } 309 | Type::Object(props) 310 | } 311 | Expr::DotProperty(obj, prop) => { 312 | typecheck_expr(obj, as_const, context)?; 313 | get_dot_prop_type(obj, prop)? 314 | } 315 | Expr::BracketProperty(obj, prop) => { 316 | typecheck_expr(obj, as_const, context)?; 317 | typecheck_expr(prop, true, context)?; 318 | get_bracket_prop_type(obj, prop)? 319 | } 320 | Expr::ArrowFunction(func) => { 321 | eval_function_types(func, context)?; 322 | let mut body_context = context.clone(); 323 | for (arg, type_) in func.args.iter_mut() { 324 | body_context.variables.insert(arg, type_.1.clone()); 325 | } 326 | typecheck_function_body(func, &Type::Value(Value::Undefined), &mut body_context)? 327 | } 328 | Expr::Array(exprs) => { 329 | let mut types = Vec::new(); 330 | for expr in exprs.iter_mut() { 331 | typecheck_expr(expr, as_const, context)?; 332 | types.push(expr.1.clone()); 333 | } 334 | Type::FixedArray(types) 335 | } 336 | Expr::Var(name) => match context.variables.get(name) { 337 | Some(t) => t.clone(), 338 | None => return Err((TypeError::UndefinedVariable(name.to_string()), expr.2)), 339 | }, 340 | Expr::Binary(lhs, BinaryOp::Add | BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div, rhs) => { 341 | let lhs_type = check_subtype(&Type::Num, lhs, as_const, context)?; 342 | let rhs_type = check_subtype(&Type::Num, rhs, as_const, context)?; 343 | match (lhs_type, rhs_type) { 344 | (Type::Any, _) | (_, Type::Any) => Type::Any, 345 | _ => Type::Num, 346 | } 347 | } 348 | // n.b. the semantics of the And and Or operators here differ from those of JavaScript 349 | Expr::Binary(lhs, BinaryOp::And, rhs) => { 350 | let lhs_type = check_subtype(&Type::Bool, lhs, as_const, context)?; 351 | let rhs_context = narrowed_context(lhs, context.clone(), true); 352 | let rhs_type = check_subtype(&Type::Bool, rhs, as_const, &rhs_context)?; 353 | match lhs_type { 354 | Type::Value(Value::Bool(true)) => rhs_type, 355 | Type::Value(Value::Bool(false)) => lhs_type, 356 | _ => join(&lhs_type, &rhs_type), 357 | } 358 | } 359 | Expr::Binary(lhs, BinaryOp::Or, rhs) => { 360 | let lhs_type = check_subtype(&Type::Bool, lhs, as_const, context)?; 361 | let rhs_type = check_subtype(&Type::Bool, rhs, as_const, context)?; 362 | match lhs_type { 363 | Type::Value(Value::Bool(true)) => lhs_type, 364 | Type::Value(Value::Bool(false)) => rhs_type, 365 | _ => join(&lhs_type, &rhs_type), 366 | } 367 | } 368 | Expr::Binary(lhs, BinaryOp::StrictEquality | BinaryOp::StrictInequality, rhs) => { 369 | let lhs_type = typecheck_expr(lhs, as_const, context)?; 370 | check_subtype(&lhs_type, rhs, as_const, context)?; 371 | Type::Bool 372 | } 373 | Expr::Assign(lhs, rhs) => { 374 | let lhs_type = typecheck_expr(lhs, as_const, context)?; 375 | match &lhs.0 { 376 | Expr::Var(_) | Expr::DotProperty(_, _) | Expr::BracketProperty(_, _) => { 377 | check_subtype(&lhs_type, rhs, as_const, context)? 378 | } 379 | _ => return Err((TypeError::CannotAssign(rhs.1.clone()), expr.2)), 380 | } 381 | } 382 | Expr::FunctionCall(func, args) => { 383 | typecheck_expr(func, as_const, context)?; 384 | get_function_call_type(func, args, context)? 385 | } 386 | Expr::If(cond, then, else_) => { 387 | check_subtype(&Type::Bool, cond, as_const, context)?; 388 | let then_const_type = typecheck_expr(then, true, context)?; 389 | let else_const_type = typecheck_expr(else_, true, context)?; 390 | let then_type = typecheck_expr(then, as_const, context)?; 391 | let else_type = typecheck_expr(else_, as_const, context)?; 392 | if then_type == else_const_type { 393 | else_const_type 394 | } else if then_const_type == else_type { 395 | then_const_type 396 | } else { 397 | join(&then_type, &else_type) 398 | } 399 | } 400 | }; 401 | 402 | // if the type is a guess, nothing is known about it, so it is any 403 | let type_ = matches!(type_, Type::Guess) 404 | .then(|| Type::Any) 405 | .unwrap_or(type_); 406 | 407 | expr.1 = type_.clone(); 408 | Ok(type_) 409 | } 410 | 411 | /// Process a type with a function that returns a new type. If the type is a union, the function is 412 | /// applied to each type in the union and the union of the results is returned. 413 | fn process_union<'src, F>( 414 | type_: &Type<'src>, 415 | mut f: F, 416 | ) -> Result, Spanned>> 417 | where 418 | F: FnMut(&Type<'src>) -> Result, Spanned>>, 419 | { 420 | match type_ { 421 | Type::Union(types) => { 422 | let mut new_types = BTreeSet::new(); 423 | for t in types.iter() { 424 | let new_t = f(t)?; 425 | new_types.insert(Box::new(new_t)); 426 | } 427 | let joined = new_types 428 | .iter() 429 | .fold(Type::Never, |acc, type_| join(&acc, type_)); 430 | Ok(joined) 431 | } 432 | _ => f(type_), 433 | } 434 | } 435 | 436 | /// Get the type of a property of an object accessed with dot notation. 437 | fn get_dot_prop_type<'src>( 438 | type_: &TypeSpanned<'src, Expr<'src>>, 439 | prop: &Spanned<&'src str>, 440 | ) -> Result, Spanned>> { 441 | process_union(&type_.1, |type_| match type_ { 442 | Type::Any => Ok(Type::Any), 443 | Type::Object(props) => props 444 | .get(&Value::Str(prop.0)) 445 | .map(|prop| prop.as_ref().clone()) 446 | .ok_or_else(|| { 447 | ( 448 | TypeError::UndefinedProperty(type_.clone(), prop.0.to_string()), 449 | prop.1, 450 | ) 451 | }), 452 | Type::Record((k, v)) => { 453 | if is_subtype(&Type::Value(Value::Str(prop.0)), k) { 454 | Ok(v.as_ref().clone()) 455 | } else { 456 | Err(( 457 | TypeError::UndefinedProperty(type_.clone(), prop.0.to_string()), 458 | prop.1, 459 | )) 460 | } 461 | } 462 | Type::Array(_) | Type::FixedArray(_) => match prop.0 { 463 | "length" => match type_ { 464 | Type::FixedArray(arr) => Ok(Type::Value(Value::Num(OrderedFloat::from( 465 | arr.len() as f64 466 | )))), 467 | _ => Ok(Type::Num), 468 | }, 469 | "forEach" | "map" => { 470 | let callback_type = Type::Function { 471 | arg_types: vec![ 472 | // TODO: element should be generic 473 | ("element", Box::new(Type::Any)), 474 | ("index", Box::new(Type::Num)), 475 | ], 476 | return_type: Box::new(Type::Void), 477 | }; 478 | Ok(Type::Function { 479 | arg_types: vec![("callback", Box::new(callback_type))], 480 | return_type: Box::new(Type::Void), 481 | }) 482 | } 483 | _ => Err(( 484 | TypeError::UndefinedProperty(type_.clone(), prop.0.to_string()), 485 | prop.1, 486 | )), 487 | }, 488 | _ => Err(( 489 | TypeError::UndefinedProperty(type_.clone(), prop.0.to_string()), 490 | prop.1, 491 | )), 492 | }) 493 | } 494 | 495 | /// Get the type of a property of an object accessed with bracket notation. 496 | fn get_bracket_prop_type<'src>( 497 | type_: &TypeSpanned<'src, Expr<'src>>, 498 | prop: &TypeSpanned<'src, Expr<'src>>, 499 | ) -> Result, Spanned>> { 500 | process_union(&type_.1, |type_| match type_ { 501 | Type::Any => Ok(Type::Any), 502 | Type::Object(props) => process_union(&prop.1, |p| match p { 503 | Type::Value(val) => Ok(props 504 | .get(val) 505 | .map(|prop| prop.as_ref().clone()) 506 | .unwrap_or_else(|| Type::Any)), 507 | Type::Any | Type::Bool | Type::Num | Type::Str => { 508 | // these types are allowed as index types 509 | Ok(Type::Any) 510 | } 511 | _ => Err(( 512 | TypeError::IndexingType { 513 | type_: type_.clone(), 514 | index: p.clone(), 515 | }, 516 | prop.2, 517 | )), 518 | }), 519 | Type::Record((k, v)) => process_union(&prop.1, |p| { 520 | if is_subtype(p, k) { 521 | Ok(v.as_ref().clone()) 522 | } else { 523 | Ok(Type::Any) 524 | } 525 | }), 526 | Type::Array(arr) => process_union(&prop.1, |p| match p { 527 | Type::Any | Type::Value(Value::Num(_)) | Type::Num => Ok(arr.as_ref().clone()), 528 | _ => Err(( 529 | TypeError::IndexingType { 530 | type_: type_.clone(), 531 | index: prop.1.clone(), 532 | }, 533 | prop.2, 534 | )), 535 | }), 536 | Type::FixedArray(arr) => process_union(&prop.1, |p| match p { 537 | Type::Value(Value::Num(n)) => { 538 | float_to_usize(n).and_then(|n| arr.get(n).cloned()).ok_or(( 539 | TypeError::IndexingType { 540 | type_: type_.clone(), 541 | index: prop.1.clone(), 542 | }, 543 | prop.2, 544 | )) 545 | } 546 | Type::Any | Type::Num => { 547 | let joined = arr.iter().fold(Type::Never, |acc, type_| join(&acc, type_)); 548 | Ok(joined) 549 | } 550 | _ => Err(( 551 | TypeError::IndexingType { 552 | type_: type_.clone(), 553 | index: prop.1.clone(), 554 | }, 555 | prop.2, 556 | )), 557 | }), 558 | _ => Err(( 559 | TypeError::IndexingType { 560 | type_: type_.clone(), 561 | index: prop.1.clone(), 562 | }, 563 | prop.2, 564 | )), 565 | }) 566 | } 567 | 568 | /// Get the type of a function call. 569 | fn get_function_call_type<'src>( 570 | type_: &TypeSpanned<'src, Expr<'src>>, 571 | args: &mut [TypeSpanned<'src, Expr<'src>>], 572 | context: &Context<'src>, 573 | ) -> Result, Spanned>> { 574 | process_union(&type_.1, |func_type| match func_type { 575 | Type::Any => Ok(Type::Any), 576 | Type::Function { 577 | arg_types, 578 | return_type, 579 | } => { 580 | if args.len() != arg_types.len() { 581 | return Err(( 582 | TypeError::UnexpectedArguments { 583 | expected: arg_types.len(), 584 | actual: args.len(), 585 | }, 586 | if args.len() < arg_types.len() { 587 | type_.2 588 | } else { 589 | args.get(arg_types.len()).unwrap().2 590 | }, 591 | )); 592 | } 593 | for (arg, (_, arg_type)) in args.iter_mut().zip(arg_types.iter()) { 594 | check_subtype(arg_type, arg, true, context)?; 595 | } 596 | Ok(return_type.as_ref().clone()) 597 | } 598 | func_type => Err((TypeError::CannotCall(func_type.clone()), type_.2)), 599 | }) 600 | } 601 | 602 | /// Typecheck a statement. Returns a list of return types and a boolean indicating whether the 603 | /// statement returns in all branches. 604 | /// TODO: Top-level definitions are not global. 605 | fn typecheck_stmt<'src>( 606 | stmt: &mut Spanned>, 607 | expected_return_type: Option<&Type<'src>>, 608 | context: &mut Context<'src>, 609 | ) -> Result<(Vec>, bool), Spanned>> { 610 | match &mut stmt.0 { 611 | Stmt::Expr(expr) => { 612 | typecheck_expr(expr, false, context)?; 613 | Ok((Vec::new(), false)) 614 | } 615 | Stmt::Decl { 616 | name, 617 | type_expr, 618 | expr, 619 | body, 620 | } => { 621 | let as_const = false; 622 | let decl_type = match type_expr { 623 | Some(type_expr) => { 624 | let type_ = eval_type(type_expr, context)?; 625 | check_subtype(&type_, expr, as_const, context)?; 626 | type_ 627 | } 628 | None => { 629 | let expr_type = typecheck_expr(expr, as_const, context)?; 630 | type_expr.replace(create_type_spanned( 631 | TypeExpr::Type(expr_type.clone()), 632 | expr_type.clone(), 633 | )); 634 | expr_type 635 | } 636 | }; 637 | context.variables.insert(name, decl_type); 638 | typecheck_stmt(body.as_mut(), expected_return_type, context) 639 | } 640 | Stmt::TypeDecl { 641 | name, 642 | type_expr, 643 | body, 644 | } => { 645 | let type_ = eval_type(type_expr, context)?; 646 | context.types.insert(name, type_); 647 | typecheck_stmt(body.as_mut(), expected_return_type, context) 648 | } 649 | Stmt::Function(name, func) => { 650 | eval_function_types(func, context)?; 651 | let function_type = first_pass_function_type(func)?; 652 | let mut body_context = context.clone(); 653 | for (arg, type_) in func.args.iter_mut() { 654 | body_context.variables.insert(arg, type_.1.clone()); 655 | } 656 | body_context.variables.insert(name, function_type); 657 | let function_type = 658 | typecheck_function_body(func, &Type::Value(Value::Undefined), &mut body_context)?; 659 | context.variables.insert(name, function_type); 660 | Ok((Vec::new(), false)) 661 | } 662 | Stmt::If(cond, then, else_) => { 663 | check_subtype(&Type::Bool, cond, true, context)?; 664 | let mut then_context = narrowed_context(cond, context.clone(), true); 665 | let (mut then_types, then_returns) = 666 | typecheck_stmt(then, expected_return_type, &mut then_context)?; 667 | let (mut else_types, else_returns) = if let Some(else_) = else_ { 668 | let mut else_context = narrowed_context(cond, context.clone(), false); 669 | typecheck_stmt(else_, expected_return_type, &mut else_context)? 670 | } else { 671 | (Vec::new(), false) 672 | }; 673 | then_types.append(&mut else_types); 674 | Ok((then_types, then_returns && else_returns)) 675 | } 676 | Stmt::Seq(lhs, rhs) => { 677 | let (mut lhs_types, lhs_returns) = typecheck_stmt(lhs, expected_return_type, context)?; 678 | if lhs_returns { 679 | return Err((TypeError::Unreachable, lhs.1)); 680 | } 681 | let (mut rhs_types, rhs_returns) = typecheck_stmt(rhs, expected_return_type, context)?; 682 | lhs_types.append(&mut rhs_types); 683 | Ok((lhs_types, rhs_returns)) 684 | } 685 | Stmt::Return(expr) => match expected_return_type { 686 | Some(expected_return_type) => { 687 | let return_type = check_subtype(expected_return_type, expr, false, context)?; 688 | Ok((vec![return_type], true)) 689 | } 690 | None => Err((TypeError::UnexpectedReturn, stmt.1)), 691 | }, 692 | Stmt::Class { 693 | name, 694 | constructor, 695 | members, 696 | } => { 697 | let class_type = first_pass_class_type(members, context)?; 698 | let mut class_context = context.clone(); 699 | class_context.types.insert(name, class_type.clone()); 700 | class_context.variables.insert("this", class_type); 701 | let class_type = typecheck_class(name, constructor, members, &class_context)?; 702 | context.types.insert(name, class_type); 703 | Ok((Vec::new(), false)) 704 | } 705 | Stmt::Noop => Ok((Vec::new(), false)), 706 | } 707 | } 708 | 709 | /// Returns a first-pass type for a function. This type is used to typecheck the function body. 710 | fn first_pass_function_type<'src>( 711 | func: &mut Function<'src>, 712 | ) -> Result, Spanned>> { 713 | let mut arg_types = Vec::new(); 714 | for (arg, type_) in func.args.iter_mut() { 715 | arg_types.push((*arg, Box::new(type_.1.clone()))); 716 | } 717 | let return_type = match &func.return_type { 718 | Some(return_type) => Box::new(return_type.1.clone()), 719 | None => Box::new(Type::Guess), 720 | }; 721 | Ok(Type::Function { 722 | arg_types, 723 | return_type, 724 | }) 725 | } 726 | 727 | /// Typechecks a function's body. 728 | fn typecheck_function_body<'src>( 729 | func: &mut Function<'src>, 730 | implicit_return_type: &Type<'src>, 731 | body_context: &mut Context<'src>, 732 | ) -> Result, Spanned>> { 733 | let expected_return_type = func 734 | .return_type 735 | .as_ref() 736 | .map(|return_type| &return_type.1) 737 | .unwrap_or(&Type::Any); 738 | let (return_types, does_return) = 739 | typecheck_stmt(&mut func.body, Some(expected_return_type), body_context)?; 740 | let return_type = match func.return_type.as_ref() { 741 | Some(return_type_expr) => { 742 | let annotated_return_type = &return_type_expr.1; 743 | // if the body does not return in all branches, the function may return the implicit 744 | // return type. we need to check that the implicit return type is a subtype of the 745 | // annotated return type. 746 | if !does_return && !is_subtype(implicit_return_type, annotated_return_type) { 747 | return Err((TypeError::MissingEndingReturn, return_type_expr.2)); 748 | } 749 | annotated_return_type.clone() 750 | } 751 | None => { 752 | let return_type = if !does_return && return_types.is_empty() { 753 | Type::Void 754 | } else { 755 | let mut joined_type = return_types 756 | .into_iter() 757 | .fold(Type::Never, |acc, type_| join(&acc, &type_)); 758 | if !does_return { 759 | joined_type = join(&joined_type, implicit_return_type); 760 | } 761 | joined_type 762 | }; 763 | func.return_type.replace(Box::new(create_type_spanned( 764 | TypeExpr::Type(return_type.clone()), 765 | return_type.clone(), 766 | ))); 767 | return_type 768 | } 769 | }; 770 | Ok(Type::Function { 771 | arg_types: func 772 | .args 773 | .iter() 774 | .map(|(arg, (_, arg_type, _))| (*arg, Box::new(arg_type.clone()))) 775 | .collect(), 776 | return_type: Box::new(return_type), 777 | }) 778 | } 779 | 780 | /// Returns a first-pass type for a class. This type is used to typecheck the class members. 781 | fn first_pass_class_type<'src>( 782 | members: &mut HashMap<&'src str, ClassMember<'src>>, 783 | context: &Context<'src>, 784 | ) -> Result, Spanned>> { 785 | let mut props = BTreeMap::new(); 786 | for (member_name, member) in members.iter_mut() { 787 | match member { 788 | ClassMember::Field { type_expr, .. } => { 789 | let type_ = match type_expr { 790 | Some(type_) => eval_type(type_, context)?, 791 | None => Type::Guess, 792 | }; 793 | props.insert(Value::Str(member_name), Box::new(type_)); 794 | } 795 | ClassMember::Method(func) => { 796 | eval_function_types(func, context)?; 797 | let function_type = first_pass_function_type(func)?; 798 | props.insert(Value::Str(member_name), Box::new(function_type)); 799 | } 800 | } 801 | } 802 | Ok(Type::Object(props)) 803 | } 804 | 805 | /// Typechecks a class's members. 806 | fn typecheck_class<'src>( 807 | name: &'src str, 808 | constructor: &mut Function<'src>, 809 | members: &mut HashMap<&'src str, ClassMember<'src>>, 810 | context: &Context<'src>, 811 | ) -> Result, Spanned>> { 812 | let mut props = BTreeMap::new(); 813 | for (member_name, member) in members.iter_mut() { 814 | match member { 815 | ClassMember::Field { type_expr, expr } => { 816 | let field_type = match type_expr { 817 | Some(type_expr) => { 818 | let type_ = eval_type(type_expr, context)?; 819 | check_subtype(&type_, expr, false, context)?; 820 | type_ 821 | } 822 | None => { 823 | let expr_type = typecheck_expr(expr, false, context)?; 824 | type_expr.replace(create_type_spanned( 825 | TypeExpr::Type(expr_type.clone()), 826 | expr_type.clone(), 827 | )); 828 | expr_type 829 | } 830 | }; 831 | props.insert(Value::Str(member_name), Box::new(field_type)); 832 | } 833 | ClassMember::Method(func) => { 834 | let mut body_context = context.clone(); 835 | for (arg, type_) in func.args.iter_mut() { 836 | let type_ = eval_type(type_, context)?; 837 | body_context.variables.insert(arg, type_); 838 | } 839 | let function_type = typecheck_function_body( 840 | func, 841 | &Type::Value(Value::Undefined), 842 | &mut body_context, 843 | )?; 844 | props.insert(Value::Str(member_name), Box::new(function_type)); 845 | } 846 | } 847 | } 848 | let type_ = Type::Object(props); 849 | 850 | let mut constructor_context = context.clone(); 851 | constructor_context.types.insert(name, type_.clone()); 852 | constructor_context.variables.insert("this", type_.clone()); 853 | constructor 854 | .return_type 855 | .replace(Box::new(create_type_spanned( 856 | TypeExpr::Var(name), 857 | type_.clone(), 858 | ))); 859 | 860 | for (arg, type_) in constructor.args.iter_mut() { 861 | eval_type(type_, context)?; 862 | constructor_context.variables.insert(arg, type_.1.clone()); 863 | } 864 | typecheck_function_body(constructor, &type_, &mut constructor_context)?; 865 | 866 | Ok(type_) 867 | } 868 | 869 | /// Typecheck a program. 870 | pub fn typecheck<'src>(program: &mut Spanned>) -> Result<(), Spanned>> { 871 | let mut context = Context::default(); 872 | typecheck_stmt(program, None, &mut context)?; 873 | Ok(()) 874 | } 875 | --------------------------------------------------------------------------------