diff --git a/compiler/oxidate/src/tests.rs b/compiler/oxidate/src/tests.rs index c3ba6e5..4c8fc08 100644 --- a/compiler/oxidate/src/tests.rs +++ b/compiler/oxidate/src/tests.rs @@ -13,6 +13,7 @@ mod tests { fn exp_compile_str(inp: &str) -> Vec { let parser = Parser::new_from_string(inp); let parsed = parser.parse().expect("Should parse"); + dbg!(inp); dbg!("parsed:", &parsed); let comp = Compiler::new(parsed); comp.compile().expect("Should compile") @@ -20,7 +21,7 @@ mod tests { fn test_comp(inp: &str, exp: Vec) { let res = exp_compile_str(inp); - dbg!(&res); + // dbg!(&res[28]); assert_eq!(res, exp); } diff --git a/example/concurrency-01.rst b/example/concurrency-01.rst index 9e0b62a..dda5f80 100644 --- a/example/concurrency-01.rst +++ b/example/concurrency-01.rst @@ -1,3 +1,5 @@ +// Expected: prints in order + fn loop_and_print(x: int) { let count = 0; @@ -12,6 +14,8 @@ fn loop_and_print(x: int) { } } +print("Spawning 3 threads"); + let thread_id_1 = spawn loop_and_print(1); let thread_id_2 = spawn loop_and_print(2); let thread_id_3 = spawn loop_and_print(3); @@ -20,4 +24,4 @@ join thread_id_3; join thread_id_2; join thread_id_1; -println(true); +println("Done"); diff --git a/example/concurrency-03.rst b/example/concurrency-03.rst index 60b17a6..97c5858 100644 --- a/example/concurrency-03.rst +++ b/example/concurrency-03.rst @@ -1,5 +1,4 @@ -// Expected: inconsistent count on each run due to race on count -// When running with a small quantum e.g 1 +// Expected: count != 3000 on each run, though we want 3000 let count = 0; diff --git a/example/concurrency-04.rst b/example/concurrency-04.rst index 3ca25d1..ac562c4 100644 --- a/example/concurrency-04.rst +++ b/example/concurrency-04.rst @@ -1,7 +1,7 @@ // Expected: count = 3000 on each run let count = 0; -let sem = sem_create(); +let sem : sem = sem_create(); fn increment(times: int) { let i = 0; diff --git a/example/garbage-collection-02.rst b/example/garbage-collection-02.rst index 3a183fe..1c66273 100644 --- a/example/garbage-collection-02.rst +++ b/example/garbage-collection-02.rst @@ -1,6 +1,5 @@ -// Compile untyped: hof type annotation not added yet -fn higher_order(x) { - fn g(y) { +fn higher_order(x: int) -> fn(int) -> int { + fn g(y:int) -> int { x + y } diff --git a/example/higher-order-fn-01.rst b/example/higher-order-fn-01.rst index 833bcd7..c4ef3f0 100644 --- a/example/higher-order-fn-01.rst +++ b/example/higher-order-fn-01.rst @@ -1,14 +1,16 @@ -// Compile untyped: hof type annotation not added yet - -fn f(x) { +fn f(x: int) -> fn(int) -> int { let z = 3; - fn g(y) { + fn g(y: int) -> int { return x + y + z; } g } -let hof = f(2); +// try uncommenting this line to get type error when compiling +// let hof : fn(int) -> bool = f(2); + +let hof : fn(int) -> int = f(2); +// Expected: 9 hof(4) \ No newline at end of file diff --git a/example/loop-04.rst b/example/loop-04.rst index 83ec259..2249116 100644 --- a/example/loop-04.rst +++ b/example/loop-04.rst @@ -1,6 +1,3 @@ -// Doesn't do anything in particular, I asked ChatGPT to generate this and verified -// by converting to Rust - let count = 0; let x = 0; diff --git a/example/simple-spawn.rst b/example/simple-spawn.rst index e8ce81f..3a1b04d 100644 --- a/example/simple-spawn.rst +++ b/example/simple-spawn.rst @@ -1,9 +1,11 @@ fn func() { - let j = 500; - loop j < 600 { - println(j); + let j = 0; + loop j < 100 { + println("in func"); j = j + 1; } + + println("func is done"); } @@ -11,10 +13,12 @@ fn func() { let tid = spawn func(); let i = 0; -loop i < 100 { - println(i); +loop i < 200 { + println("in main"); i = i + 1; } // Uncomment to ensure func finishes before main //join tid; + +println("main is done"); diff --git a/src/parser/src/fn_decl.rs b/src/parser/src/fn_decl.rs index 7861c33..a2d0917 100644 --- a/src/parser/src/fn_decl.rs +++ b/src/parser/src/fn_decl.rs @@ -62,7 +62,7 @@ impl<'inp> Parser<'inp> { param_ty.replace(ty); // to go past last token of type_ann, so peek is at comma or close paren - self.advance(); + // self.advance(); } // Comma or CloseParen @@ -98,7 +98,7 @@ impl<'inp> Parser<'inp> { if self.consume_opt_token_type(Token::FnDeclReturn) { // peek is now at type_ann first token let ret_ty_ann = self.parse_type_annotation()?; - self.advance(); // go past last token of ty_ann + // self.advance(); // go past last token of ty_ann ret_ty = ret_ty_ann; } @@ -426,4 +426,21 @@ mod tests { "fn fac (n:int) -> int { if (n==0) { return 1; };return (n*fac((n-1))); };", ); } + + #[test] + fn test_parse_fn_decl_hof_ret() { + let t = r" + fn adder(x : int) -> fn(int) -> bool { + fn f(y:int) -> bool { + x+y > 0 + } + + adder + } + "; + test_parse( + t, + "fn adder (x:int) -> fn(int) -> bool { fn f (y:int) -> bool { ((x+y)>0) };adder };", + ); + } } diff --git a/src/parser/src/let_stmt.rs b/src/parser/src/let_stmt.rs index bfc3437..ecb0323 100644 --- a/src/parser/src/let_stmt.rs +++ b/src/parser/src/let_stmt.rs @@ -25,7 +25,7 @@ impl<'inp> Parser<'inp> { type_ann.replace(ty); // call advance so peek is at equals - self.advance(); + // self.advance(); } self.consume_token_type(Token::Eq, "Expected '='")?; diff --git a/src/parser/src/lib.rs b/src/parser/src/lib.rs index 0d34f88..94b981c 100644 --- a/src/parser/src/lib.rs +++ b/src/parser/src/lib.rs @@ -10,6 +10,7 @@ pub mod ident; pub mod if_else; pub mod let_stmt; pub mod parse_loop; +pub mod parse_type_ann; pub mod seq; pub mod structs; @@ -135,7 +136,7 @@ impl<'inp> Parser<'inp> { fn expect_token_for_type_ann(token: Option<&Result>) -> Result<(), ParseError> { if let Some(Ok(tok)) = token { match tok { - Token::Ident(_) | Token::OpenParen => Ok(()), + Token::Ident(_) | Token::OpenParen | Token::Fn => Ok(()), _ => { let e = format!( "Expected identifier or '(' for type annotation, got '{}'", @@ -150,39 +151,6 @@ impl<'inp> Parser<'inp> { )) } } - - /// Parse and return type annotation. Expect lexer.peek() to be at Colon before call - // Should only consume tokens belonging to the annotation, starting peek at first token and ending - // peek at the last token of the annotation - fn parse_type_annotation(&mut self) -> Result { - // self.consume_token_type(Token::Colon, "Expected a colon")?; - // expect_token_body!(self.lexer.peek(), Ident, "identifier")?; - Parser::expect_token_for_type_ann(self.lexer.peek())?; - - // if ident, get the string and try to convert type. else, handle specially - let peek = self - .lexer - .peek() - .unwrap() - .to_owned() - .expect("Lexer should not fail"); // would have erred earlier - - let type_ann = match peek { - Token::Ident(id) => Type::from_string(&id), - Token::OpenParen => { - self.advance(); - if let Some(Ok(Token::CloseParen)) = self.lexer.peek() { - Ok(Type::Unit) - } else { - Err(ParseError::new("Expected '()' for unit type annotation")) - } - } - _ => unreachable!(), - }?; - - Ok(type_ann) - } - /* Precedence */ // Return (left bp, right bp) @@ -384,39 +352,6 @@ mod tests { ); } - #[test] - fn test_parse_type_annotations() { - test_parse("let x : int = 2;", "let x : int = 2;"); - test_parse("let x : bool = true;", "let x : bool = true;"); - test_parse("let x : float = true;", "let x : float = true;"); - test_parse("let x : () = true;", "let x : () = true;"); - } - - #[test] - fn test_parse_type_annotations_errs() { - // test_parse("let x : int = 2;", ""); - test_parse_err( - "let x : let ", - "Expected identifier or '(' for type annotation, got 'let'", - true, - ); - test_parse_err( - "let x : 2 ", - "Expected identifier or '(' for type annotation, got '2'", - true, - ); - test_parse_err( - "let x : ", - "Expected identifier or '(' for type annotation, got end of input", - true, - ); - test_parse_err( - "let x : (2 ", - "Expected '()' for unit type annotation", - true, - ); - } - #[test] fn test_parse_concurrency() { let t = r" diff --git a/src/parser/src/parse_type_ann.rs b/src/parser/src/parse_type_ann.rs new file mode 100644 index 0000000..318005d --- /dev/null +++ b/src/parser/src/parse_type_ann.rs @@ -0,0 +1,177 @@ +use crate::FnTypeData; +use crate::ParseError; +use crate::Parser; +use crate::Type; +use lexer::Token; + +impl<'inp> Parser<'inp> { + /// Parse and return type annotation. Expect lexer.peek() to be at Colon before call + // Should only consume tokens belonging to the annotation, starting peek at first token and ending + // peek at token AFTER the last token of type annotation + pub(crate) fn parse_type_annotation(&mut self) -> Result { + // self.consume_token_type(Token::Colon, "Expected a colon")?; + // expect_token_body!(self.lexer.peek(), Ident, "identifier")?; + Parser::expect_token_for_type_ann(self.lexer.peek())?; + + // if ident, get the string and try to convert type. else, handle specially + let peek = self + .lexer + .peek() + .unwrap() + .to_owned() + .expect("Lexer should not fail"); // would have erred earlier + + let type_ann = match peek { + Token::Ident(id) => { + let res = Type::from_string(&id); + self.advance(); + res + } + Token::OpenParen => { + self.advance(); + if let Some(Ok(Token::CloseParen)) = self.lexer.peek() { + self.advance(); + Ok(Type::Unit) + } else { + Err(ParseError::new("Expected '()' for unit type annotation")) + } + } + Token::Fn => { + self.advance(); // go past fn + self.consume_token_type( + Token::OpenParen, + "Expected '(' for function type annotation", + )?; // go past ( + + let mut param_types: Vec = vec![]; + let mut ret_ty = Type::Unit; + + // Parse param types + while let Some(tok) = self.lexer.peek() { + let tok = tok.clone(); + // stop at ) + if tok.clone().unwrap().eq(&Token::CloseParen) { + break; + } + + let param_ty = self.parse_type_annotation()?; + param_types.push(param_ty); + // self.advance(); // go past token of last ty_an + + // Comma or CloseParen + if !self.lexer.peek().eq(&Some(&Ok(Token::CloseParen))) { + self.consume_token_type( + Token::Comma, + "Expected ',' to separate function parameters", + )?; + } + } + + // dbg!("PEEK AFTER LOOP:", &self.lexer.peek()); + + self.advance(); // skip past open paren, peek is at return arrow or equals + + if self.consume_opt_token_type(Token::FnDeclReturn) { + // peek is now at type_ann first token + let ret_ty_ann = self.parse_type_annotation()?; + // self.advance(); // go past last token of ty_ann + // dbg!(&ret_ty_ann); + ret_ty = ret_ty_ann; + } + + let fn_ty_data = FnTypeData { + params: param_types, + ret_type: ret_ty, + }; + + Ok(Type::UserFn(Box::new(fn_ty_data))) + } + _ => unreachable!(), + }?; + + Ok(type_ann) + } +} + +#[cfg(test)] +mod tests { + use crate::tests::{test_parse, test_parse_err}; + + #[test] + fn test_parse_type_annotations() { + test_parse("let x : int = 2;", "let x : int = 2;"); + test_parse("let x : bool = true;", "let x : bool = true;"); + test_parse("let x : float = true;", "let x : float = true;"); + test_parse("let x : () = true;", "let x : () = true;"); + test_parse(r"let x : str = 2;", "let x : str = 2;"); + test_parse("let x : sem = 2;", "let x : sem = 2;"); + } + + #[test] + fn test_parse_type_annotations_errs() { + // test_parse("let x : int = 2;", ""); + test_parse_err( + "let x : let ", + "Expected identifier or '(' for type annotation, got 'let'", + true, + ); + test_parse_err( + "let x : 2 ", + "Expected identifier or '(' for type annotation, got '2'", + true, + ); + test_parse_err( + "let x : ", + "Expected identifier or '(' for type annotation, got end of input", + true, + ); + test_parse_err( + "let x : (2 ", + "Expected '()' for unit type annotation", + true, + ); + } + + #[test] + fn test_parse_type_annotations_fns() { + // // empty + let t = r" + let g : fn() = f; + "; + test_parse(t, "let g : fn() = f;"); + + // // has params + let t = r" + let g : fn(int) = f; + "; + test_parse(t, "let g : fn(int) = f;"); + + let t = r" + let g : fn(int, bool) = f; + "; + test_parse(t, "let g : fn(int, bool) = f;"); + + // // // param is fn + let t = r" + let g : fn(int, fn(int)) = f; + "; + test_parse(t, "let g : fn(int, fn(int)) = f;"); + + // ret type + let t = r" + let g : fn(int) -> bool = f; + "; + test_parse(t, "let g : fn(int) -> bool = f;"); + + let t = r" + let g : fn(int, float) -> bool = f; + "; + test_parse(t, "let g : fn(int, float) -> bool = f;"); + + // returns fn + let t = r" + let g : fn(int, bool) -> fn(int) -> int = f; + "; + test_parse(t, "let g : fn(int, bool) -> fn(int) -> int = f;"); + } +} diff --git a/src/parser/src/structs.rs b/src/parser/src/structs.rs index b65a0d8..dcc2003 100644 --- a/src/parser/src/structs.rs +++ b/src/parser/src/structs.rs @@ -395,9 +395,10 @@ impl Display for ParseError { impl std::error::Error for ParseError {} // Type of a function value - subset of FnDeclData +// Params: care only about types not names #[derive(Debug, Clone, PartialEq)] pub struct FnTypeData { - pub params: Vec, + pub params: Vec, pub ret_type: Type, } @@ -407,12 +408,7 @@ impl Display for FnTypeData { let params_str = if self.params.is_empty() { "()".to_string() } else { - let params_display: Vec = self - .params - .iter() - .map(|p| format!("{}", p.type_ann.clone().unwrap_or(Type::Unit))) - .map(|x| x.to_string()) - .collect(); + let params_display: Vec = self.params.iter().map(|x| x.to_string()).collect(); format!("({})", params_display.join(", ")) }; @@ -465,6 +461,8 @@ impl Type { "int" => Ok(Self::Int), "bool" => Ok(Self::Bool), "float" => Ok(Self::Float), + "str" => Ok(Self::String), + "sem" => Ok(Self::Semaphore), _ => Err(ParseError::new(&format!( "Unknown primitive type: {}", input @@ -482,7 +480,7 @@ impl Display for Type { Self::Unit => "()".to_string(), Self::Unitialised => "uninit".to_string(), Self::BuiltInFn => "builtin_fn".to_string(), - Self::String => "string".to_string(), + Self::String => "str".to_string(), Self::UserFn(fn_ty) => fn_ty.to_string(), Self::ThreadId => "tid".to_string(), Self::Semaphore => "sem".to_string(), diff --git a/src/types/src/check_fn_call.rs b/src/types/src/check_fn_call.rs index eb853f3..ae161a0 100644 --- a/src/types/src/check_fn_call.rs +++ b/src/types/src/check_fn_call.rs @@ -382,16 +382,10 @@ impl<'prog> TypeChecker<'prog> { // TODO: lookup type of the user fn in env, cast to function type and use its return type let fn_ty = self.get_type(&fn_call.name)?.to_fn_type(); if let Some(ty) = fn_ty { - let param_types: Vec = ty - .params - .iter() - .map(|x| x.type_ann.clone().unwrap()) - .collect(); + let param_types: Vec = ty.params.iter().map(|x| x.to_owned()).collect(); TypeChecker::check_arg_params_match(&fn_call.name, &arg_types, ¶m_types)?; check_res.ty = ty.ret_type; - } else { - panic!("Fn name not in env"); } // dbg!("fn_ty", fn_ty); // check_res.ty = fn_ty; @@ -410,6 +404,11 @@ mod tests { #[test] fn test_type_check_userfn_call() { + let t = r" + f(); + "; + expect_err(t, "Identifier 'f' not declared", true); + let t = r" fn fac(n : int) -> int { 2 diff --git a/src/types/src/check_fn_decl.rs b/src/types/src/check_fn_decl.rs index 0c6be3e..7ff35b3 100644 --- a/src/types/src/check_fn_decl.rs +++ b/src/types/src/check_fn_decl.rs @@ -3,33 +3,89 @@ use parser::structs::{FnDeclData, FnTypeData, Type}; use crate::type_checker::{CheckResult, TypeChecker, TypeErrors}; impl<'prog> TypeChecker<'prog> { - // 1. all nested returns should have same type as annotated ret type: use fn_stack to track this pub(crate) fn check_fn_decl( &mut self, fn_decl: &FnDeclData, ) -> Result { + self.fn_type_stack.push(fn_decl.ret_type.clone()); + let res = self.check_fn_decl_inner(fn_decl); + self.fn_type_stack.pop(); + res + } + + // 1. all nested returns belonging to fn should have same type as annotated ret type: use fn_stack to track this + // 2. Last expr (if it exists) must have same type as annotated, unless there was must_return before + + // 3. Fn decl well-typed iff - all nested return stmts belonging to the function return the same type as the ty_ann, + // AND (somewhere in the block we encounter a terminating decl/ last_expr OR the + // last expression of the block has the same type as the ty_ann) + // Everything after a must_return is ignored. function returns unit => don't need must_return, but nested ret cannot return anything else + fn check_fn_decl_inner(&mut self, fn_decl: &FnDeclData) -> Result { + // Assert all params have type ann and add their types + let mut param_types: Vec = vec![]; + + for param in fn_decl.params.iter() { + if let Some(ty) = ¶m.type_ann { + param_types.push(ty.to_owned()); + } else { + let e = format!("Parameter '{}' has no type annotation", param.name); + return Err(TypeErrors::new_err(&e)); + } + } + let fn_ty = FnTypeData { - params: fn_decl.params.clone(), + params: param_types, ret_type: fn_decl.ret_type.clone(), }; let fn_ty = Type::UserFn(Box::new(fn_ty)); // let mut ty_errs = TypeErrors::new(); + let fn_res = CheckResult { + ty: fn_ty.clone(), + must_break: false, + must_return: false, + }; + // Before checking block, add this fn to env to support recursion self.assign_ident(&fn_decl.name, fn_ty.clone())?; // should work because of enterscope // dbg!("FN_PARAMS:", &fn_decl.params, &fn_decl.name); - self.check_block(&fn_decl.body, fn_decl.params.clone())?; + let blk_res = self.check_block(&fn_decl.body, fn_decl.params.clone())?; + // dbg!("FN BLK TYPE:", &blk_res); + + // If must_return encountered in block, we assume nested returns are correct type so just stop here + if blk_res.must_return { + return Ok(fn_res); + } + + // check blk_ty matches overall ret type only if last_expr exists + if fn_decl.body.last_expr.is_some() { + if blk_res.ty.eq(&fn_decl.ret_type) { + return Ok(fn_res); + } else { + let e = format!( + "Function '{}' has return type '{}' but found block type '{}'", + fn_decl.name, fn_decl.ret_type, blk_res.ty + ); + return Err(TypeErrors::new_err(&e)); + } + } + + // if no must_return, and no last_expr, and overall type is not Unit, err + if !fn_decl.ret_type.eq(&Type::Unit) { + let e = format!( + "Function '{}' might not return '{}'", + fn_decl.name, fn_decl.ret_type + ); + return Err(TypeErrors::new_err(&e)); + } + + Ok(fn_res) + // If everything is ok, return the annotated types // Fn decl doesn't contribute to overall must_ret / must_break of the outer block - let res = CheckResult { - ty: fn_ty, - must_break: false, - must_return: false, - }; - Ok(res) } } @@ -94,13 +150,143 @@ mod tests { #[test] fn test_type_check_fn_rettype() { - // should fail because blk has unit - // let t = r" - // fn f(x: int) -> int { - // f(x-1); - // } - // f - // "; + // should fail because blk has type bool + let t = r" + fn f(x: int) -> int { + true + } + f + "; + expect_err(t, "has return type 'int' but found block type 'bool'", true); + + // last expr ret + let t = r" + fn f() -> int { + 20+30 + } + "; + expect_pass(t, Type::Unit); + + let t = r" + fn f() -> int { + return 20; + } + "; + expect_pass(t, Type::Unit); + + let t = r" + fn f() -> int { + { + return 30; + } + true + } + "; + expect_pass(t, Type::Unit); + + let t = r" + fn f() -> int { + if true { + return 20; + } else { + return 30; + } + } + "; + expect_pass(t, Type::Unit); + + // // if only, loop are not must_ret + // // although inf loop that would definitely return here, we are conservative + let t = r" + fn f() -> int { + if true { + return 20; + } + + loop { + return 30; + } + } + "; + expect_err(t, "might not return", true); + + // unit - don't have to must_return + let t = r" + fn f() { + if true { + return; + } + + loop { + return; + } + + fn g() -> int { + 200 + } + } + "; + expect_pass(t, Type::Unit); + + // if else + let t = r" + fn f() -> int { + if true { + return 20; + } else { + 30 + } + } + "; + expect_pass(t, Type::Unit); + + let t = r" + fn f(x: int) -> int { + 2; + } + f + "; + expect_err(t, "might not return", true); + } + + #[test] + fn test_type_check_fn_ret_stmt() { + let t = r" + fn f() -> int { + if true { + return true; + } else { + return 2.56; + } + + return 5; + } + "; + expect_err(t, "[TypeError]: Expected function return type 'int' but return statement has type 'bool'\n[TypeError]: Expected function return type 'int' but return statement has type 'float'", false); + + // check that it ignores inner return for hof + let t = r" + fn f() -> int { + fn g() -> bool { + return true; + } + + return 20; + } + f + "; + expect_pass_str(t, "fn() -> int"); + + // when return expr has error - keeps checking rest + let t = r" + fn f() -> int { + if true { + return 2+ !2; + } + return !true; + } + "; + expect_err(t, "[TypeError]: Can't apply logical NOT to type int\n[TypeError]: Expected function return type 'int' but return statement has type 'bool'", false); } #[test] @@ -124,5 +310,59 @@ mod tests { fac(1) "; expect_err(t, "'x' has declared type bool but assigned type int", true); + + let t = r" + fn fac(n: int, b: bool) { + n + b + } + fac(1) + "; + expect_err(t, "Can't apply '+' to types 'int' and 'bool'", true); + } + + #[test] + fn test_type_check_fn_hof() { + let t = r" + fn adder(x : int) -> fn(int) -> bool { + fn f(y:int) -> bool { + x+y > 0 + } + + adder + } + "; + expect_err( + t, + "has return type 'fn(int) -> bool' but found block type 'fn(int) -> fn(int) -> bool'", + true, + ); + + let t = r" + fn adder(x : int) -> fn(int) -> bool { + fn f(y:int) -> int { + x+y + } + + f + } + "; + expect_err( + t, + "has return type 'fn(int) -> bool' but found block type 'fn(int) -> int", + true, + ); + + // ok + let t = r" + fn adder(x : int) -> fn(int) -> int { + fn f(y:int) -> int { + x+y + } + + f + } + adder + "; + expect_pass_str(t, "fn(int) -> fn(int) -> int"); } } diff --git a/src/types/src/type_checker.rs b/src/types/src/type_checker.rs index 0e03939..be668fd 100644 --- a/src/types/src/type_checker.rs +++ b/src/types/src/type_checker.rs @@ -91,6 +91,8 @@ impl CheckResult { pub struct TypeChecker<'prog> { program: &'prog BlockSeq, pub(crate) envs: Vec, + // stores type of function currently being checked at top (empty if not checking function) + pub(crate) fn_type_stack: Vec, } impl<'prog> TypeChecker<'prog> { @@ -98,6 +100,7 @@ impl<'prog> TypeChecker<'prog> { TypeChecker { program, envs: vec![], + fn_type_stack: vec![], } } @@ -449,11 +452,38 @@ impl<'prog> TypeChecker<'prog> { } Decl::FnDeclStmt(fn_decl) => self.check_fn_decl(fn_decl), // TODO: check nested returns with fn stack - Decl::ReturnStmt(_) => Ok(CheckResult { - ty: Type::Unit, - must_break: true, - must_return: true, - }), + Decl::ReturnStmt(ret_expr) => { + // dbg!("fn_stack at return:", &self.fn_type_stack); + let mut res = CheckResult { + ty: Type::Unit, + must_break: true, + must_return: true, + }; + + if let Some(expr) = ret_expr { + let expr_res = self.check_expr(expr)?; + res = CheckResult::combine(&res, &expr_res); + res.ty = expr_res.ty; + } + + // now it's either unit or the type of the ret_expr + // return type must match fn annotated + + // expect because parser rejects return outside function + let fn_ty = self + .fn_type_stack + .last() + .expect("Should have type in fn_stack"); + if !res.ty.eq(fn_ty) { + let e = format!( + "Expected function return type '{}' but return statement has type '{}'", + fn_ty, res.ty + ); + return Err(TypeErrors::new_err(&e)); + } + + Ok(res) + } Decl::WaitStmt(_) => Ok(CheckResult { ty: Type::Unit, must_break: false, @@ -719,4 +749,13 @@ mod tests { true, ); } + + #[test] + fn type_check_sem_string() { + let t = r#"let t = "hello world"; t"#; + expect_pass(t, Type::String); + + let t = r"let t = sem_create(); t"; + expect_pass(t, Type::Semaphore); + } } diff --git a/vm/ignite/tests/e2e.rs b/vm/ignite/tests/e2e.rs index 6b15ce8..d51f659 100644 --- a/vm/ignite/tests/e2e.rs +++ b/vm/ignite/tests/e2e.rs @@ -557,7 +557,7 @@ fn test_e2e_fib() -> Result<()> { #[test] fn test_e2e_fn_decl() -> Result<()> { let t = r" - fn fac(n: int) { + fn fac(n: int) -> int { 2 + n } @@ -583,5 +583,33 @@ fn test_e2e_fn_decl() -> Result<()> { "; test_pass(hof, "24")?; + let hof = r" + fn adder(x: int) -> fn(int) -> int { + fn g(y: int) -> int { + x + y + } + + return g; + } + + let add5 : fn(int) -> int = adder(5); + add5(10) + "; + test_pass(hof, "15")?; + + // apply fn passed in + let hof = r" + fn apply(f: fn(int) -> int, x: int) -> int { + f(x) + } + + fn add(x: int) -> int { + return x + 5; + } + + apply(add, 9) + "; + test_pass(hof, "14")?; + Ok(()) }