diff --git a/src/check.rs b/src/check.rs index eddeff9fa..7b9b704e9 100644 --- a/src/check.rs +++ b/src/check.rs @@ -3,6 +3,7 @@ use crate::{ codegen::ProgramData, lexer::LexicalError, tokens::Token, + type_analysis::TypeError, }; use annotate_snippets::{ display_list::{DisplayList, FormatOptions}, @@ -97,7 +98,6 @@ pub fn print_error(source: &str, err: ParseError) { line_start: 1, fold: true, origin: None, - annotations: vec![SourceAnnotation { label: "invalid token", annotation_type: AnnotationType::Error, @@ -126,14 +126,14 @@ pub fn print_error(source: &str, err: ParseError) { }), footer: vec![], slices: vec![Slice { - source: dbg!(source), + source: source, line_start: 1, fold: false, origin: None, annotations: vec![SourceAnnotation { label: "invalid token (lexical error)", annotation_type: AnnotationType::Error, - range: dbg!((range.start, range.end)), + range: (range.start, range.end), }], }], opt: FormatOptions { @@ -147,3 +147,67 @@ pub fn print_error(source: &str, err: ParseError) { }, }; } + +pub fn print_type_error(source: &str, err: TypeError) { + dbg!(&err); + match err { + TypeError::Mismatch { + found, + expected, + span, + } => { + let snippet = Snippet { + title: Some(Annotation { + id: None, + label: Some("type mismatch"), + annotation_type: AnnotationType::Error, + }), + footer: vec![], + slices: vec![Slice { + source, + line_start: 1, + fold: false, + origin: None, + annotations: vec![SourceAnnotation { + label: "type mismatch", + annotation_type: AnnotationType::Error, + range: span, + }], + }], + opt: FormatOptions { + color: true, + ..Default::default() + }, + }; + let dl = DisplayList::from(snippet); + println!("{dl}"); + } + TypeError::UndeclaredVariable { name, span } => { + let snippet = Snippet { + title: Some(Annotation { + id: None, + label: Some("undeclared variable"), + annotation_type: AnnotationType::Error, + }), + footer: vec![], + slices: vec![Slice { + source, + line_start: 1, + fold: false, + origin: None, + annotations: vec![SourceAnnotation { + label: "undeclared variable", + annotation_type: AnnotationType::Error, + range: span, + }], + }], + opt: FormatOptions { + color: true, + ..Default::default() + }, + }; + let dl = DisplayList::from(snippet); + println!("{dl}"); + } + }; +} diff --git a/src/codegen.rs b/src/codegen.rs index 15f34d8b4..88739450d 100644 --- a/src/codegen.rs +++ b/src/codegen.rs @@ -178,12 +178,12 @@ impl<'ctx> CodeGen<'ctx> { let args_types: Vec> = args_types.into_iter().map(|t| t.into()).collect_vec(); - let (fn_type, ret_type) = match &function.return_type { + let fn_type = match &function.return_type { Some(id) => { let return_type = self.get_llvm_type(id)?; - (return_type.fn_type(&args_types, false), Some(id.clone())) + return_type.fn_type(&args_types, false) } - None => (self.context.void_type().fn_type(&args_types, false), None), + None => self.context.void_type().fn_type(&args_types, false), }; self.module.add_function(&function.name, fn_type, None); @@ -447,6 +447,8 @@ impl<'ctx> CodeGen<'ctx> { .compile_expression(rhs, variables, scope_info)? .expect("should have result"); + assert_eq!(lhs.get_type(), rhs.get_type(), "type mismatch"); + let lhs = lhs.into_int_value(); let rhs = rhs.into_int_value(); diff --git a/src/main.rs b/src/main.rs index ae4df5d60..3ca42dc33 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,12 +3,15 @@ use check::print_error; use clap::{Parser, Subcommand}; use codegen::ProgramData; -use color_eyre::Result; use inkwell::{context::Context, execution_engine::JitFunction, OptimizationLevel}; use lalrpop_util::lalrpop_mod; use std::{fs, path::PathBuf, println}; -use crate::{ast::Program, check::Check, lexer::Lexer}; +use crate::{ + ast::Program, + check::{print_type_error, Check}, + lexer::Lexer, +}; pub mod ast; pub mod check; @@ -91,7 +94,7 @@ fn check_program(program: &ProgramData, ast: &ast::Program) -> bool { error_count == 0 } -fn main() -> Result<()> { +fn main() -> color_eyre::Result<()> { color_eyre::install()?; tracing_subscriber::fmt::init(); let args = Args::parse(); @@ -102,7 +105,10 @@ fn main() -> Result<()> { let lexer = Lexer::new(code.as_str()); let parser = grammar::ProgramParser::new(); let mut ast = parser.parse(lexer)?; - type_analysis::type_inference(&mut ast); + if let Err(e) = type_analysis::type_check(&mut ast) { + print_type_error(&code, e); + return Ok(()); + } let program = ProgramData::new(&input, &code); check_program(&program, &ast); } @@ -112,7 +118,10 @@ fn main() -> Result<()> { let parser = grammar::ProgramParser::new(); match parser.parse(lexer) { Ok(mut ast) => { - type_analysis::type_inference(&mut ast); + if let Err(e) = type_analysis::type_check(&mut ast) { + print_type_error(&code, e); + return Ok(()); + } println!("{ast:#?}"); } Err(e) => { @@ -130,7 +139,10 @@ fn main() -> Result<()> { let lexer = Lexer::new(code.as_str()); let parser = grammar::ProgramParser::new(); let mut ast: Program = parser.parse(lexer)?; - type_analysis::type_inference(&mut ast); + if let Err(e) = type_analysis::type_check(&mut ast) { + print_type_error(&code, e); + return Ok(()); + } let program = ProgramData::new(&input, &code); @@ -156,7 +168,11 @@ fn main() -> Result<()> { let code = fs::read_to_string(&input)?; let lexer = Lexer::new(&code[..]); let parser = grammar::ProgramParser::new(); - let ast = parser.parse(lexer).unwrap(); + let mut ast: Program = parser.parse(lexer)?; + if let Err(e) = type_analysis::type_check(&mut ast) { + print_type_error(&code, e); + return Ok(()); + } let program = ProgramData::new(&input, &code); diff --git a/src/type_analysis.rs b/src/type_analysis.rs index 271ed112d..5777c5b4c 100644 --- a/src/type_analysis.rs +++ b/src/type_analysis.rs @@ -2,21 +2,30 @@ use std::collections::{HashMap, HashSet}; use crate::ast::{self, Expression, Function, Statement, TypeExp}; +#[derive(Debug, Clone)] +pub enum TypeError { + Mismatch { + found: TypeExp, + expected: TypeExp, + span: (usize, usize), + }, + UndeclaredVariable { + name: String, + span: (usize, usize), + }, +} + #[derive(Debug, Clone, Default)] struct Storage { structs: HashMap>, functions: HashMap, } -// problem with scopes, -// let x = 2; -// let x = 2i64; - type ScopeMap = HashMap>>; // this works, but need to find a way to store the found info + handle literal integer types (or not?) // maybe use scope ids -pub fn type_inference(ast: &mut ast::Program) { +pub fn type_check(ast: &mut ast::Program) -> Result<(), TypeError> { let mut storage = Storage::default(); // gather global constructs first @@ -40,8 +49,6 @@ pub fn type_inference(ast: &mut ast::Program) { } } - dbg!(&storage); - for statement in ast.statements.iter_mut() { if let Statement::Function(function) = statement { let mut scope_vars: ScopeMap = HashMap::new(); @@ -52,7 +59,7 @@ pub fn type_inference(ast: &mut ast::Program) { let func_info = function.clone(); let (new_scope_vars, _) = - type_inference_scope(&mut function.body, &scope_vars, &func_info, &storage); + type_inference_scope(&mut function.body, &scope_vars, &func_info, &storage)?; // todo: check all vars have type info? function.scope_type_info = new_scope_vars .into_iter() @@ -60,6 +67,7 @@ pub fn type_inference(ast: &mut ast::Program) { .collect(); } } + Ok(()) } /// Finds variable types in the scope, returns newly created variables to handle shadowing @@ -68,7 +76,7 @@ fn type_inference_scope( scope_vars: &ScopeMap, func: &Function, storage: &Storage, -) -> (ScopeMap, HashSet) { +) -> Result<(ScopeMap, HashSet), TypeError> { let mut scope_vars = scope_vars.clone(); let mut new_vars: HashSet = HashSet::new(); @@ -78,11 +86,11 @@ fn type_inference_scope( name, value, value_type, - span: _, + span, } => { new_vars.insert(name.clone()); - let exp_type = type_inference_expression(value, &mut scope_vars, storage, None); + let exp_type = type_inference_expression(value, &mut scope_vars, storage, None)?; if !scope_vars.contains_key(name) { scope_vars.insert(name.clone(), vec![]); @@ -94,27 +102,31 @@ fn type_inference_scope( var.push(exp_type); } else { if exp_type.is_some() && &exp_type != value_type { - panic!("let type mismatch: {:?} != {:?}", value_type, exp_type); + Err(TypeError::Mismatch { + found: exp_type.clone().unwrap(), + expected: value_type.clone().unwrap(), + span: *span, + })?; } var.push(value_type.clone()); } } - Statement::Mutate { - name, - value, - span: _, - } => { + Statement::Mutate { name, value, span } => { if !scope_vars.contains_key(name) { panic!("undeclared variable"); } - let exp_type = type_inference_expression(value, &mut scope_vars, storage, None); + let exp_type = type_inference_expression(value, &mut scope_vars, storage, None)?; let var = scope_vars.get_mut(name).unwrap().last_mut().unwrap(); if var.is_none() { *var = exp_type; } else if exp_type.is_some() && &exp_type != var { - panic!("mutate type mismatch: {:?} != {:?}", var, exp_type); + Err(TypeError::Mismatch { + found: exp_type.clone().unwrap(), + expected: var.clone().unwrap(), + span: *span, + })?; } } Statement::If { @@ -129,10 +141,10 @@ fn type_inference_scope( &mut scope_vars, storage, Some(TypeExp::Boolean), - ); + )?; let (new_scope_vars, new_vars) = - type_inference_scope(body, &scope_vars, func, storage); + type_inference_scope(body, &scope_vars, func, storage)?; for (k, v) in new_scope_vars.iter() { // not a new var within the scope (shadowing), so type info is valid @@ -148,7 +160,7 @@ fn type_inference_scope( if let Some(body) = else_body { let (new_scope_vars, new_vars) = - type_inference_scope(body, &scope_vars, func, storage); + type_inference_scope(body, &scope_vars, func, storage)?; for (k, v) in new_scope_vars.iter() { // not a new var within the scope (shadowing), so type info is valid @@ -170,7 +182,7 @@ fn type_inference_scope( &mut scope_vars, storage, func.return_type.clone(), - ); + )?; } } Statement::Function(_) => unreachable!(), @@ -178,7 +190,7 @@ fn type_inference_scope( } } - (scope_vars, new_vars) + Ok((scope_vars, new_vars)) } fn type_inference_expression( @@ -186,8 +198,8 @@ fn type_inference_expression( scope_vars: &mut ScopeMap, storage: &Storage, expected_type: Option, -) -> Option { - match exp { +) -> Result, TypeError> { + Ok(match exp { Expression::Literal(lit) => { match lit { ast::LiteralValue::String(_) => None, // todo @@ -214,7 +226,13 @@ fn type_inference_expression( *var = expected_type.clone(); expected_type } else if expected_type.is_some() { - assert_eq!(*var, expected_type, "type mismatch with variables"); + if *var != expected_type { + Err(TypeError::Mismatch { + found: expected_type.clone().unwrap(), + expected: var.clone().unwrap(), + span: name.span, + })?; + } expected_type } else { var.clone() @@ -229,7 +247,7 @@ fn type_inference_expression( for (i, arg) in args.iter().enumerate() { let arg_type = func.params[i].type_exp.clone(); // result is ignored, but need these to infer call arg types - type_inference_expression(arg, scope_vars, storage, Some(arg_type)); + type_inference_expression(arg, scope_vars, storage, Some(arg_type))?; } func.return_type @@ -238,15 +256,19 @@ fn type_inference_expression( ast::OpCode::Eq | ast::OpCode::Ne => Some(TypeExp::Boolean), _ => { let lhs_type = - type_inference_expression(lhs, scope_vars, storage, expected_type.clone()); - let rhs_type = type_inference_expression(rhs, scope_vars, storage, expected_type); + type_inference_expression(lhs, scope_vars, storage, expected_type.clone())?; + let rhs_type = type_inference_expression(rhs, scope_vars, storage, expected_type)?; - if lhs_type.is_some() && rhs_type.is_some() { - assert_eq!(lhs_type, rhs_type, "types should match"); + if lhs_type.is_some() && rhs_type.is_some() && lhs_type != rhs_type { + Err(TypeError::Mismatch { + found: rhs_type.clone().unwrap(), + expected: lhs_type.clone().unwrap(), + span: (0, 0), // todo + })?; } lhs_type.or(rhs_type) } }, - } + }) }