# HG changeset patch # User Simon Heath # Date 1670275807 18000 # Mon Dec 05 16:30:07 2022 -0500 # Node ID 9eb70576c7ab08eba4b8df8e1a6514d628328aa6 # Parent 96850b92e1563bd4f708e5abb610d321beafa895 omg I can typecheck generics let's gooooooooooo diff --git a/fml/src/ast.rs b/fml/src/ast.rs --- a/fml/src/ast.rs +++ b/fml/src/ast.rs @@ -97,6 +97,7 @@ }, TypeCtor { name: String, + type_params: Vec, body: ExprNode, }, } diff --git a/fml/src/lib.rs b/fml/src/lib.rs --- a/fml/src/lib.rs +++ b/fml/src/lib.rs @@ -17,7 +17,7 @@ } impl Type { - fn get_primitive_type(s: &str) -> Option { + fn _get_primitive_type(s: &str) -> Option { match s { "I32" => Some(Type::Named("I32".to_string(), vec![])), "Bool" => Some(Type::Named("Bool".to_string(), vec![])), @@ -26,8 +26,14 @@ } } - fn get_generic_names(&self) -> HashSet { - fn helper(t: &Type, accm: &mut HashSet) { + /// TODO: + /// The ordering here is a little crazy 'cause we have to + /// match names with arg positions, so if we have type T(@Foo, @Bar) + /// and we instantiate it later with $T(I32, F(Bool)) then it has + /// to be semi sane. + /// ...or something. Think about this more. + fn get_generic_names(&self) -> Vec { + fn helper(t: &Type, accm: &mut Vec) { match t { Type::Named(_, ts) => { for t in ts { @@ -46,11 +52,11 @@ } } Type::Generic(s) => { - accm.insert(s.clone()); + accm.push(s.clone()); } } } - let mut accm = HashSet::new(); + let mut accm = vec![]; helper(self, &mut accm); accm } diff --git a/fml/src/parser.rs b/fml/src/parser.rs --- a/fml/src/parser.rs +++ b/fml/src/parser.rs @@ -375,7 +375,18 @@ /// typedef = "type" ident "=" type fn parse_typedef(&mut self) -> ast::Decl { let name = self.expect_ident(); - let params = vec![]; + let mut params = vec![]; + if self.peek_expect(T::LParen.discr()) { + while !self.peek_is(T::RParen.discr()) { + self.expect(T::At); + let name = self.expect_ident(); + params.push(name); + if !self.peek_expect(T::Comma.discr()) { + break; + } + } + self.expect(T::RParen); + } self.expect(T::Equals); let ty = self.parse_type(); @@ -428,13 +439,14 @@ fn parse_fn_type(&mut self) -> Type { // TODO: Parse generic stuffs? - let params = self.parse_fn_type_args(); + let params = self.parse_type_list(); self.expect(T::Colon); let rettype = self.parse_type(); Type::Func(params, Box::new(rettype)) } - fn parse_fn_type_args(&mut self) -> Vec { + /// type_list = "(" [type {"," type} [","] ")" + fn parse_type_list(&mut self) -> Vec { let mut args = vec![]; self.expect(T::LParen); @@ -523,8 +535,21 @@ // $Foo(Type) expr // or we could have // $Foo (expr) + // So for now we just force it to be the first one + // But this is Actually Really Bad because it means that we can + // NOT follow a type constructor with a parenthesized expression, + // such as `$Foo (1 + 2) * 3` + let type_params = if self.peek_is(T::LParen.discr()) { + self.parse_type_list() + } else { + vec![] + }; let body = self.parse_expr(0)?; - ast::ExprNode::new(ast::Expr::TypeCtor { name, body }) + ast::ExprNode::new(ast::Expr::TypeCtor { + name, + type_params, + body, + }) } // Something else not a valid expr @@ -678,36 +703,30 @@ } fn parse_type(&mut self) -> Type { - let t = self.next(); - match t { - Some(Token { - kind: T::Ident(ref s), - span: _, - }) => { - if let Some(t) = Type::get_primitive_type(s) { - t.clone() + let t = self.next().unwrap_or_else(|| self.error("type", None)); + match t.kind { + T::Ident(ref s) => { + let type_params = if self.peek_is(T::LParen.discr()) { + self.parse_type_list() } else { - Type::Named(s.clone(), vec![]) - } + vec![] + }; + Type::Named(s.clone(), type_params) } - Some(Token { kind: T::At, .. }) => { + T::At => { let s = self.expect_ident(); Type::Generic(s) } - Some(Token { - kind: T::LBrace, .. - }) => { + T::LBrace => { let tuptype = self.parse_tuple_type(); tuptype } - Some(Token { kind: T::Fn, .. }) => { + T::Fn => { let fntype = self.parse_fn_type(); fntype } - Some(Token { - kind: T::Struct, .. - }) => self.parse_struct_type(), - other => self.error("type", other), + T::Struct => self.parse_struct_type(), + _ => self.error("type", Some(t)), } } diff --git a/fml/src/typeck.rs b/fml/src/typeck.rs --- a/fml/src/typeck.rs +++ b/fml/src/typeck.rs @@ -212,6 +212,8 @@ // generic type actually exists in the function's scope or such, // when naming an unknown generic name this gives "type mismatch" // rather than "unknown name". Guess it works for now though. + // + // TODO: The scoping here is still bananas, figure it out let tid = self.insert(TypeInfo::Unknown); named_types.insert(s.clone(), tid); TypeInfo::Ref(tid) @@ -225,7 +227,13 @@ let inst_ret = self.instantiate(named_types, rettype); TypeInfo::Func(inst_args, inst_ret) } - Type::Struct(_body) => todo!(), + Type::Struct(body) => { + let inst_body = body + .iter() + .map(|(nm, ty)| (nm.clone(), self.instantiate(named_types, ty))) + .collect(); + TypeInfo::Struct(inst_body) + } }; self.insert(typeinfo) } @@ -489,17 +497,34 @@ tck.set_expr_type(expr, typeid); Ok(typeid) } - TypeCtor { name, body } => { + TypeCtor { + name, + type_params, + body, + } => { let named_type = symtbl.get_type(name).expect("Unknown type constructor"); println!("Got type named {}: is {:?}", name, named_type); - let tid = tck.insert_known(&named_type); + // Ok if we have declared type params we gotta instantiate them + // to match the type's generics. + let type_param_names = named_type.get_generic_names(); + assert_eq!(type_params.len(), type_param_names.len()); + let mut type_mapping = HashMap::new(); + for (name, ty) in type_param_names.iter().zip(type_params.iter()) { + let tid = tck.insert_known(ty); + type_mapping.insert(name.clone(), tid); + } + let tid = tck.instantiate(&mut type_mapping, &named_type); + + //let tid = tck.insert_known(&named_type); let body_type = typecheck_expr(tck, symtbl, body)?; println!("Expected type is {:?}, body type is {:?}", tid, body_type); tck.unify(symtbl, tid, body_type)?; println!("Done unifying type ctor"); // The type the expression returns // TODO: Generic params for type constructors - let constructed_type = tck.insert_known(&Type::Named(name.clone(), vec![])); + //let constructed_type = tck.insert_known(&Type::Named(name.clone(), vec![])); + let constructed_type = + tck.insert_known(&Type::Named(name.clone(), type_params.clone())); tck.set_expr_type(expr, constructed_type); Ok(tid) } @@ -526,13 +551,13 @@ let t = typecheck_func_body(Some(name), tck, symtbl, signature, body); t.unwrap_or_else(|e| { tck.print_types(); - panic!("Error while typechecking function {}:\n{:?}", name, e) + panic!("Error while typechecking function {}:\n{}", name, e) }); } TypeDef { name, params, ty } => { // Make sure that there are no unbound generics in the typedef // that aren't mentioned in the params. - let generic_names = ty.get_generic_names(); + let generic_names: HashSet = ty.get_generic_names().into_iter().collect(); let param_names: HashSet = params.iter().cloned().collect(); let difference: Vec<_> = generic_names .symmetric_difference(¶m_names) diff --git a/fml/tests/main.rs b/fml/tests/main.rs --- a/fml/tests/main.rs +++ b/fml/tests/main.rs @@ -125,3 +125,10 @@ let src = include_str!("test_struct4.gt"); let _output = fml::compile("test_struct4.gt", src); } + +#[test] +#[should_panic] +fn test_struct5() { + let src = include_str!("test_struct5.gt"); + let _output = fml::compile("test_struct5.gt", src); +} diff --git a/fml/tests/test_struct4.gt b/fml/tests/test_struct4.gt --- a/fml/tests/test_struct4.gt +++ b/fml/tests/test_struct4.gt @@ -14,6 +14,10 @@ .a = 3, .b = false }) - --let f2: struct a: I32, b: Bool end = id(struct { a = 3, b = false }) + + let f1: Foo(Bool) = id($Foo(Bool) { + .a = true, + .b = false + }) 3 end diff --git a/fml/tests/test_struct5.gt b/fml/tests/test_struct5.gt new file mode 100644 --- /dev/null +++ b/fml/tests/test_struct5.gt @@ -0,0 +1,23 @@ + +type Foo(@Thing) = struct + a: @Blarg, + b: Bool +end + +fn id(x: @T): @T = + x +end + + +fn main(): I32 = + let f1: Foo(I32) = id($Foo(I32) { + .a = 3, + .b = false + }) + + let f1: Foo(Bool) = id($Foo(Bool) { + .a = true, + .b = false + }) + 3 +end diff --git a/fml/tests/test_typedef1.gt b/fml/tests/test_typedef1.gt --- a/fml/tests/test_typedef1.gt +++ b/fml/tests/test_typedef1.gt @@ -2,7 +2,7 @@ type Foo = I32 fn thing(i: I32): Foo = - $Foo(i) + $Foo i end diff --git a/fml/tests/test_typedef2.gt b/fml/tests/test_typedef2.gt --- a/fml/tests/test_typedef2.gt +++ b/fml/tests/test_typedef2.gt @@ -2,7 +2,7 @@ type Foo = {I32, Bool} fn thing(i: I32): Foo = - $Foo({i, false}) + $Foo {i, false} end