diff --git a/docs/09-projects01.md b/docs/09-projects01.md index 995eb12..f9f8f37 100644 --- a/docs/09-projects01.md +++ b/docs/09-projects01.md @@ -393,4 +393,4 @@ A good starting point for any of them is: * Review the implementation of the type code generator * Deadline: 30/06 -The outcomes of the projects must be submitted via pull-requests. +The outcomes of the projects must be submitted via pull-requests. \ No newline at end of file diff --git a/docs/semantics-first-class-fn.md b/docs/semantics-first-class-fn.md new file mode 100644 index 0000000..87de355 --- /dev/null +++ b/docs/semantics-first-class-fn.md @@ -0,0 +1,77 @@ +# Semântica: Funções de Primeira Classe e Closures (Milestone 2) + +Objetivo curto +- Formalizar como funções passam a ser valores em MiniC, qual a representação em runtime e a semântica de captura (closures). + +Decisão principal +- Captura por snapshot no momento da criação: uma lambda carrega uma cópia do *ambiente de execução* (snapshot do `Environment`) quando é criada. Ao chamar a closure, o intérprete restaura esse ambiente capturado, vincula os parâmetros e executa o corpo. + +Representação sugerida (estrutura em Rust-like, acho que era assim que o professor fez) +``` +// representação conceitual +struct ClosureValue { + params: Vec, // nomes + tipos + return_type: Type, + body: Statement, // AST do corpo + captured_env: Environment, // snapshot por criação +} + +// em runtime stored as +// Value::Fn(FnValue::Closure { decl, captured }) +``` + +O que é capturado +- Capturamos um *snapshot completo* do `Environment` (mapa nome → `Value`) no momento da criação da lambda. Isso significa: + - variáveis livres ficam com o valor que tinham na criação; + - a closure não observa mudanças subsequentes em bindings fora do seu escopo local. + +Semântica da chamada (ideia em pseudocódigo) +``` +fn call_closure(closure, args, env) { + // 1. snapshot do caller + caller_snapshot = env.snapshot() + + // 2. entra no ambiente capturado (lexical scope) + env.restore(closure.captured_env.clone()) + + // 3. bind dos parâmetros com os argumentos (sobrepondo nomes capturados) + for (param, arg) in zip(closure.params, args) { + env.declare(param.name.clone(), arg) + } + + // 4. executar o corpo + result = exec_stmt(&closure.body, env) + + // 5. restaurar ambiente do caller + env.restore(caller_snapshot) + + return result.unwrap_or(Value::Void) +} +``` + +Decisão sobre declarações sem inicializador +- Não declarar uma variável de tipo função sem init (a não ser que queiram mais trabalho, ai só avisar no grupo pra gente rever isso). + +Invariantes de tipo +- Uma `Expr::Lambda` tem tipo `Type::Fun(param_types, Box::new(return_type))` no type checker. +- `types_compatible` deve comparar aridade e compatibilidade por posição de `Type::Fun`. + +Mensagens de erro (sugeridas) +- Chamar algo não-função: `RuntimeError: 'X' is not a function` ou `attempting to call a non-function value`. +- Número de argumentos errado: `function 'f' expects N arguments, got M`. +- Atribuição de tipo inválida: `assignment to f: expected fn(...)->..., got ...`. + +Testes obrigatórios (mínimos) — mapeados +- Pessoa 2 (Sistema de Tipos): + - `fn(int) -> int f;` — válido + - `fn(float) -> int f; f = fn(int x) -> int { return x; }` — inválido (TypeError) + +- Pessoa 3 (Lambdas / Chamadas — type-checker): + - `fn(int x) -> int { return x * 2; }` — type-check OK + - `f(true)` com `f: fn(int)->int` — inválido (TypeError) + +- Pessoa 4 (Runtime: função como valor): + - declarar `f = fn(int x) -> int { return x * 2 }; print(f(21));` → saída `42` + +- Pessoa 5 (Closures): + - `int y = 10; fn(int)->int f = fn(int x)->int { return x + y; }; y = 20; print(f(1));` → saída `11` (captura por snapshot) diff --git a/src/interpreter/eval_expr.rs b/src/interpreter/eval_expr.rs index 49fcbef..03f0179 100644 --- a/src/interpreter/eval_expr.rs +++ b/src/interpreter/eval_expr.rs @@ -45,7 +45,6 @@ use crate::ir::ast::{CheckedExpr, Expr, Literal}; use super::exec_stmt::exec_stmt; use super::value::{FnValue, RuntimeError, Value}; -/// Evaluate a checked expression to a runtime value. pub fn eval_expr(expr: &CheckedExpr, env: &mut Environment) -> Result { match &expr.exp { Expr::Literal(lit) => Ok(eval_literal(lit)), @@ -145,12 +144,36 @@ pub fn eval_expr(expr: &CheckedExpr, env: &mut Environment) -> Result { let arg_vals: Result, RuntimeError> = args.iter().map(|a| eval_expr(a, env)).collect(); - eval_call(name, arg_vals?, env) + + let callee = env + .get(name) + .cloned() + .ok_or_else(|| RuntimeError::new(format!("undefined function '{}'", name)))?; + + eval_call_value(callee, arg_vals?, env, Some(name)) + } + + Expr::CallExpr { chmd, args } => { + let callee_val = eval_expr(chmd, env)?; + let arg_vals: Result, RuntimeError> = + args.iter().map(|a| eval_expr(a, env)).collect(); + + eval_call_value(callee_val, arg_vals?, env, None) + } + + Expr::Lambda { params, return_tipo, crp } => { + let captured = env.snapshot(); + let decl = crate::ir::ast::CheckedFunDecl { + name: "".to_string(), + params: params.clone(), + return_type: return_tipo.clone(), + body: crp.clone(), + }; + Ok(Value::Fn(FnValue::Closure { decl, captured })) } } } -/// Dispatch a function call via the unified environment. pub fn eval_call( name: &str, args: Vec, @@ -171,15 +194,91 @@ pub fn eval_call( for ((param_name, _), val) in decl.params.iter().zip(args.into_iter()) { env.declare(param_name.clone(), val); } - let result = exec_stmt(&decl.body, env)?; + + let result = exec_stmt(&decl.body, env); env.restore(snapshot); - Ok(result.unwrap_or(Value::Void)) + + match result { + Ok(result) => Ok(result.unwrap_or(Value::Void)), + Err(err) => Err(err), + } } + Some(_) => Err(RuntimeError::new(format!("'{}' is not a function", name))), None => Err(RuntimeError::new(format!("undefined function '{}'", name))), } } +fn eval_call_value( + callee: Value, + args: Vec, + env: &mut Environment, + callee_name: Option<&str>, +) -> Result { + match callee { + Value::Fn(FnValue::Native(f)) => (f)(args), + + Value::Fn(FnValue::UserDefined(decl)) => { + if args.len() != decl.params.len() { + return Err(RuntimeError::new(format!( + "function '{}' expects {} arguments, got {}", + decl.name, + decl.params.len(), + args.len() + ))); + } + + let snapshot = env.snapshot(); + for ((param_name, _), val) in decl.params.iter().zip(args.into_iter()) { + env.declare(param_name.clone(), val); + } + + let result = exec_stmt(&decl.body, env); + env.restore(snapshot); + + match result { + Ok(result) => Ok(result.unwrap_or(Value::Void)), + Err(err) => Err(err), + } + } + + Value::Fn(FnValue::Closure { decl, captured }) => { + if args.len() != decl.params.len() { + return Err(RuntimeError::new(format!( + "function '{}' expects {} arguments, got {}", + decl.name, + decl.params.len(), + args.len() + ))); + } + + let caller_snapshot = env.snapshot(); + + env.restore(captured); + + for ((param_name, _), val) in decl.params.iter().zip(args.into_iter()) { + env.declare(param_name.clone(), val); + } + + let result = exec_stmt(&decl.body, env); + env.restore(caller_snapshot); + + match result { + Ok(result) => Ok(result.unwrap_or(Value::Void)), + Err(err) => Err(err), + } + } + + other => { + if let Some(name) = callee_name { + Err(RuntimeError::new(format!("'{}' is not a function", name))) + } else { + Err(RuntimeError::new(format!("value is not callable: {}", other))) + } + } + } +} + // --- Helpers --- fn eval_literal(lit: &Literal) -> Value { @@ -237,4 +336,4 @@ fn values_equal(a: &Value, b: &Value) -> bool { (Value::Str(x), Value::Str(y)) => x == y, _ => false, } -} +} \ No newline at end of file diff --git a/src/interpreter/exec_stmt.rs b/src/interpreter/exec_stmt.rs index ceeda2c..608d465 100644 --- a/src/interpreter/exec_stmt.rs +++ b/src/interpreter/exec_stmt.rs @@ -47,7 +47,11 @@ pub fn exec_stmt(stmt: &CheckedStmt, env: &mut Environment) -> ExecResult match &stmt.stmt { // --- Variable declaration --- Statement::Decl { name, init, .. } => { - let val = eval_expr(init, env)?; + let val = if let Some(init_expr) = init { + eval_expr(init_expr, env)? + } else { + Value::Void + }; env.declare(name.clone(), val); Ok(None) } diff --git a/src/interpreter/value.rs b/src/interpreter/value.rs index 2abdc6e..e03c2cf 100644 --- a/src/interpreter/value.rs +++ b/src/interpreter/value.rs @@ -61,6 +61,7 @@ //! needs `Value`, and `Value` needs to reference the callable type. use std::fmt; +use std::collections::HashMap; use crate::ir::ast::CheckedFunDecl; @@ -72,6 +73,11 @@ pub type NativeFn = fn(Vec) -> Result; pub enum FnValue { UserDefined(CheckedFunDecl), Native(NativeFn), + + Closure { + decl: CheckedFunDecl, + captured: HashMap, + }, } impl PartialEq for FnValue { @@ -79,6 +85,7 @@ impl PartialEq for FnValue { match (self, other) { (FnValue::UserDefined(a), FnValue::UserDefined(b)) => a == b, (FnValue::Native(a), FnValue::Native(b)) => (*a as usize) == (*b as usize), + (FnValue::Closure { decl: da, .. }, FnValue::Closure { decl: db, .. }) => da == db, _ => false, } } @@ -89,6 +96,7 @@ impl fmt::Debug for FnValue { match self { FnValue::UserDefined(decl) => write!(f, "UserDefined({})", decl.name), FnValue::Native(_) => write!(f, "Native()"), + FnValue::Closure { decl, .. } => write!(f, "Closure({})", decl.name), } } } @@ -148,4 +156,4 @@ impl fmt::Display for RuntimeError { } } -impl std::error::Error for RuntimeError {} +impl std::error::Error for RuntimeError {} \ No newline at end of file diff --git a/src/ir/ast.rs b/src/ir/ast.rs index fc400dd..d7f5266 100644 --- a/src/ir/ast.rs +++ b/src/ir/ast.rs @@ -105,6 +105,25 @@ pub enum Expr { name: String, args: Vec>, }, + + /// Chamada de função por expressão: chmd(args) + /// apenas para não mexer em Call, mas depois podemos mesclar os dois (Call pode ser um caso especial de CallExpr onde callee é um Ident). + /// Ex.: 'f(42)', '(funçãolambda)(42)', etc.) + CallExpr { + chmd: Box>, + args: Vec>, + }, + + /// Função Lambda: `fn(params) -> return_tipo { crp }` + /// regra pra não ficar ambiguo: + /// 'fn(...) -> ...' é tipo, ou seja 'Type::Fun' + /// 'fn(...) -> ... { ... }' é expressão, ou seja 'Expr::Lambda' + Lambda { + params: Vec, + return_tipo: Type, + crp: Box>, + }, + /// Array literal: [ expr, expr, ... ] ArrayLit(Vec>), /// Index expression: `base[index]` @@ -128,7 +147,8 @@ pub enum Statement { Decl { name: String, ty: Type, - init: Box>, + /// Adicionado para suportar algo como 'fn(int) -> int f;' + init: Option>>, }, Assign { target: Box>, diff --git a/src/parser/expressions.rs b/src/parser/expressions.rs index 8cfcab4..e514472 100644 --- a/src/parser/expressions.rs +++ b/src/parser/expressions.rs @@ -34,9 +34,11 @@ //! recursing on the right-hand side, which would accidentally produce //! right-associative trees. -use crate::ir::ast::{Expr, ExprD, UncheckedExpr}; +use crate::ir::ast::{Expr, ExprD, Param, UncheckedExpr}; use crate::parser::identifiers::identifier; +use crate::parser::functions::type_name; use crate::parser::literals::literal; +use crate::parser::statements::block_statement; use nom::{ branch::alt, bytes::complete::tag, @@ -65,11 +67,51 @@ pub fn parse_call(input: &str) -> IResult<&str, (String, Vec)> { Ok((rest, (name.to_string(), args))) } +/// Parser de Lambda +fn lambda_expr(input: &str) -> IResult<&str, UncheckedExpr> { + // fn ( Type name, ... ) -> Type { ... } + let (rest, _) = preceded(multispace0, tag("fn"))(input)?; + let (rest, params) = delimited( + preceded(multispace0, tag("(")), + separated_list0( + preceded(multispace0, tag(",")), + map( + tuple(( + preceded(multispace0, type_name), + preceded(nom::character::complete::multispace1, identifier), + )), + |(ty, name)| -> Param { (name.to_string(), ty) }, + ), + ), + preceded(multispace0, tag(")")), + )(rest)?; + let (rest, _) = preceded(multispace0, tag("->"))(rest)?; + let (rest, return_tipo) = preceded(multispace0, type_name)(rest)?; + let (rest, crp) = preceded(multispace0, block_statement)(rest)?; + Ok(( + rest, + wrap(Expr::Lambda { + params, + return_tipo, + crp: Box::new(crp), + }), + )) +} + +/// Parser de lista de args +fn arg_list(input: &str) -> IResult<&str, Vec> { + delimited( + preceded(multispace0, tag("(")), + separated_list0(preceded(multispace0, tag(",")), preceded(multispace0, expression)), + preceded(multispace0, tag(")")), + )(input) +} + /// Atom: literal, call, array literal, identifier, or parenthesized expression. fn atom(input: &str) -> IResult<&str, UncheckedExpr> { alt(( + lambda_expr, map(literal, |l| wrap(Expr::Literal(l.into()))), - map(parse_call, |(name, args)| wrap(Expr::Call { name, args })), map( delimited( preceded(multispace0, char('[')), @@ -94,6 +136,21 @@ fn atom(input: &str) -> IResult<&str, UncheckedExpr> { fn primary(input: &str) -> IResult<&str, UncheckedExpr> { let (mut rest, mut acc) = atom(input)?; loop { + if let Ok((r, args)) = arg_list(rest) { + acc = if let Expr::Ident(name) = &acc.exp { + wrap(Expr::Call { + name: name.clone(), + args, + }) + } else { + wrap(Expr::CallExpr { + chmd: Box::new(acc), + args, + }) + }; + rest = r; + continue; + } let index_parse = delimited( preceded(multispace0, char('[')), preceded(multispace0, expression), diff --git a/src/parser/functions.rs b/src/parser/functions.rs index 845a59c..3d0cf0e 100644 --- a/src/parser/functions.rs +++ b/src/parser/functions.rs @@ -39,6 +39,7 @@ pub fn type_name(input: &str) -> IResult<&str, Type> { preceded( multispace0, alt(( + fun_type, // 2D arrays must be tried before 1D (longer prefix first) map(tag("int[][]"), |_| Type::Array(Box::new(Type::Array(Box::new(Type::Int))))), map(tag("float[][]"), |_| Type::Array(Box::new(Type::Array(Box::new(Type::Float))))), @@ -57,6 +58,36 @@ pub fn type_name(input: &str) -> IResult<&str, Type> { )(input) } +/// Parse a function type: `fn(T1, T2, ...) -> Ret`. +/// +/// This parser must reject lambdas like: +/// `fn(int x) -> int { return x; }` +fn fun_type(input: &str) -> IResult<&str, Type> { + let (rest, _) = preceded(multispace0, tag("fn"))(input)?; + + let (rest, params) = delimited( + preceded(multispace0, tag("(")), + separated_list0( + preceded(multispace0, tag(",")), + preceded(multispace0, type_name), + ), + preceded(multispace0, tag(")")), + )(rest)?; + + let (rest, _) = preceded(multispace0, tag("->"))(rest)?; + let (rest, ret) = preceded(multispace0, type_name)(rest)?; + + // avoid confusing function types with lambdas + if rest.trim_start().starts_with("{") { + return Err(nom::Err::Error(nom::error::Error::new( + rest, + nom::error::ErrorKind::Tag, + ))); + } + + Ok((rest, Type::Fun(params, Box::new(ret)))) +} + /// Parse a typed parameter (C-style): `Type name`. fn param(input: &str) -> IResult<&str, (String, Type)> { map( @@ -79,6 +110,7 @@ pub fn fun_decl(input: &str) -> IResult<&str, UncheckedFunDecl> { preceded(multispace0, tag(")")), )(rest)?; let (rest, body) = preceded(multispace0, statement)(rest)?; + Ok(( rest, FunDecl { @@ -88,4 +120,4 @@ pub fn fun_decl(input: &str) -> IResult<&str, UncheckedFunDecl> { body: Box::new(body), }, )) -} +} \ No newline at end of file diff --git a/src/parser/identifiers.rs b/src/parser/identifiers.rs index f6bdecb..5691461 100644 --- a/src/parser/identifiers.rs +++ b/src/parser/identifiers.rs @@ -28,7 +28,7 @@ use nom::{ }; /// Reserved words: boolean literals and type names. -const RESERVED: &[&str] = &["true", "false", "int", "float", "bool", "str", "void", "return"]; +const RESERVED: &[&str] = &["true", "false", "int", "float", "bool", "str", "void", "return", "fn"]; /// Parse an identifier (variable name). /// Must start with letter or underscore; subsequent chars may be letter, digit, or underscore. diff --git a/src/parser/statements.rs b/src/parser/statements.rs index 9dcfef5..888ca5e 100644 --- a/src/parser/statements.rs +++ b/src/parser/statements.rs @@ -82,21 +82,27 @@ fn return_statement(input: &str) -> IResult<&str, UncheckedStmt> { Ok((rest, wrap(Statement::Return(expr.map(Box::new))))) } -/// Parse a variable declaration: `Type ident = expr ;`. Must come before assignment. +/// Parse a variable declaration: `Type ident [= expr] ;`. +/// Must come before assignment. fn decl_statement(input: &str) -> IResult<&str, UncheckedStmt> { map( tuple(( type_name, preceded(nom::character::complete::multispace1, identifier), - preceded(multispace0, nom::bytes::complete::tag("=")), - preceded(multispace0, expression), + opt(preceded( + multispace0, + preceded( + nom::bytes::complete::tag("="), + preceded(multispace0, expression), + ), + )), preceded(multispace0, char(';')), )), - |(ty, name, _, init, _)| { + |(ty, name, init, _)| { wrap(Statement::Decl { name: name.to_string(), ty, - init: Box::new(init), + init: init.map(Box::new), }) }, )(input) @@ -104,7 +110,7 @@ fn decl_statement(input: &str) -> IResult<&str, UncheckedStmt> { /// Parse a block statement: `{ stmt* }`. /// Each statement inside the block carries its own terminator (`;` or `}`). -fn block_statement(input: &str) -> IResult<&str, UncheckedStmt> { +pub(crate) fn block_statement(input: &str) -> IResult<&str, UncheckedStmt> { map( delimited( preceded(multispace0, char('{')), @@ -206,4 +212,4 @@ pub fn assignment(input: &str) -> IResult<&str, UncheckedStmt> { }) }, )(input) -} +} \ No newline at end of file diff --git a/src/semantic/type_checker.rs b/src/semantic/type_checker.rs index e17c4b3..9783e18 100644 --- a/src/semantic/type_checker.rs +++ b/src/semantic/type_checker.rs @@ -44,7 +44,7 @@ //! Centralising compatibility logic here means all callers (declaration, //! assignment, call-argument checking) share one consistent definition. -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use crate::environment::Environment; use crate::ir::ast::{ @@ -152,18 +152,29 @@ fn type_check_stmt( if env.get(name).is_some() { return Err(TypeError::new(format!("redeclaration of variable: {}", name))); } - let init_checked = type_check_expr_to_typed(init, env)?; - if !types_compatible(&init_checked.ty, ty) { - return Err(TypeError::new(format!( - "declaration of {}: expected {:?}, got {:?}", - name, ty, init_checked.ty - ))); - } + let init_checked = if let Some(init_expr) = init { + let checked = type_check_expr_to_typed(init_expr, env)?; + if !types_compatible(&checked.ty, ty) { + return Err(TypeError::new(format!( + "declaration of {}: expected {:?}, got {:?}", + name, ty, checked.ty + ))); + } + Some(Box::new(checked)) + } else { + if !matches!(ty, Type::Fun(_, _)) { + return Err(TypeError::new(format!( + "variable '{}' must be initialized", + name + ))); + } + None + }; env.declare(name.clone(), ty.clone()); Statement::Decl { name: name.clone(), ty: ty.clone(), - init: Box::new(init_checked), + init: init_checked, } } Statement::Assign { target, value } => { @@ -268,32 +279,16 @@ fn check_call( args: &[CheckedExpr], env: &Environment, ) -> Result<(), TypeError> { - match env.get(name) { - Some(Type::Fun(param_tys, _)) => { - if args.len() != param_tys.len() { - return Err(TypeError::new(format!( - "function '{}' expects {} arguments, got {}", - name, - param_tys.len(), - args.len() - ))); - } - for (i, (arg, param_ty)) in args.iter().zip(param_tys.iter()).enumerate() { - if !types_compatible(&arg.ty, param_ty) { - return Err(TypeError::new(format!( - "argument {} to {}: expected {:?}, got {:?}", - i + 1, - name, - param_ty, - arg.ty - ))); - } - } - Ok(()) - } - Some(_) => Err(TypeError::new(format!("'{}' is not a function", name))), - None => Err(TypeError::new(format!("undefined function: {}", name))), - } + let callee_ty = env + .get(name) + .ok_or_else(|| TypeError::new(format!("undefined function: {}", name)))?; + + type_of_function_call( + callee_ty, + args, + &format!("function '{}'", name), + ) + .map(|_| ()) } fn type_check_assign_target( @@ -333,6 +328,83 @@ fn type_check_assign_target( } } +fn type_of_function_call( + callee_ty: &Type, + args: &[CheckedExpr], + label: &str, +) -> Result { + match callee_ty { + Type::Fun(param_tys, return_ty) => { + if args.len() != param_tys.len() { + return Err(TypeError::new(format!( + "{} expects {} arguments, got {}", + label, + param_tys.len(), + args.len() + ))); + } + + for (i, (arg, param_ty)) in args.iter().zip(param_tys.iter()).enumerate() { + if !types_compatible(&arg.ty, param_ty) { + return Err(TypeError::new(format!( + "argument {} to {}: expected {:?}, got {:?}", + i + 1, + label, + param_ty, + arg.ty + ))); + } + } + + Ok((**return_ty).clone()) + } + + other => { + if label == "function value" { + Err(TypeError::new("attempting to call a non-function value")) + } + else { + Err(TypeError::new(format!( + "{} is not a function, got {:?}", + label, other + ))) + } + } + } +} + +fn type_check_lambda_body( + params: &[(String, Type)], + return_tipo: &Type, + crp: &UncheckedStmt, + env: &Environment, +) -> Result { + let mut lambda_env = Environment::::new(); + lambda_env.restore(env.snapshot()); + + let mut seen_params = HashSet::new(); + + for (name, ty) in params.iter() { + if ty == &Type::Unit { + return Err(TypeError::new(format!( + "lambda parameter '{}' cannot have type void", + name + ))); + } + + if !seen_params.insert(name.clone()) { + return Err(TypeError::new(format!( + "duplicate lambda parameter: {}", + name + ))); + } + + lambda_env.declare(name.clone(), ty.clone()); + } + + type_check_stmt(crp, &mut lambda_env, return_tipo) +} + fn type_check_expr_to_typed( e: &UncheckedExpr, env: &Environment, @@ -401,12 +473,48 @@ fn type_check_expr_inner( )), Expr::Call { name, args } => { let args_checked: Result, _> = - args.iter().map(|a| type_check_expr_to_typed(a, env)).collect(); + args.iter().map(|a| type_check_expr_to_typed(a, env)).collect(); + + let args_checked = args_checked?; + + check_call(name, &args_checked, env)?; + Ok(Expr::Call { name: name.clone(), - args: args_checked?, + args: args_checked, + }) + } + + Expr::CallExpr { chmd, args } => { + let chmd_checked = type_check_expr_to_typed(chmd, env)?; + + let args_checked: Result, _> = + args.iter().map(|a| type_check_expr_to_typed(a, env)).collect(); + + let args_checked = args_checked?; + + type_of_function_call(&chmd_checked.ty, &args_checked, "function value")?; + + Ok(Expr::CallExpr { + chmd: Box::new(chmd_checked), + args: args_checked, + }) + } + + Expr::Lambda { + params, + return_tipo, + crp, + } => { + let body_checked = type_check_lambda_body(params, return_tipo, crp, env)?; + + Ok(Expr::Lambda { + params: params.clone(), + return_tipo: return_tipo.clone(), + crp: Box::new(body_checked), }) } + Expr::ArrayLit(elems) => { let elems_checked: Result, _> = elems.iter().map(|e| type_check_expr_to_typed(e, env)).collect(); @@ -426,10 +534,6 @@ fn type_check_expr( match &e.exp { Expr::Literal(l) => Ok(literal_type(l)), Expr::Ident(name) => match env.get(name) { - Some(Type::Fun(_, _)) => Err(TypeError::new(format!( - "cannot use function '{}' as a value", - name - ))), Some(ty) => Ok(ty.clone()), None => Err(TypeError::new(format!("undeclared variable: {}", name))), }, @@ -488,36 +592,43 @@ fn type_check_expr( Expr::Call { name, args } => { let args_checked: Result, _> = args.iter().map(|a| type_check_expr_to_typed(a, env)).collect(); + let args_checked = args_checked?; - match env.get(name) { - Some(Type::Fun(param_tys, return_ty)) => { - if args_checked.len() != param_tys.len() { - return Err(TypeError::new(format!( - "function '{}' expects {} arguments, got {}", - name, - param_tys.len(), - args_checked.len() - ))); - } - for (i, (arg, param_ty)) in - args_checked.iter().zip(param_tys.iter()).enumerate() - { - if !types_compatible(&arg.ty, param_ty) { - return Err(TypeError::new(format!( - "argument {} to {}: expected {:?}, got {:?}", - i + 1, - name, - param_ty, - arg.ty - ))); - } - } - Ok((**return_ty).clone()) - } - Some(_) => Err(TypeError::new(format!("'{}' is not a function", name))), - None => Err(TypeError::new(format!("undefined function: {}", name))), - } + + let callee_ty = env + .get(name) + .ok_or_else(|| TypeError::new(format!("undefined function: {}", name)))?; + + type_of_function_call( + callee_ty, + &args_checked, + &format!("function '{}'", name), + ) + } + + Expr::CallExpr { chmd, args } => { + let chmd_checked = type_check_expr_to_typed(chmd, env)?; + + let args_checked: Result, _> = + args.iter().map(|a| type_check_expr_to_typed(a, env)).collect(); + + let args_checked = args_checked?; + + type_of_function_call(&chmd_checked.ty, &args_checked, "function value") } + + Expr::Lambda { + params, + return_tipo, + crp, + } => { + type_check_lambda_body(params, return_tipo, crp, env)?; + + let param_tys = params.iter().map(|(_, ty)| ty.clone()).collect(); + + Ok(Type::Fun(param_tys, Box::new(return_tipo.clone()))) + } + Expr::ArrayLit(elems) => { if elems.is_empty() { return Err(TypeError::new("empty array literal needs type annotation")); @@ -580,6 +691,19 @@ fn types_compatible(a: &Type, b: &Type) -> bool { | (Type::Unit, Type::Unit) => true, (Type::Int, Type::Float) | (Type::Float, Type::Int) => true, (Type::Array(a), Type::Array(b)) => types_compatible(a, b), + (Type::Fun(params_a, ret_a), Type::Fun(params_b, ret_b)) => { + if params_a.len() != params_b.len() { + return false; + } + + for (a, b) in params_a.iter().zip(params_b.iter()) { + if a != b { + return false; + } + } + + ret_a == ret_b + }, _ => false, } } diff --git a/tests/interpreter.rs b/tests/interpreter.rs index 51696c9..cfbf22d 100644 --- a/tests/interpreter.rs +++ b/tests/interpreter.rs @@ -256,3 +256,62 @@ fn test_stdlib_pow_float_args() { "#; assert!(run(src).is_ok(), "{}", run(src).unwrap_err()); } + +// LAMBDA + +/// Executa o pipeline completo: Parser -> Type Check -> Interpret +fn run_full(src: &str) -> Result<(), String> { + let (_, unchecked) = program(src).map_err(|e| format!("Parser Error: {:?}", e))?; + + // Passando &unchecked (Referência) + let checked = type_check(&unchecked).map_err(|e| format!("Type Error: {}", e.message))?; + + // Execução + interpret(&checked).map_err(|e| format!("Runtime Error: {}", e.message))?; + + Ok(()) +} + +#[test] +fn test_exec_lambda_simple_math() { + let src = r#" + void main() { + fn(int) -> int dobrar = fn(int n) -> int { return n * 2; }; + print(dobrar(10)); + } + "#; + + let result = run_full(src); + assert!(result.is_ok(), "Falha na execução da lambda básica. Erro: {:?}", result.err()); +} + +#[test] +fn test_exec_closure_capture_success() { + // Este teste valida se o seu interpretador injeta o HashMap 'captured' no ambiente + let src = r#" + void main() { + int base = 100; + fn(int) -> int somar = fn(int n) -> int { return n + base; }; + print(somar(50)); + } + "#; + + let result = run_full(src); + assert!( + result.is_ok(), + "A Closure falhou! Provavelmente a variável 'base' não foi injetada no ambiente da lambda. Erro: {:?}", + result.err() + ); +} + +#[test] +fn test_exec_immediate_lambda_call() { + // Testa (fn(x)->x{...})(val) + let src = r#" + void main() { + int x = (fn(int a, int b) -> int { return a + b; })(5, 5); + print(x); + } + "#; + assert!(run_full(src).is_ok()); +} \ No newline at end of file diff --git a/tests/interpreter_closure.rs b/tests/interpreter_closure.rs new file mode 100644 index 0000000..ed642cc --- /dev/null +++ b/tests/interpreter_closure.rs @@ -0,0 +1,139 @@ +use mini_c::{interpreter::interpret, parser::program, semantic::type_check}; + +fn run(src: &str) -> Result<(), String> { + let (_, unchecked) = program(src).map_err(|e| format!("parse error: {:?}", e))?; + let checked = type_check(&unchecked).map_err(|e| format!("type error: {}", e.message))?; + interpret(&checked).map_err(|e| format!("runtime error: {}", e.message)) +} + +#[test] +fn closure_snapshot_captures_creation_time_value() { + let src = r#" + void main() { + int y = 10; + fn(int) -> int f = fn(int x) -> int { return x + y; }; + y = 20; + print(f(1)); + } + "#; + + assert!(run(src).is_ok()); +} + +#[test] +fn closures_capture_different_snapshots() { + let src = r#" + void main() { + int y = 10; + + fn(int) -> int f = + fn(int x) -> int { + return x + y; + }; + + y = 20; + + fn(int) -> int g = + fn(int x) -> int { + return x + y; + }; + + print(f(1)); + print(g(1)); + } + "#; + + assert!(run(src).is_ok()); +} + +#[test] +fn closure_snapshot_captures_multiple_variables() { + let src = r#" + void main() { + int a = 2; + int b = 3; + + fn(int) -> int f = fn(int x) -> int { + return x + a + b; + }; + + a = 100; + b = 200; + print(f(1)); + } + "#; + + assert!(run(src).is_ok()); +} + +#[test] +fn closure_called_through_variable_is_accepted() { + let src = r#" + void main() { + fn(int) -> int inc = fn(int x) -> int { return x + 1; }; + print(inc(41)); + } + "#; + + assert!(run(src).is_ok()); +} + +#[test] +fn closure_can_be_called_multiple_times() { + let src = r#" + void main() { + int y = 10; + + fn(int) -> int f = fn(int x) -> int { + return x + y; + }; + + print(f(1)); + print(f(2)); + print(f(3)); + } + "#; + + assert!(run(src).is_ok()); +} + +#[test] +fn caller_environment_is_restored_after_closure_call() { + let src = r#" + void main() { + int y = 10; + + fn(int) -> int f = fn(int x) -> int { + return x + y; + }; + + print(f(1)); + y = 50; + print(y); + } + "#; + + assert!(run(src).is_ok()); +} + +#[test] +fn nested_closure_still_uses_captured_snapshot() { + let src = r#" + void main() { + int y = 10; + + fn(int) -> int make = fn(int x) -> int { + return x + y; + }; + + fn(int) -> int call = fn(int z) -> int { + return make(z); + }; + + y = 20; + print(call(1)); + } + "#; + + assert!(run(src).is_ok()); +} \ No newline at end of file diff --git a/tests/interpreter_fn_value.rs b/tests/interpreter_fn_value.rs new file mode 100644 index 0000000..d5e2881 --- /dev/null +++ b/tests/interpreter_fn_value.rs @@ -0,0 +1,32 @@ +use mini_c::interpreter::interpret; +use mini_c::parser::program; +use mini_c::semantic::type_check; + +fn run(src: &str) -> Result<(), String> { + let (_, unchecked) = program(src).map_err(|e| format!("parse error: {:?}", e))?; + let checked = type_check(&unchecked).map_err(|e| format!("type error: {}", e.message))?; + interpret(&checked).map_err(|e| format!("runtime error: {}", e.message)) +} + +#[test] +fn test_fn_value_runtime() { + let src = r#" + void main() { + fn(int) -> int f = fn(int x) -> int { return x * 2; }; + print(f(21)); + } + "#; + assert!(run(src).is_ok(), "expected runtime to succeed"); +} + +#[test] +fn test_fn_value_call_through_variable() { + let src = r#" + void main() { + fn(int) -> int f = fn(int x) -> int { return x * 2; }; + fn(int) -> int g = f; + print(g(21)); + } + "#; + assert!(run(src).is_ok(), "expected function call through variable to succeed"); +} \ No newline at end of file diff --git a/tests/parser.rs b/tests/parser.rs index eca6640..5583172 100644 --- a/tests/parser.rs +++ b/tests/parser.rs @@ -340,6 +340,7 @@ fn test_decl_statement() { assert!(matches!(result.stmt, Statement::Decl { ref name, ref ty, .. } if name == "x" && ty == &Type::Int)); if let Statement::Decl { ref init, .. } = result.stmt { + let init = init.as_ref().expect("Decl init should be present"); assert_eq!(init.exp, Expr::Literal(Literal::Int(42))); } @@ -658,7 +659,7 @@ fn test_multidimensional_indexed_assignment() { #[test] fn test_nested_index() { let result = expression("arr[i][j]").unwrap().1; - assert!(matches!(result.exp, Expr::Index { ref base, ref index } + assert!(matches!(result.exp, Expr::Index { base: _, ref index } if matches!(index.exp, Expr::Ident(ref s) if s == "j"))); if let Expr::Index { ref base, .. } = result.exp { assert!(matches!(base.exp, Expr::Index { ref base, ref index } diff --git a/tests/type_checker.rs b/tests/type_checker.rs index 3357161..3b6d8dd 100644 --- a/tests/type_checker.rs +++ b/tests/type_checker.rs @@ -27,6 +27,7 @@ fn test_type_check_int_float_coercion() { let prog = result.unwrap(); let main_fn = prog.functions.iter().find(|f| f.name == "main").unwrap(); if let mini_c::ir::ast::Statement::Decl { ref init, .. } = main_fn.body.stmt { + let init = init.as_ref().expect("Decl init should be present"); assert_eq!(init.ty, Type::Float); } else { panic!("expected Decl"); @@ -202,3 +203,26 @@ fn test_type_check_print_wrong_arity() { let result = parse_and_type_check("void main() { print(1, 2); }"); assert!(result.is_err(), "expected arity error for print(1, 2)"); } + +// LAMBDA + +#[test] +fn test_type_check_lambda_capture() { + let src = r#" + void main() { + int externo = 10; + fn(int) -> int f = fn(int x) -> int { return x + externo; }; + } + "#; + let (_, unchecked) = program(src).expect("Falha no parser"); + // Usando &unchecked para evitar erro de mismatched types (E0308) + let result = type_check(&unchecked); + assert!(result.is_ok(), "Type Checker não encontrou a variável capturada 'externo'"); +} + +#[test] +fn test_type_check_void_lambda() { + let src = "void main() { fn() -> void f = fn() -> void { return; }; }"; + let (_, unchecked) = program(src).expect("Falha no parser"); + assert!(type_check(&unchecked).is_ok()); +} \ No newline at end of file diff --git a/tests/type_fn_assign.rs b/tests/type_fn_assign.rs new file mode 100644 index 0000000..d3b880d --- /dev/null +++ b/tests/type_fn_assign.rs @@ -0,0 +1,28 @@ +use mini_c::{parser::program, semantic::type_check}; + +fn type_check_src(src: &str) -> Result<(), String> { + let (_, unchecked) = program(src).map_err(|e| format!("parse error: {:?}", e))?; + type_check(&unchecked).map(|_| ()).map_err(|e| format!("type error: {}", e.message)) +} + +#[test] +fn test_assign_compatible_fn_type() { + let src = r#" + void main() { + fn(int) -> int f = fn(int x) -> int { return x; }; + int r = f(5); + } + "#; + assert!(type_check_src(src).is_ok(), "expected type-check to succeed"); +} + +#[test] +fn test_assign_incompatible_fn_type() { + let src = r#" + void main() { + fn(float) -> int f = fn(int x) -> int { return x; }; + } + "#; + let res = type_check_src(src); + assert!(res.is_err(), "expected type error for incompatible function assignment"); +} diff --git a/tests/type_function_compat.rs b/tests/type_function_compat.rs new file mode 100644 index 0000000..e2997b0 --- /dev/null +++ b/tests/type_function_compat.rs @@ -0,0 +1,131 @@ +use mini_c::{interpreter::interpret, parser::program, semantic::type_check}; + +fn type_check_src(src: &str) -> Result<(), String> { + let (_, unchecked) = program(src).map_err(|e| format!("parse error: {:?}", e))?; + type_check(&unchecked).map(|_| ()).map_err(|e| format!("type error: {}", e.message)) +} + +fn run_src(src: &str) -> Result<(), String> { + let (_, unchecked) = program(src).map_err(|e| format!("parse error: {:?}", e))?; + let checked = type_check(&unchecked).map_err(|e| format!("type error: {}", e.message))?; + interpret(&checked).map_err(|e| format!("runtime error: {}", e.message)) +} + +// --------------------------------------------------------------------------- +// ✅ CASOS VÁLIDOS +// --------------------------------------------------------------------------- + +#[test] +fn test_valid_uninitialized_fn_decl() { + let src = r#" + void main() { + fn(int) -> int f; + } + "#; + assert!(type_check_src(src).is_ok(), "Expected valid uninitialized fn declaration to pass type checker"); +} + +#[test] +fn test_valid_fn_assign_and_call() { + let src = r#" + void main() { + fn(int) -> int f; + f = fn(int x) -> int { return x * 3; }; + int r = f(4); + print(r); + } + "#; + assert!(run_src(src).is_ok(), "Expected valid function assignment and call to succeed at runtime"); +} + +// --------------------------------------------------------------------------- +// ❌ CASOS INVÁLIDOS +// --------------------------------------------------------------------------- + +#[test] +fn test_invalid_uninitialized_int_decl() { + let src = r#" + void main() { + int x; + } + "#; + let res = type_check_src(src); + assert!(res.is_err(), "Expected type checker to reject uninitialized int variable"); + assert!(res.unwrap_err().contains("must be initialized")); +} + +#[test] +fn test_incompatible_fn_type_assignment() { + let src = r#" + void main() { + fn(float) -> int f; + f = fn(int x) -> int { return x; }; + } + "#; + let res = type_check_src(src); + assert!(res.is_err(), "Expected type checker to reject incompatible function type assignment"); + assert!(res.unwrap_err().contains("assignment to f")); +} + +#[test] +fn test_incompatible_fn_type_declaration() { + let src = r#" + void main() { + fn(float) -> int f = fn(int x) -> int { return x; }; + } + "#; + let res = type_check_src(src); + assert!(res.is_err(), "Expected type checker to reject incompatible function type initialization"); + assert!(res.unwrap_err().contains("declaration of f")); +} + +#[test] +fn test_call_non_function() { + let src = r#" + void main() { + int x = 42; + x(5); + } + "#; + let res = type_check_src(src); + assert!(res.is_err(), "Expected type checker to reject calling a non-function value"); + assert!(res.unwrap_err().contains("is not a function")); +} + +#[test] +fn test_call_non_function_literal() { + let src = r#" + void main() { + int y = 1(5); + } + "#; + let res = type_check_src(src); + assert!(res.is_err(), "Expected type checker to reject calling a non-function literal"); + assert!(res.unwrap_err().contains("attempting to call a non-function value")); +} + +#[test] +fn test_wrong_argument_count() { + let src = r#" + void main() { + fn(int) -> int f = fn(int x) -> int { return x; }; + f(1, 2); + } + "#; + let res = type_check_src(src); + assert!(res.is_err(), "Expected type checker to reject calling function with wrong number of arguments"); + assert!(res.unwrap_err().contains("expects 1 arguments, got 2")); +} + +#[test] +fn test_uninitialized_fn_call_runtime_error() { + let src = r#" + void main() { + fn(int) -> int f; + f(5); + } + "#; + let res = run_src(src); + assert!(res.is_err(), "Expected calling an uninitialized function variable to fail at runtime"); + assert!(res.unwrap_err().contains("is not a function")); +} diff --git a/tests/type_function_integration_tests.rs b/tests/type_function_integration_tests.rs new file mode 100644 index 0000000..83050fd --- /dev/null +++ b/tests/type_function_integration_tests.rs @@ -0,0 +1,139 @@ +use mini_c::parser::functions::{fun_decl, type_name}; +use mini_c::parser::statements::statement; + +// ========================= +// ✅ DECLARAÇÕES COM TYPE::FUN +// ========================= + +#[test] +fn test_statement_decl_with_function_type_no_init() { + let input = "fn(int) -> int f;"; + + let result = statement(input); + assert!(result.is_ok()); + + let (rest, _) = result.unwrap(); + assert_eq!(rest.trim(), ""); +} + +#[test] +fn test_statement_decl_with_nested_function_type_no_init() { + let input = "fn(fn(int) -> int) -> int g;"; + + let result = statement(input); + assert!(result.is_ok()); + + let (rest, _) = result.unwrap(); + assert_eq!(rest.trim(), ""); +} + +#[test] +fn test_statement_decl_with_function_type_and_init() { + let input = "fn(int) -> int f = foo;"; + + let result = statement(input); + assert!(result.is_ok()); + + let (rest, _) = result.unwrap(); + assert_eq!(rest.trim(), ""); +} + +// ========================= +// ✅ FUNÇÕES COM PARÂMETROS TYPE::FUN +// ========================= + +#[test] +fn test_fun_decl_param_with_function_type() { + let input = "int apply(fn(int) -> int f, int x) { return x; }"; + + let result = fun_decl(input); + assert!(result.is_ok()); + + let (rest, _) = result.unwrap(); + assert_eq!(rest.trim(), ""); +} + +#[test] +fn test_fun_decl_nested_function_type_in_param() { + let input = "int h(fn(fn(int) -> int) -> int f) { return 0; }"; + + let result = fun_decl(input); + assert!(result.is_ok()); + + let (rest, _) = result.unwrap(); + assert_eq!(rest.trim(), ""); +} + +// ========================= +// ✅ FUNÇÕES COM RETORNO TYPE::FUN +// ========================= + +#[test] +fn test_fun_decl_returning_function_type() { + let input = "fn(int) -> int make() { return foo; }"; + + let result = fun_decl(input); + assert!(result.is_ok()); + + let (rest, _) = result.unwrap(); + assert_eq!(rest.trim(), ""); +} + +#[test] +fn test_fun_decl_returning_nested_function_type() { + let input = "fn(int) -> fn(int) -> int make() { return foo; }"; + + let result = fun_decl(input); + assert!(result.is_ok()); + + let (rest, _) = result.unwrap(); + assert_eq!(rest.trim(), ""); +} + +// ========================= +// ✅ ESPAÇOS / ROBUSTEZ +// ========================= + +#[test] +fn test_fun_decl_with_many_spaces() { + let input = " + int apply( + fn(int) -> int f, + int x + ) + { + return x; + } + "; + + let result = fun_decl(input); + assert!(result.is_ok()); + + let (rest, _) = result.unwrap(); + assert_eq!(rest.trim(), ""); +} + +#[test] +fn test_statement_decl_with_many_spaces() { + let input = " + fn( int , float ) -> bool g ; + "; + + let result = statement(input); + assert!(result.is_ok()); + + let (rest, _) = result.unwrap(); + assert_eq!(rest.trim(), ""); +} + +// ========================= +// ❌ NÃO CONFUNDIR COM LAMBDA +// ========================= + +#[test] +fn test_type_name_still_rejects_lambda() { + let input = "fn(int x) -> int { return x; }"; + + let result = type_name(input); + assert!(result.is_err()); +} \ No newline at end of file diff --git a/tests/type_function_tests.rs b/tests/type_function_tests.rs new file mode 100644 index 0000000..1bc3ef3 --- /dev/null +++ b/tests/type_function_tests.rs @@ -0,0 +1,200 @@ +use mini_c::parser::functions::type_name; +use mini_c::ir::ast::Type; + +// ========================= +// ✅ CASOS VÁLIDOS +// ========================= + +#[test] +fn test_fun_type_simple() { + let input = "fn(int) -> int"; + + let result = type_name(input); + assert!(result.is_ok()); + + let (rest, ty): (&str, Type) = result.unwrap(); + assert_eq!(rest.trim(), ""); + + match ty { + Type::Fun(params, ret) => { + assert_eq!(params.len(), 1); + assert_eq!(params[0], Type::Int); + assert_eq!(*ret, Type::Int); + } + _ => panic!("Expected function type"), + } +} + +#[test] +fn test_fun_type_multiple_params() { + let input = "fn(int, float) -> bool"; + + let (_, ty) = type_name(input).unwrap(); + + match ty { + Type::Fun(params, ret) => { + assert_eq!(params.len(), 2); + assert_eq!(params[0], Type::Int); + assert_eq!(params[1], Type::Float); + assert_eq!(*ret, Type::Bool); + } + _ => panic!("Expected function type"), + } +} + +#[test] +fn test_fun_type_no_params() { + let input = "fn() -> int"; + + let (_, ty) = type_name(input).unwrap(); + + match ty { + Type::Fun(params, ret) => { + assert_eq!(params.len(), 0); + assert_eq!(*ret, Type::Int); + } + _ => panic!("Expected function type"), + } +} + +#[test] +fn test_fun_type_nested() { + let input = "fn(fn(int) -> int) -> int"; + + let (_, ty) = type_name(input).unwrap(); + + match ty { + Type::Fun(params, ret) => { + assert_eq!(params.len(), 1); + + match ¶ms[0] { + Type::Fun(inner_params, inner_ret) => { + assert_eq!(inner_params.len(), 1); + assert_eq!(inner_params[0], Type::Int); + assert_eq!(**inner_ret, Type::Int); + } + _ => panic!("Expected nested function type"), + } + + assert_eq!(*ret, Type::Int); + } + _ => panic!("Expected function type"), + } +} + +#[test] +fn test_fun_type_spaces_everywhere() { + let input = " fn( int , float ) -> bool "; + + let (_, ty) = type_name(input).unwrap(); + + match ty { + Type::Fun(params, ret) => { + assert_eq!(params.len(), 2); + assert_eq!(params[0], Type::Int); + assert_eq!(params[1], Type::Float); + assert_eq!(*ret, Type::Bool); + } + _ => panic!("Expected function type"), + } +} + +// ========================= +// ❌ CASOS INVÁLIDOS +// ========================= + +#[test] +fn test_lambda_not_type() { + let input = "fn(int x) -> int { return x; }"; + + let result = type_name(input); + + assert!(result.is_err()); +} + +#[test] +fn test_invalid_comma() { + let input = "fn(,) -> int"; + + let result = type_name(input); + + assert!(result.is_err()); +} + +#[test] +fn test_missing_arrow() { + let input = "fn(int) int"; + + let result = type_name(input); + + assert!(result.is_err()); +} + +#[test] +fn test_unclosed_paren() { + let input = "fn(int -> int"; + + let result = type_name(input); + + assert!(result.is_err()); +} + +#[test] +fn test_missing_return_type() { + let input = "fn(int) ->"; + + let result = type_name(input); + + assert!(result.is_err()); +} + +#[test] +fn test_random_garbage() { + let input = "fn(abc) => ???"; + + let result = type_name(input); + + assert!(result.is_err()); +} + +// ========================= +// 🔥 EDGE CASES +// ========================= + +#[test] +fn test_deeply_nested_functions() { + let input = "fn(fn(fn(int) -> int) -> int) -> int"; + + let result = type_name(input); + + assert!(result.is_ok()); +} + +#[test] +fn test_trailing_input() { + let input = "fn(int) -> int extra"; + + let (rest, _): (&str, Type) = type_name(input).unwrap(); + + assert!(rest.trim().starts_with("extra")); +} + +// ========================= +// 💡 ROBUSTEZ +// ========================= + +#[test] +fn test_many_spaces_and_newlines() { + let input = " + fn( + int, + float + ) + -> + bool + "; + + let result = type_name(input); + + assert!(result.is_ok()); +} \ No newline at end of file diff --git a/tests/type_lambda_call.rs b/tests/type_lambda_call.rs new file mode 100644 index 0000000..845f1bd --- /dev/null +++ b/tests/type_lambda_call.rs @@ -0,0 +1,107 @@ +use mini_c::{parser::program, semantic::type_check}; + +fn type_check_src(src: &str) -> Result<(), String> { + let (rest, unchecked) = program(src) + .map_err(|e| format!("parse error: {:?}", e))?; + + if !rest.trim().is_empty() { + return Err(format!("unparsed input: {:?}", rest)); + } + + type_check(&unchecked) + .map(|_| ()) + .map_err(|e| format!("type error: {}", e.message)) +} + +#[test] +fn test_lambda_type_check_ok() { + let src = r#" + void main() { + fn(int) -> int f = fn(int x) -> int { + return x * 2; + }; + + int r = f(3); + } + "#; + + assert!( + type_check_src(src).is_ok(), + "lambda should type-check" + ); +} + +#[test] +fn test_lambda_call_wrong_arg() { + let src = r#" + void main() { + fn(int) -> int f = fn(int x) -> int { + return x; + }; + + f(true); + } + "#; + + let res = type_check_src(src); + + assert!( + res.is_err(), + "expected type error when calling with wrong arg type" + ); +} + +#[test] +fn test_lambda_wrong_return_type() { + let src = r#" + void main() { + fn(int) -> int f = fn(int x) -> int { + return true; + }; + } + "#; + + let res = type_check_src(src); + + assert!( + res.is_err(), + "expected type error when lambda returns bool instead of int" + ); +} + +#[test] +fn test_lambda_call_wrong_number_of_args() { + let src = r#" + void main() { + fn(int) -> int f = fn(int x) -> int { + return x; + }; + + f(); + } + "#; + + let res = type_check_src(src); + + assert!( + res.is_err(), + "expected type error when calling function with wrong number of args" + ); +} + +#[test] +fn test_call_non_function_value() { + let src = r#" + void main() { + int x = 10; + x(1); + } + "#; + + let res = type_check_src(src); + + assert!( + res.is_err(), + "expected type error when calling a non-function value" + ); +} \ No newline at end of file