diff --git a/Cargo.lock b/Cargo.lock index 880dfbd..86310ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -131,6 +131,7 @@ dependencies = [ "chumsky", "hir", "lexer", + "typecheck", ] [[package]] @@ -163,6 +164,7 @@ dependencies = [ "serde", "serde_derive", "toml", + "typecheck", ] [[package]] @@ -361,6 +363,13 @@ dependencies = [ "serde", ] +[[package]] +name = "typecheck" +version = "0.1.0" +dependencies = [ + "hir", +] + [[package]] name = "unicode-xid" version = "0.2.2" diff --git a/Cargo.toml b/Cargo.toml index 1a1ffc4..1edecab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,5 +5,6 @@ members = [ "crates/parser", "crates/diagnostic", "crates/hir", + "crates/typecheck", "crates/codegen", -] \ No newline at end of file +] diff --git a/crates/codegen/src/ts.rs b/crates/codegen/src/ts.rs index 0115f5f..1e111b7 100644 --- a/crates/codegen/src/ts.rs +++ b/crates/codegen/src/ts.rs @@ -31,7 +31,7 @@ impl Codegen { macro_rules! semicolon { () => { if should_gen_semicolon { ";" } else { "" } }; } match ir { - IRKind::Define { public, name, type_hint, value, mutable } => { + IRKind::Define { public, name, type_hint, value, mutable, .. } => { format!( "{} {} v_{}: {} = {}{}\n", if *public { "export" } else { "" }, @@ -43,7 +43,7 @@ impl Codegen { ) }, - IRKind::Call { name, args } => { + IRKind::Call { name, args, .. } => { format!( "f_{}({}){}", name, @@ -57,7 +57,7 @@ impl Codegen { ) }, - IRKind::Intrinsic { name, args } => { + IRKind::Intrinsic { name, args, .. } => { match name.as_str() { "write" => { format!("write({}){}\n" , self.gen_ir(&args[0], false), semicolon!()) }, "write_file" => { format!("writeFile({}, {}){}\n", self.gen_ir(&args[0], false), self.gen_ir(&args[1], false), semicolon!()) }, @@ -71,7 +71,7 @@ impl Codegen { } }, - IRKind::Fun { public, name, return_type_hint, args, body } => { + IRKind::Fun { public, name, return_type_hint, args, body, .. } => { let args = args .iter() .map(|arg| format!("v_{}: {}", arg.0, arg.1)) @@ -87,14 +87,14 @@ impl Codegen { ) }, - IRKind::Return { value } => { + IRKind::Return { value, .. } => { format!( "return {};\n", self.gen_ir(value, false) ) }, - IRKind::Do { body } => { + IRKind::Do { body, .. } => { let mut out = "{\n".to_string(); for expr in body { out.push_str(&self.gen_ir(&expr, true)); @@ -103,7 +103,7 @@ impl Codegen { out }, - IRKind::If { cond, body, else_body } => { + IRKind::If { cond, body, else_body, .. } => { format!( "if ({}) {{\n{}}} else {{\n{}}}\n", self.gen_ir(cond, true), @@ -112,7 +112,7 @@ impl Codegen { ) }, - IRKind::Case { cond, cases, default } => { + IRKind::Case { cond, cases, default, .. } => { format!( "switch ({}) {{\n{}{}\n}}\n", self.gen_ir(cond, true), @@ -131,11 +131,11 @@ impl Codegen { ) }, - IRKind::Unary { op, right } => { + IRKind::Unary { op, right, .. } => { format!("{}{}", op, self.gen_ir(right, false)) }, - IRKind::Binary { left, op, right } => { + IRKind::Binary { left, op, right, .. } => { format!("{} {} {}", self.gen_ir(left, false), op, self.gen_ir(right, false)) }, diff --git a/crates/diagnostic/Cargo.toml b/crates/diagnostic/Cargo.toml index 8dfae66..af43f71 100644 --- a/crates/diagnostic/Cargo.toml +++ b/crates/diagnostic/Cargo.toml @@ -9,4 +9,5 @@ edition = "2021" chumsky = "0.8.0" ariadne = "0.1.5" lexer = { path = "../lexer" } -hir = { path = "../hir" } \ No newline at end of file +hir = { path = "../hir" } +typecheck = { path = "../typecheck" } \ No newline at end of file diff --git a/crates/diagnostic/src/lib.rs b/crates/diagnostic/src/lib.rs index 3811794..eb02dd0 100644 --- a/crates/diagnostic/src/lib.rs +++ b/crates/diagnostic/src/lib.rs @@ -12,6 +12,7 @@ pub enum Kind { LexError(Simple), ParseError(Simple), LoweringError(hir::LoweringError), + TypecheckError(typecheck::TypecheckError), } impl Diagnostics { @@ -37,6 +38,10 @@ impl Diagnostics { self.errors.push(Kind::LoweringError(error)); } + pub fn add_typecheck_error(&mut self, error: typecheck::TypecheckError) { + self.errors.push(Kind::TypecheckError(error)); + } + pub fn display(&self, src: String) { let lex_error = self.errors.iter().filter_map(|kind| match kind { Kind::LexError(error) => Some(error.clone()), // Using clone() to remove reference @@ -120,6 +125,11 @@ impl Diagnostics { Kind::LoweringError(error) => Some(error.clone()), _ => None, }); + let typecheck_error = self.errors.iter().filter_map(|kind| match kind { + Kind::TypecheckError(error) => Some(error.clone()), + _ => None, + }); + // TODO: so many .iter(), maybe collapse them into one? lower_error.into_iter() .for_each(|e| { @@ -138,13 +148,27 @@ impl Diagnostics { .with_color(Color::Red) ); - if let Some(note) = &e.note { - report - .with_note(note) - .finish().print(Source::from(&src)).unwrap(); - } else { - report.finish().print(Source::from(&src)).unwrap(); - } + report.finish().print(Source::from(&src)).unwrap(); }); + + typecheck_error.into_iter() + .for_each(|e| { + let span = &e.span; + let message = &e.kind; + + let report = Report::build(ReportKind::Error, (), span.start) + .with_message( + format!("{}", message) + ) + .with_label( + Label::new(span.clone()) + .with_message( + format!("{}", message) + ) + .with_color(Color::Red) + ); + + report.finish().print(Source::from(&src)).unwrap(); + }) } } diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 657fe77..0f25e50 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -29,31 +29,62 @@ impl std::fmt::Display for Value { pub enum IRKind { Value { value: Value }, Vector { values: Vec }, - Unary { op: String, right: Box }, - Binary { op: String, left: Box, right: Box }, - Call { name: String, args: Vec }, - Intrinsic { name: String, args: Vec }, + + Unary { + op: String, + right: Box, + span: Range + }, + Binary { + op: String, + left: Box, + right: Box, + span: Range + }, + Call { + name: String, + args: Vec, + span: Range + }, + Intrinsic { + name: String, + args: Vec, + span: Range + }, Define { public: bool, name: String, type_hint: String, value: Box, - mutable: bool + mutable: bool, + span: Range, }, Fun { public: bool, name: String, return_type_hint: String, args: Vec<(String, String)>, - body: Box + body: Box, + span: Range, }, - If { cond: Box, body: Box, else_body: Box }, - Case { cond: Box, cases: Vec<(Box, Box)>, default: Box }, - Do { body: Vec }, + If { + cond: Box, + body: Box, + else_body: Box, + span: Range, + }, + Case { + cond: Box, + cases: Vec<(Box, Box)>, + default: Box, + span: Range, + }, + Do { body: Vec, span: Range }, - Return { value: Box }, + Return { value: Box, span: Range }, + // Error { message: String, note: Option, span: Range }, } #[derive(Debug)] @@ -62,70 +93,10 @@ pub struct IR { pub span: Range } -// S-Expression displaying for IR -// Used for debugging (run `compile` with `-a`) -impl std::fmt::Display for IRKind { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - IRKind::Define { ref public, ref name, ref type_hint, ref value, ref mutable } => { - write!(f, "(let {} {} {} {} {})", - if *public { "export" } else { "" }, - name, - type_hint, - value, - if *mutable { "mut" } else { "" }, - ) - }, - IRKind::Fun { ref public, ref name, ref return_type_hint, ref args, ref body } => { - write!(f, "(fun{} {} :{} [{}] {})", - if *public { " export" } else { "" }, - name, - return_type_hint, - args.iter().map(|(name, type_hint)| format!(":{} {}", name, type_hint)).collect::>().join(" "), - body, - ) - }, - IRKind::Call { ref name, ref args } => { - write!(f, "({} {})", name, args.iter().map(|arg| arg.to_string()).collect::>().join(" ")) - }, - IRKind::Intrinsic { ref name, ref args } => { - write!(f, "(@{} {})", name, args.iter().map(|arg| arg.to_string()).collect::>().join(" ")) - }, - IRKind::Do { ref body } => { - write!(f, "(do {})", body.iter().map(|expr| expr.to_string()).collect::>().join(" ")) - }, - IRKind::If { ref cond, ref body, ref else_body } => { - write!(f, "(if {} {} {})", cond, body, else_body) - }, - IRKind::Case { ref cond, ref cases, ref default } => { - write!(f, "(case {} {} {})", cond, cases.iter().map(|(cond, body)| format!("({} {})", cond, body)).collect::>().join(" "), default) - }, - IRKind::Unary { ref op, ref right } => { - write!(f, "({} {})", op, right) - }, - IRKind::Binary { ref op, ref left, ref right } => { - write!(f, "({} {} {})", op, left, right) - }, - IRKind::Value { ref value } => { - write!(f, "{}", value) - }, - IRKind::Vector { ref values } => { - write!(f, "[{}]", values.iter().map(|expr| expr.to_string()).collect::>().join(" ")) - }, - IRKind::Return { ref value } => { - write!(f, "(return {})", value) - } - #[allow(unreachable_patterns)] - _ => { dbg!(self); unreachable!() } - } - } -} - #[derive(Debug)] pub struct LoweringError { pub span: Range, pub message: String, - pub note: Option, } pub fn ast_to_ir(ast: Vec<(Expr, Range)>) -> (Vec, Vec) { @@ -153,13 +124,31 @@ macro_rules! if_err_return { }; } +#[macro_export] +macro_rules! return_ok { + ($value:expr) => { + return (Some($value), None) + }; +} + +#[macro_export] +macro_rules! return_err { + ($value:expr) => { + return (None, Some($value)) + }; +} + pub fn expr_to_ir(expr: &Expr) -> (Option, Option) { match expr { Expr::Unary { op, rhs } => { let rhs_ir = expr_to_ir(&rhs.0); if_err_return!(rhs_ir.1); - return (Some(IRKind::Unary { op: op.to_string(), right: Box::new(rhs_ir.0.unwrap()) }), None); + return_ok!(IRKind::Unary { + op: op.to_string(), + right: Box::new(rhs_ir.0.unwrap()), + span: rhs.1.clone() + }); } Expr::Binary { lhs, op, rhs } => { @@ -169,14 +158,19 @@ pub fn expr_to_ir(expr: &Expr) -> (Option, Option) { let rhs_ir = expr_to_ir(&rhs.0); if_err_return!(rhs_ir.1); - return (Some(IRKind::Binary { op: op.to_string(), left: Box::new(lhs_ir.0.unwrap()), right: Box::new(rhs_ir.0.unwrap()) }), None) + return_ok!(IRKind::Binary { + op: op.to_string(), + left: Box::new(lhs_ir.0.unwrap()), + right: Box::new(rhs_ir.0.unwrap()), + span: lhs.1.start..rhs.1.end + }); }, Expr::Call { name, args } => { - let name = match &name.0 { + let lname = match &name.0 { Expr::Identifier(s) => s.clone(), // Should never happen because the parser should have caught this - _ => return (None, Some(LoweringError { span: name.1.clone(), message: "Expected identifier".to_string(), note: None })) + _ => return_err!(LoweringError { span: name.1.clone(), message: "Expected identifier".to_string()}) }; let mut largs = Vec::new(); // `largs` stand for lowered args // Iterate over args @@ -186,8 +180,12 @@ pub fn expr_to_ir(expr: &Expr) -> (Option, Option) { if_err_return!(arg.1); largs.push(arg.0.unwrap()); } - let ir_kind = IRKind::Call { name, args: largs }; - return (Some(ir_kind), None); + + return_ok!(IRKind::Call { + name: lname, + args: largs, + span: name.1.start..args.1.end + }); }, Expr::Pipeline { lhs, rhs } => { @@ -200,7 +198,7 @@ pub fn expr_to_ir(expr: &Expr) -> (Option, Option) { let cname = match &name.0 { Expr::Identifier(s) => s.clone(), // Should never happen because the parser should have caught this - _ => return (None, Some(LoweringError { span: name.1.clone(), message: "Expected identifier".to_string(), note: None })) + _ => return_err!(LoweringError { span: name.1.clone(), message: "Expected identifier".to_string()}) }; // Get all the `Hole` indexes @@ -213,11 +211,10 @@ pub fn expr_to_ir(expr: &Expr) -> (Option, Option) { // If there is no `Hole` in the args then return early if indexes.is_empty() { - return (None, Some(LoweringError { + return_err!(LoweringError { span: rhs.1.clone(), message: "Expected hole in piping".to_string(), - note: None - })); + }); } // Remove the `Hole` from the args @@ -259,50 +256,54 @@ pub fn expr_to_ir(expr: &Expr) -> (Option, Option) { // Match the call to the right IRKind let ir_kind = match new_call.0.unwrap() { - IRKind::Call { .. } => IRKind::Call { name: cname, args: largs }, - IRKind::Intrinsic { .. } => IRKind::Intrinsic { name: cname, args: largs }, + IRKind::Call { .. } => IRKind::Call { + name: cname, + args: largs, + span: name.1.start..args.1.end + }, + IRKind::Intrinsic { .. } => IRKind::Intrinsic { + name: cname, + args: largs, + span: name.1.start..args.1.end + }, _ => unreachable!() }; - return (Some(ir_kind), None); + return_ok!(ir_kind); }, - _ => return (None, Some(LoweringError { + _ => return_err!(LoweringError { span: rhs.1.clone(), - message: "Expected call".to_string(), - note: None - })), + message: "Expected call".to_string() + }), }; }, Expr::Let { public, name, type_hint, value, mutable } => { - let value = expr_to_ir(&value.0); - if_err_return!(value.1); + let lvalue = expr_to_ir(&value.0); + if_err_return!(lvalue.1); - let value = value.0.unwrap(); - let ir_kind = IRKind::Define { + return_ok!(IRKind::Define { public: *public, - name: name.clone(), - type_hint: gen_type_hint(type_hint), - value: Box::new(value), - mutable: *mutable - }; - - return (Some(ir_kind), None); + name: name.0.clone(), + type_hint: gen_type_hint(&type_hint.0), + value: Box::new(lvalue.0.unwrap()), + mutable: *mutable, + span: value.1.clone() + }); }, Expr::Intrinsic { name, args } => { - let name = match &name.0 { + let lname = match &name.0 { Expr::Identifier(s) => { if INTRINSICS.contains(&s.as_str()) { s.clone() } else { - return (None, Some(LoweringError { + return_err!(LoweringError { span: name.1.clone(), message: format!("Unknown intrinsic: `{}`", s), - note: Some(format!("Did you mean: {}?", closet_intrinsic(s.to_string()))) - })); + }); } } - _ => return (None, Some(LoweringError { span: name.1.clone(), message: "Expected identifier".to_string(), note: None })) + _ => return_err!(LoweringError { span: name.1.clone(), message: "Expected identifier".to_string()}) }; let mut largs = Vec::new(); @@ -314,73 +315,80 @@ pub fn expr_to_ir(expr: &Expr) -> (Option, Option) { if let IRKind::Value{ .. } = larg.0.clone().unwrap() { largs.push(larg.0.clone().unwrap()); } else { - return (None, Some(LoweringError { span: arg.1.clone(), message: "Expected string".to_string(), note: None })) + return_err!(LoweringError { span: arg.1.clone(), message: "Expected string".to_string()}); } } - let ir_kind = IRKind::Intrinsic { name, args: largs }; - return (Some(ir_kind), None); + return_ok!(IRKind::Intrinsic { + name: lname, + args: largs, + span: name.1.start..args.1.end + }); }, Expr::Fun { public, name, type_hint, args, body } => { // Iterate each argument and give it a type hint - let args = args.0.iter().map(|arg| (arg.0.0.clone(), gen_type_hint(&arg.1.0))).collect::>(); + let largs = args.0.iter().map(|arg| (arg.0.0.clone(), gen_type_hint(&arg.1.0))).collect::>(); - let body = expr_to_ir(&body.0); - if_err_return!(body.1); + let lbody = expr_to_ir(&body.0); + if_err_return!(lbody.1); - let body = body.0.unwrap(); - let ir_kind = IRKind::Fun { + return_ok!(IRKind::Fun { public: *public, - name: name.clone(), - return_type_hint: gen_type_hint(type_hint), - args, - body: Box::new(body) - }; - return (Some(ir_kind), None); + name: name.0.clone(), + return_type_hint: gen_type_hint(&type_hint.0), + args: largs, + body: Box::new(lbody.0.unwrap()), + span: name.1.start..body.1.end + }); }, Expr::Return { expr } => { - let expr = expr_to_ir(&expr.0); - if_err_return!(expr.1); + let lexpr = expr_to_ir(&expr.0); + if_err_return!(lexpr.1); - let expr = expr.0.unwrap(); - let ir_kind = IRKind::Return { value: Box::new(expr) }; - return (Some(ir_kind), None); + return_ok!(IRKind::Return { + value: Box::new(lexpr.0.unwrap()), + span: expr.1.clone() + }); }, Expr::Do { body } => { let mut lbody = Vec::new(); - for expr in body { + + for expr in &body.0 { let expr = expr_to_ir(&expr.0); if_err_return!(expr.1); lbody.push(expr.0.unwrap()); - } - let ir_kind = IRKind::Do { body: lbody }; - return (Some(ir_kind), None); + }; + + return_ok!(IRKind::Do { + body: lbody, + span: body.1.clone() + }); }, Expr::If { cond, body, else_body } => { - let cond = expr_to_ir(&cond.0); - if_err_return!(cond.1); + let lcond = expr_to_ir(&cond.0); + if_err_return!(lcond.1); - let body = expr_to_ir(&body.0); - if_err_return!(body.1); + let lbody = expr_to_ir(&body.0); + if_err_return!(lbody.1); - let else_body = expr_to_ir(&else_body.0); - if_err_return!(else_body.1); + let lelse_body = expr_to_ir(&else_body.0); + if_err_return!(lelse_body.1); - let ir_kind = IRKind::If { - cond: Box::new(cond.0.unwrap()), - body: Box::new(body.0.unwrap()), - else_body: Box::new(else_body.0.unwrap()) - }; - return (Some(ir_kind), None); + return_ok!(IRKind::If { + cond: Box::new(lcond.0.unwrap()), + body: Box::new(lbody.0.unwrap()), + else_body: Box::new(lelse_body.0.unwrap()), + span: cond.1.start..else_body.1.end + }); }, Expr::Case { expr, cases, default } => { - let expr = expr_to_ir(&expr.0); - if_err_return!(expr.1); + let lexpr = expr_to_ir(&expr.0); + if_err_return!(lexpr.1); let mut lcases = Vec::new(); for case in &cases.0 { @@ -395,23 +403,23 @@ pub fn expr_to_ir(expr: &Expr) -> (Option, Option) { ); } - let default = expr_to_ir(&default.0); - if_err_return!(default.1); + let ldefault = expr_to_ir(&default.0); + if_err_return!(ldefault.1); - let ir_kind = IRKind::Case { - cond: Box::new(expr.0.unwrap()), + return_ok!(IRKind::Case { + cond: Box::new(lexpr.0.unwrap()), cases: lcases, - default: Box::new(default.0.unwrap()) - }; - return (Some(ir_kind), None); + default: Box::new(ldefault.0.unwrap()), + span: expr.1.start..default.1.end + }); }, // TODO: Handle primitive types error (e.g. overflow) // For now it just leaves the value as is and let the target compiler handle it - Expr::Int(value) => (Some(IRKind::Value { value: Value::Int(*value) }), None), - Expr::Boolean(value) => (Some(IRKind::Value { value: Value::Boolean(*value) }), None), - Expr::String(value) => (Some(IRKind::Value { value: Value::String(value.clone()) }), None), - Expr::Identifier(value) => (Some(IRKind::Value { value: Value::Ident(value.clone()) }), None), + Expr::Int(value) => return_ok!(IRKind::Value { value: Value::Int(*value) }), + Expr::Boolean(value) => return_ok!(IRKind::Value { value: Value::Boolean(*value) }), + Expr::String(value) => return_ok!(IRKind::Value { value: Value::String(value.clone()) }), + Expr::Identifier(value) => return_ok!(IRKind::Value { value: Value::Ident(value.clone()) }), Expr::Vector(values) => { let mut lvalues = Vec::new(); @@ -427,8 +435,7 @@ pub fn expr_to_ir(expr: &Expr) -> (Option, Option) { // Probably will never happen because it is catched in parser Expr::Hole(start, end) => (None, Some(LoweringError { span: *start..*end, - message: "Hole can only be used in piping, it is not allowed here.".to_string(), - note: None + message: "Hole can only be used in piping, it is not allowed here.".to_string() })), _ => { dbg!(expr); todo!() } } diff --git a/crates/main/Cargo.toml b/crates/main/Cargo.toml index 9d207fe..d636802 100644 --- a/crates/main/Cargo.toml +++ b/crates/main/Cargo.toml @@ -18,6 +18,7 @@ lexer = { path = "../lexer" } parser = { path = "../parser" } # Diagnostics +typecheck = { path = "../typecheck" } diagnostic = { path = "../diagnostic" } # Codegen diff --git a/crates/main/src/main.rs b/crates/main/src/main.rs index 1010ecf..90e5044 100644 --- a/crates/main/src/main.rs +++ b/crates/main/src/main.rs @@ -6,6 +6,7 @@ use lexer::lex; use parser::parse; use diagnostic::Diagnostics; use hir::ast_to_ir; +use typecheck::check; use codegen::ts; pub mod args; @@ -52,16 +53,24 @@ fn main() { logif!(0, format!("Parsing took {}ms", start.elapsed().as_millis())); } - // TODO: S-Expr syntax for AST - // if print_ast { log(0, format!("{:#?}", ast)); } - match ast { Some(ast) => { // Convert the AST to HIR let (ir, lowering_error) = ast_to_ir(ast); for err in lowering_error { diagnostics.add_lowering_error(err); } - if print_ast { log(0, format!("IR\n{}", ir.iter().map(|x| format!("{}", x.kind)).collect::>().join("\n\n"))); } + if print_ast { log(0, format!("IR\n{:#?}", ir)); } + + // Typecheck the HIR + match check(&ir) { + Ok(_) => {}, + Err(err) => { + diagnostics.add_typecheck_error(err); + diagnostics.display(src); + logif!(0, "Typechecking failed"); + std::process::exit(1); + } + } // Report lowering errors if any if diagnostics.has_error() { diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index 0c9ee5e..a7b2322 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -18,15 +18,15 @@ pub enum Expr { Let { public: bool, - name: String, - type_hint: String, + name: Spanned, + type_hint: Spanned, value: Box>, mutable: bool, }, Fun { public: bool, - name: String, - type_hint: String, + name: Spanned, + type_hint: Spanned, args: Spanned, Spanned)>>, body: Box> }, @@ -43,7 +43,7 @@ pub enum Expr { default: Box> }, Do { - body: Vec> + body: Spanned>> }, // Hole for positional argument(s) in piping @@ -226,8 +226,8 @@ fn expr_parser() -> impl Parser>, Error = Simple ( Expr::Let { public: public.is_some(), - name: name.0.clone(), - type_hint: type_hint.0, + name: name.clone(), + type_hint, value: Box::new(value.clone()), mutable: mutable.is_some(), }, @@ -256,8 +256,8 @@ fn expr_parser() -> impl Parser>, Error = Simple ( Expr::Fun { public: public.is_some(), - name: name.0.clone(), - type_hint: type_hint.0, + name: name.clone(), + type_hint, args: (args, name.1.clone()), body: Box::new(body.clone()), }, @@ -286,7 +286,7 @@ fn expr_parser() -> impl Parser>, Error = Simple .map_with_span(|body, span| { ( Expr::Do { - body: body.clone(), + body: (body.clone(), span.clone()), }, span, ) diff --git a/crates/typecheck/Cargo.toml b/crates/typecheck/Cargo.toml new file mode 100644 index 0000000..a94b23b --- /dev/null +++ b/crates/typecheck/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "typecheck" +license = "MIT OR Apache-2.0" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +hir = { path = "../hir" } diff --git a/crates/typecheck/src/lib.rs b/crates/typecheck/src/lib.rs new file mode 100644 index 0000000..592e26b --- /dev/null +++ b/crates/typecheck/src/lib.rs @@ -0,0 +1,34 @@ +use hir::{IR, IRKind}; + +#[derive(Debug)] +pub enum TypecheckErrorKind { + DefinitionTypeMismatch { + type_specified: String, + type_found: String, + }, +} + +impl std::fmt::Display for TypecheckErrorKind { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + TypecheckErrorKind::DefinitionTypeMismatch { + type_specified, + type_found, + } => write!( + f, + "expected type `{}`, found type `{}`", + type_specified, type_found + ), + } + } +} + +#[derive(Debug)] +pub struct TypecheckError { + pub kind: TypecheckErrorKind, + pub span: std::ops::Range, +} + +pub fn check(ir: &Vec) -> Result<(), TypecheckError> { + todo!(); +} \ No newline at end of file