diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 7592f59..05e708e 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -1,66 +1,67 @@ -pub mod expr; -pub mod module; - use std::path::Path; -pub use crate::ast::expr::{BinaryOperator, Expr}; -use crate::ast::module::*; -use crate::typing::Type; +pub mod typed; +pub mod untyped; + +#[derive(Debug, PartialEq, Clone)] +pub enum BinaryOperator { + Add, + Sub, + Mul, + Div, + Modulo, + Equal, + NotEqual, +} + +#[derive(Debug, PartialEq, Clone)] +pub enum UnaryOperator { +} pub type Identifier = String; -// XXX: Is this enum actually useful? Is 3:30 AM btw -#[derive(Debug, PartialEq)] -pub enum Ast { - Module(Module), +#[derive(Debug, PartialEq, Clone)] +pub struct ModulePath { + components: Vec, } -#[derive(Debug, PartialEq)] -pub enum Definition { - FunctionDefinition(FunctionDefinition), - //StructDefinition(StructDefinition), +impl std::fmt::Display for ModulePath { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("{}", self.components.join("::"))) + } } -#[derive(Debug, PartialEq)] -pub struct Location { - pub file: Box, +impl From<&Path> for ModulePath { + fn from(path: &Path) -> Self { + let meta = std::fs::metadata(path).unwrap(); + ModulePath { + components: path + .components() + .map(|component| match component { + std::path::Component::Normal(n) => { + if meta.is_file() { + n.to_str().unwrap().split(".").nth(0).unwrap().to_string() + } else if meta.is_dir() { + n.to_str().unwrap().to_string() + } else { + // XXX: symlinks? + unreachable!() + } + } + _ => unreachable!(), + }) + .collect(), + } + } } -#[derive(Debug, PartialEq)] -pub struct FunctionDefinition { - pub name: Identifier, - pub parameters: Vec, - pub return_type: Option, - pub body: Box, - pub line_col: (usize, usize), -} - -#[derive(Debug, PartialEq)] -pub struct Block { - pub statements: Vec, - pub value: Option, -} - -#[derive(Debug, PartialEq)] -pub enum Statement { - DeclareStatement(Identifier, Expr), - AssignStatement(Identifier, Expr), - ReturnStatement(Option), - CallStatement(Call), - UseStatement(ModulePath), - IfStatement(Expr, Block), - WhileStatement(Box, Box), -} - -#[derive(Debug, PartialEq)] -pub struct Call { - pub callee: Expr, - pub args: Vec, -} - -#[derive(Debug, PartialEq)] -pub struct Parameter { - pub name: Identifier, - pub typ: Type, +impl From<&str> for ModulePath { + fn from(string: &str) -> Self { + ModulePath { + components: string.split("::").map(|c| c.to_string()).collect(), + } + } } +#[derive(Eq, PartialEq, Debug)] +pub struct Import(pub String); diff --git a/src/ast/module.rs b/src/ast/module.rs deleted file mode 100644 index 6159c21..0000000 --- a/src/ast/module.rs +++ /dev/null @@ -1,66 +0,0 @@ -use std::path::Path; -use super::Definition; - -#[derive(Debug, PartialEq, Clone)] -pub struct ModulePath { - components: Vec, -} - -impl std::fmt::Display for ModulePath { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("{}", self.components.join("::"))) - } -} - -impl From<&Path> for ModulePath { - fn from(path: &Path) -> Self { - let meta = std::fs::metadata(path).unwrap(); - ModulePath { - components: path - .components() - .map(|component| match component { - std::path::Component::Normal(n) => { - if meta.is_file() { - n.to_str().unwrap().split(".").nth(0).unwrap().to_string() - } else if meta.is_dir() { - n.to_str().unwrap().to_string() - } else { - // XXX: symlinks? - unreachable!() - } - } - _ => unreachable!(), - }) - .collect(), - } - } -} - -impl From<&str> for ModulePath { - fn from(string: &str) -> Self { - ModulePath { - components: string.split("::").map(|c| c.to_string()).collect(), - } - } -} - -type ImportPath = ModulePath; - -#[derive(Debug, PartialEq)] -pub struct Module { - pub file: Option, - pub path: ModulePath, - pub definitions: Vec, - pub imports: Vec, -} - -impl Module { - pub fn new(path: ModulePath) -> Self { - Module { - path, - file: None, - definitions: vec![], - imports: vec![], - } - } -} diff --git a/src/ast/typed/expr.rs b/src/ast/typed/expr.rs new file mode 100644 index 0000000..7588907 --- /dev/null +++ b/src/ast/typed/expr.rs @@ -0,0 +1,67 @@ +use crate::ast::{ + typed::{Block, Call}, + BinaryOperator, UnaryOperator, +}; +use crate::typing::Type; + +#[derive(Debug, PartialEq)] +pub enum Expr { + BinaryExpression { + lhs: Box, + op: BinaryOperator, + rhs: Box, + typ: Type, + }, + UnaryExpression { + op: UnaryOperator, + inner: Box, + }, + Variable { + name: String, + typ: Type, + }, + Call { + call: Box, + typ: Type, + }, + Block { + block: Box, + typ: Type, + }, + /// Last field is either Expr::Block or Expr::IfExpr + IfExpr { + cond: Box, + then_body: Box, + else_body: Box, + typ: Type, + }, + // Literals + UnitLiteral, + BooleanLiteral(bool), + IntegerLiteral(i64), + FloatLiteral(f64), + StringLiteral(String), +} + +impl Expr { + pub fn typ(&self) -> &Type { + match self { + Expr::BinaryExpression { lhs, op, rhs, typ } => typ, + Expr::UnaryExpression { op, inner } => inner.typ(), // XXX: problems will arise here + Expr::Variable { name, typ } => typ, + Expr::Call { call, typ } => typ, + Expr::Block { block, typ } => typ, + Expr::IfExpr { + cond, + then_body, + else_body, + typ, + } => typ, + Expr::UnitLiteral => &Type::Unit, + Expr::BooleanLiteral(_) => &Type::Bool, + Expr::IntegerLiteral(_) => &Type::Int, + Expr::FloatLiteral(_) => &Type::Float, + Expr::StringLiteral(_) => &Type::Str, + } + } +} diff --git a/src/ast/typed/mod.rs b/src/ast/typed/mod.rs new file mode 100644 index 0000000..505ed3a --- /dev/null +++ b/src/ast/typed/mod.rs @@ -0,0 +1,47 @@ +pub mod expr; + +use crate::typing::Type; +use super::{untyped::Parameter, Identifier, Import}; +use expr::Expr; + +#[derive(Debug, PartialEq)] +pub enum Statement { + DeclareStatement(Identifier, Box), + AssignStatement(Identifier, Box), + ReturnStatement(Option), + CallStatement(Box), + UseStatement(Box), + IfStatement(Box, Box), + WhileStatement(Box, Box), +} + +#[derive(Debug, PartialEq)] +pub struct Block { + pub statements: Vec, + pub value: Option, + typ: Type, +} + +impl Block { + #[inline] + pub fn typ(&self) -> Type { + self.typ.clone() + } +} + +#[derive(Debug, PartialEq)] +pub struct FunctionDefinition { + pub name: Identifier, + pub parameters: Vec, + pub return_type: Option, + pub body: Box, + pub line_col: (usize, usize), +} + +#[derive(Debug, PartialEq)] +pub struct Call { + pub callee: Box, + pub args: Vec, + pub typ: Type, +} + diff --git a/src/ast/expr.rs b/src/ast/untyped/expr.rs similarity index 75% rename from src/ast/expr.rs rename to src/ast/untyped/expr.rs index 1ea672e..ff8d1f8 100644 --- a/src/ast/expr.rs +++ b/src/ast/untyped/expr.rs @@ -1,7 +1,13 @@ +use crate::ast::{ + untyped::{Block, Call}, + Identifier, +}; + use crate::ast::*; #[derive(Debug, PartialEq)] pub enum Expr { + UnitLiteral, BinaryExpression(Box, BinaryOperator, Box), Identifier(Identifier), Call(Box), @@ -14,14 +20,3 @@ pub enum Expr { /// Last field is either Expr::Block or Expr::IfExpr IfExpr(Box, Box, Box), } - -#[derive(Debug, PartialEq, Clone)] -pub enum BinaryOperator { - Add, - Sub, - Mul, - Div, - Modulo, - Equal, - NotEqual, -} diff --git a/src/ast/untyped/mod.rs b/src/ast/untyped/mod.rs new file mode 100644 index 0000000..9138679 --- /dev/null +++ b/src/ast/untyped/mod.rs @@ -0,0 +1,61 @@ +pub mod expr; +pub mod module; + +use std::path::Path; + +pub use crate::ast::untyped::expr::Expr; +pub use crate::ast::*; +// TODO: remove all usage of 'Type' in the untyped ast +// (for now it is assumed that anything that parses +// is a Type, but the checking should be done in the typing +// phase) +use crate::typing::Type; + +#[derive(Debug, PartialEq)] +pub enum Definition { + FunctionDefinition(FunctionDefinition), + //StructDefinition(StructDefinition), +} + +#[derive(Debug, PartialEq)] +pub struct Location { + pub file: Box, +} + +#[derive(Debug, PartialEq)] +pub struct FunctionDefinition { + pub name: Identifier, + pub parameters: Vec, + pub return_type: Option, + pub body: Box, + pub line_col: (usize, usize), +} + +#[derive(Debug, PartialEq)] +pub struct Block { + pub statements: Vec, + pub value: Option, +} + +#[derive(Debug, PartialEq)] +pub enum Statement { + DeclareStatement(Identifier, Expr), + AssignStatement(Identifier, Expr), + ReturnStatement(Option), + CallStatement(Call), + UseStatement(Import), + IfStatement(Expr, Block), + WhileStatement(Box, Box), +} + +#[derive(Debug, PartialEq)] +pub struct Call { + pub callee: Box, + pub args: Vec, +} + +#[derive(Debug, PartialEq)] +pub struct Parameter { + pub name: Identifier, + pub typ: Type, +} diff --git a/src/ast/untyped/module.rs b/src/ast/untyped/module.rs new file mode 100644 index 0000000..bf4b54c --- /dev/null +++ b/src/ast/untyped/module.rs @@ -0,0 +1,20 @@ +use super::{Definition, ModulePath, Import}; + +#[derive(Debug, PartialEq)] +pub struct Module { + pub file: Option, + pub path: ModulePath, + pub definitions: Vec, + pub imports: Vec, +} + +impl Module { + pub fn new(path: ModulePath) -> Self { + Module { + path, + file: None, + definitions: vec![], + imports: vec![], + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 851c0bc..89cfa94 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,4 @@ -pub mod ast; +mod ast; +mod typing; +mod jit; +mod parsing; diff --git a/src/main.rs b/src/main.rs index 3e5151c..bfe1518 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ mod typing; use clap::{Parser, Subcommand}; -use crate::ast::module::Module; +use crate::ast::untyped::module::Module; /// Experimental compiler for lila #[derive(Parser, Debug)] diff --git a/src/parsing/backend/pest/mod.rs b/src/parsing/backend/pest/mod.rs index b7d7237..af66de3 100644 --- a/src/parsing/backend/pest/mod.rs +++ b/src/parsing/backend/pest/mod.rs @@ -6,13 +6,14 @@ use pest::iterators::Pair; use pest::pratt_parser::PrattParser; use pest::Parser; -use crate::ast::module::{Module, ModulePath}; -use crate::ast::*; +use crate::ast::untyped::module::Module; +use crate::ast::untyped::*; +use crate::ast::{Import, ModulePath}; use crate::typing::Type; #[derive(pest_derive::Parser)] #[grammar = "parsing/backend/pest/grammar.pest"] -struct KrParser; +struct LilaParser; use lazy_static; lazy_static::lazy_static! { @@ -38,7 +39,7 @@ pub fn parse_file(path: &Path) -> Result> { } pub fn parse_as_module(source: &str, path: ModulePath) -> Result> { - let mut pairs = KrParser::parse(Rule::source_file, &source)?; + let mut pairs = LilaParser::parse(Rule::source_file, &source)?; assert!(pairs.len() == 1); let module = parse_module(pairs.next().unwrap().into_inner().next().unwrap(), path); @@ -59,7 +60,7 @@ pub fn parse_module(pair: Pair, path: ModulePath) -> Module { module.definitions.push(def); } Rule::use_statement => { - let path = parse_import_path(pair.into_inner().next().unwrap()); + let path = parse_import(pair.into_inner().next().unwrap()); module.imports.push(path); } _ => panic!("unexpected rule in source_file: {:?}", pair.as_rule()), @@ -112,8 +113,8 @@ fn parse_statement(pair: Pair) -> Statement { Statement::CallStatement(call) } Rule::use_statement => { - let path = parse_import_path(pair.into_inner().next().unwrap()); - Statement::UseStatement(path) + let import = parse_import(pair.into_inner().next().unwrap()); + Statement::UseStatement(import) } Rule::if_statement => { let mut pairs = pair.into_inner(); @@ -133,8 +134,8 @@ fn parse_statement(pair: Pair) -> Statement { type ImportPath = ModulePath; -fn parse_import_path(pair: Pair) -> ImportPath { - ModulePath::from(pair.as_str()) +fn parse_import(pair: Pair) -> Import { + Import(pair.as_str().to_string()) } fn parse_call(pair: Pair) -> Call { @@ -147,7 +148,10 @@ fn parse_call(pair: Pair) -> Call { .into_inner() .map(parse_expression) .collect(); - Call { callee, args } + Call { + callee: Box::new(callee), + args, + } } fn parse_expression(pair: Pair) -> Expr { diff --git a/src/parsing/mod.rs b/src/parsing/mod.rs index 56e5061..e7f2cd5 100644 --- a/src/parsing/mod.rs +++ b/src/parsing/mod.rs @@ -1,5 +1,4 @@ mod backend; - -pub use self::backend::pest::{parse_file, parse_module}; - mod tests; + +pub use self::backend::pest::{parse_file, parse_module, parse_as_module}; diff --git a/src/parsing/tests.rs b/src/parsing/tests.rs index bc655a3..7bac09b 100644 --- a/src/parsing/tests.rs +++ b/src/parsing/tests.rs @@ -2,8 +2,9 @@ fn test_addition_function() { use crate::parsing::backend::pest::parse_as_module; use crate::{ - ast::module::{Module, ModulePath}, - ast::*, + ast::untyped::module::Module, + ast::untyped::*, + ast::ModulePath, typing::Type, }; diff --git a/src/typing/error.rs b/src/typing/error.rs new file mode 100644 index 0000000..04b98ae --- /dev/null +++ b/src/typing/error.rs @@ -0,0 +1,92 @@ +use crate::typing::{BinaryOperator, Identifier, ModulePath, Type, TypeContext}; + +#[derive(Debug)] +pub struct TypeError { + file: Option, + module: ModulePath, + function: Option, + kind: TypeErrorKind, +} + +impl std::fmt::Display for TypeError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Error\n")?; + if let Some(path) = &self.file { + f.write_fmt(format_args!(" in file {}\n", path.display()))?; + } + f.write_fmt(format_args!(" in module {}\n", self.module))?; + if let Some(name) = &self.function { + f.write_fmt(format_args!(" in function {}\n", name))?; + } + f.write_fmt(format_args!("{:#?}", self.kind))?; + Ok(()) + } +} + +#[derive(Default)] +pub struct TypeErrorBuilder { + file: Option, + module: Option, + function: Option, + kind: Option, +} + +impl TypeError { + pub fn builder() -> TypeErrorBuilder { + TypeErrorBuilder::default() + } +} + +impl TypeErrorBuilder { + pub fn context(mut self, ctx: &TypeContext) -> Self { + self.file = ctx.file.clone(); + self.module = Some(ctx.module.clone()); + self.function = ctx.function.clone(); + self + } + + pub fn kind(mut self, kind: TypeErrorKind) -> Self { + self.kind = Some(kind); + self + } + + pub fn build(self) -> TypeError { + TypeError { + file: self.file, + module: self.module.expect("TypeError builder is missing module"), + function: self.function, + kind: self.kind.expect("TypeError builder is missing kind"), + } + } +} + +#[derive(Debug)] +pub enum TypeErrorKind { + InvalidBinaryOperator { + operator: BinaryOperator, + lht: Type, + rht: Type, + }, + BlockTypeDoesNotMatchFunctionType { + block_type: Type, + function_type: Type, + }, + ReturnTypeDoesNotMatchFunctionType { + function_type: Type, + return_type: Type, + }, + UnknownIdentifier { + identifier: String, + }, + AssignmentMismatch { + lht: Type, + rht: Type, + }, + AssignUndeclared, + VariableRedeclaration, + ReturnStatementsMismatch, + UnknownFunctionCalled(Identifier), + WrongFunctionArguments, + ConditionIsNotBool, + IfElseMismatch, +} diff --git a/src/typing/mod.rs b/src/typing/mod.rs index ce39328..2c442a1 100644 --- a/src/typing/mod.rs +++ b/src/typing/mod.rs @@ -1,9 +1,11 @@ use std::collections::HashMap; -use crate::ast::{ - module::{Module, ModulePath}, - *, -}; +use crate::ast::untyped::*; +use crate::ast::untyped::module::Module; +use crate::ast::ModulePath; + +mod error; +use crate::typing::error::{TypeError, TypeErrorKind}; #[derive(Debug, PartialEq, Clone)] pub enum Type { @@ -25,22 +27,22 @@ impl From<&str> for Type { } } -impl FunctionDefinition { +impl untyped::FunctionDefinition { fn signature(&self) -> (Vec, Type) { - let return_type = self.return_type.unwrap_or(Type::Unit); - let params_types = self.parameters.iter().map(|p| p.typ).collect(); + let return_type = self.return_type.clone().unwrap_or(Type::Unit); + let params_types = self.parameters.iter().map(|p| p.typ.clone()).collect(); (params_types, return_type) } } impl Module { pub fn type_check(&self) -> Result<(), TypeError> { - let mut ctx = TypeContext::new(self.path); + let mut ctx = TypeContext::new(self.path.clone()); ctx.file = self.file.clone(); // Register all function signatures for Definition::FunctionDefinition(func) in &self.definitions { - if let Some(previous) = ctx.functions.insert(func.name.clone(), func.signature()) { + if let Some(_previous) = ctx.functions.insert(func.name.clone(), func.signature()) { todo!("handle redefinition of function or identical function names across different files"); } } @@ -50,103 +52,13 @@ impl Module { // Type-check the function bodies for Definition::FunctionDefinition(func) in &self.definitions { func.typ(&mut ctx)?; + ctx.variables.clear(); } Ok(()) } } -#[derive(Debug)] -pub struct TypeError { - file: Option, - module: ModulePath, - function: Option, - kind: TypeErrorKind, -} - -impl std::fmt::Display for TypeError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("Error\n")?; - if let Some(path) = &self.file { - f.write_fmt(format_args!(" in file {}\n", path.display()))?; - } - f.write_fmt(format_args!(" in module {}\n", self.module))?; - if let Some(name) = &self.function { - f.write_fmt(format_args!(" in function {}\n", name))?; - } - f.write_fmt(format_args!("{:#?}", self.kind))?; - Ok(()) - } -} - -#[derive(Default)] -struct TypeErrorBuilder { - file: Option, - module: Option, - function: Option, - kind: Option, -} - -impl TypeError { - fn builder() -> TypeErrorBuilder { - TypeErrorBuilder::default() - } -} - -impl TypeErrorBuilder { - fn context(mut self, ctx: &TypeContext) -> Self { - self.file = ctx.file.clone(); - self.module = Some(ctx.module.clone()); - self.function = ctx.function.clone(); - self - } - - fn kind(mut self, kind: TypeErrorKind) -> Self { - self.kind = Some(kind); - self - } - - fn build(self) -> TypeError { - TypeError { - file: self.file, - module: self.module.expect("TypeError builder is missing module"), - function: self.function, - kind: self.kind.expect("TypeError builder is missing kind"), - } - } -} - -#[derive(Debug)] -pub enum TypeErrorKind { - InvalidBinaryOperator { - operator: BinaryOperator, - lht: Type, - rht: Type, - }, - BlockTypeDoesNotMatchFunctionType { - block_type: Type, - function_type: Type, - }, - ReturnTypeDoesNotMatchFunctionType { - function_type: Type, - return_type: Type, - }, - UnknownIdentifier { - identifier: String, - }, - AssignmentMismatch { - lht: Type, - rht: Type, - }, - AssignUndeclared, - VariableRedeclaration, - ReturnStatementsMismatch, - UnknownFunctionCalled(Identifier), - WrongFunctionArguments, - ConditionIsNotBool, - IfElseMismatch, -} - pub struct TypeContext { pub file: Option, pub module: ModulePath, @@ -157,36 +69,37 @@ pub struct TypeContext { impl TypeContext { pub fn new(path: ModulePath) -> Self { - TypeContext { + let builtin_functions = + HashMap::from([(String::from("println"), (vec![Type::Str], Type::Unit))]); + + Self { file: None, module: path, function: None, - functions: Default::default(), + functions: builtin_functions, variables: Default::default(), } } } /// Trait for nodes which have a deducible type. -pub trait Typ { +pub trait TypeCheck { /// Try to resolve the type of the node. fn typ(&self, ctx: &mut TypeContext) -> Result; } -impl Typ for FunctionDefinition { +impl TypeCheck for FunctionDefinition { fn typ(&self, ctx: &mut TypeContext) -> Result { - let func = self; + ctx.function = Some(self.name.clone()); - ctx.function = Some(func.name.clone()); - - for param in &func.parameters { + for param in &self.parameters { ctx.variables.insert(param.name.clone(), param.typ.clone()); } - let body_type = &func.body.typ(ctx)?; + let body_type = &self.body.typ(ctx)?; // If the return type is not specified, it is unit. - let func_return_type = match &func.return_type { + let func_return_type = match &self.return_type { Some(typ) => typ, None => &Type::Unit, }; @@ -203,7 +116,7 @@ impl Typ for FunctionDefinition { } // Check coherence with return statements. - for statement in &func.body.statements { + for statement in &self.body.statements { if let Statement::ReturnStatement(value) = statement { let ret_type = match value { Some(expr) => expr.typ(ctx)?, @@ -225,7 +138,7 @@ impl Typ for FunctionDefinition { } } -impl Typ for Block { +impl TypeCheck for Block { fn typ(&self, ctx: &mut TypeContext) -> Result { let mut return_typ: Option = None; @@ -320,9 +233,9 @@ impl Typ for Block { } } -impl Typ for Call { +impl TypeCheck for Call { fn typ(&self, ctx: &mut TypeContext) -> Result { - match &self.callee { + match &*self.callee { Expr::Identifier(ident) => { let signature = match ctx.functions.get(ident) { Some(sgn) => sgn.clone(), @@ -356,7 +269,7 @@ impl Typ for Call { } } -impl Typ for Expr { +impl TypeCheck for Expr { fn typ(&self, ctx: &mut TypeContext) -> Result { match self { Expr::Identifier(identifier) => { @@ -426,6 +339,7 @@ impl Typ for Expr { } }, Expr::StringLiteral(_) => Ok(Type::Str), + Expr::UnitLiteral => Ok(Type::Unit), Expr::Call(call) => call.typ(ctx), Expr::Block(block) => block.typ(ctx), Expr::IfExpr(cond, true_block, else_value) => { @@ -450,3 +364,115 @@ impl Typ for Expr { } } } + +struct Typed { + inner: T, + typ: Type, +} + +trait IntoTyped { + fn into_typed(self: Self, ctx: &mut TypeContext) -> Result, TypeError>; +} + +impl IntoTyped for Block { + fn into_typed(self: Block, ctx: &mut TypeContext) -> Result, TypeError> { + let mut return_typ: Option = None; + + // Check declarations and assignments. + for statement in &self.statements { + match statement { + Statement::DeclareStatement(ident, expr) => { + let typ = expr.typ(ctx)?; + if let Some(_typ) = ctx.variables.insert(ident.clone(), typ) { + // XXX: Shadowing? (illegal for now) + return Err(TypeError::builder() + .context(ctx) + .kind(TypeErrorKind::VariableRedeclaration) + .build()); + } + } + + Statement::AssignStatement(ident, expr) => { + let rhs_typ = expr.typ(ctx)?; + let Some(lhs_typ) = ctx.variables.get(ident) else { + return Err(TypeError::builder() + .context(ctx) + .kind(TypeErrorKind::AssignUndeclared) + .build()); + }; + + // Ensure same type on both sides. + if rhs_typ != *lhs_typ { + return Err(TypeError::builder() + .context(ctx) + .kind(TypeErrorKind::AssignmentMismatch { + lht: lhs_typ.clone(), + rht: rhs_typ.clone(), + }) + .build()); + } + } + + Statement::ReturnStatement(maybe_expr) => { + let expr_typ = if let Some(expr) = maybe_expr { + expr.typ(ctx)? + } else { + Type::Unit + }; + if let Some(typ) = &return_typ { + if expr_typ != *typ { + return Err(TypeError::builder() + .context(ctx) + .kind(TypeErrorKind::ReturnStatementsMismatch) + .build()); + } + } else { + return_typ = Some(expr_typ); + } + } + + Statement::CallStatement(call) => { + call.typ(ctx)?; + } + + Statement::UseStatement(_path) => { + // TODO: import the signatures (and types) + } + + Statement::IfStatement(cond, block) => { + if cond.typ(ctx)? != Type::Bool { + return Err(TypeError::builder() + .context(ctx) + .kind(TypeErrorKind::ConditionIsNotBool) + .build()); + } + block.typ(ctx)?; + } + + Statement::WhileStatement(cond, block) => { + if cond.typ(ctx)? != Type::Bool { + return Err(TypeError::builder() + .context(ctx) + .kind(TypeErrorKind::ConditionIsNotBool) + .build()); + } + block.typ(ctx)?; + } + } + } + + // Check if there is an expression at the end of the block. + let typ = if let Some(expr) = &self.value { + expr.typ(ctx)? + } else { + Type::Unit + }; + + Ok(Typed { inner: self, typ }) + + // TODO/FIXME: find a way to return `return_typ` so that the + // top-level block (the function) can check if this return type + // (and eventually those from other block) matches the type of + // the function. + } +}