diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5468d33..839997c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -73,7 +73,25 @@ jobs: run: sudo apt-get install libc-dev build-essential - name: test run: make test - + test-macos: + name: test (macOS) + runs-on: macos-14 + env: + CARGO_TERM_COLOR: always + LIBRARY_PATH: /opt/homebrew/lib + MLIR_SYS_180_PREFIX: /opt/homebrew/opt/llvm@18 + LLVM_SYS_180_PREFIX: /opt/homebrew/opt/llvm@18 + TABLEGEN_180_PREFIX: /opt/homebrew/opt/llvm@18 + RUST_LOG: debug + steps: + - uses: actions/checkout@v4 + - name: Rustup toolchain install + uses: dtolnay/rust-toolchain@1.78.0 + - uses: homebrew/actions/setup-homebrew@master + - name: install llvm + run: brew install llvm@18 + - name: Run tests + run: make test coverage: name: coverage runs-on: ubuntu-latest diff --git a/crates/concrete_check/src/lib.rs b/crates/concrete_check/src/lib.rs index 608d53b..2ef11a9 100644 --- a/crates/concrete_check/src/lib.rs +++ b/crates/concrete_check/src/lib.rs @@ -17,7 +17,7 @@ pub fn lowering_error_to_report( program_id, } => { let offset = span.from; - let path = file_paths[program_id].to_str().unwrap().to_string(); + let path = file_paths[program_id].display().to_string(); Report::build(ReportKind::Error, path.clone(), offset) .with_code("ModuleNotFound") .with_label( @@ -33,7 +33,7 @@ pub fn lowering_error_to_report( function, program_id, } => { - let path = file_paths[program_id].to_str().unwrap().to_string(); + let path = file_paths[program_id].display().to_string(); Report::build(ReportKind::Error, path.clone(), span.from) .with_code("FunctionNotFound") .with_label( @@ -48,7 +48,7 @@ pub fn lowering_error_to_report( name, program_id, } => { - let path = file_paths[program_id].to_str().unwrap().to_string(); + let path = file_paths[program_id].display().to_string(); Report::build(ReportKind::Error, path.clone(), span.from) .with_code("StructFieldNotFound") .with_label( @@ -64,7 +64,7 @@ pub fn lowering_error_to_report( symbol, program_id, } => { - let path = file_paths[program_id].to_str().unwrap().to_string(); + let path = file_paths[program_id].display().to_string(); let offset = symbol.span.from; Report::build(ReportKind::Error, path.clone(), offset) .with_code("ImportNotFound") @@ -90,7 +90,7 @@ pub fn lowering_error_to_report( type_span, program_id, } => { - let path = file_paths[program_id].to_str().unwrap().to_string(); + let path = file_paths[program_id].display().to_string(); let mut labels = vec![Label::new((path.clone(), span.into())) .with_message(format!( "Can't mutate {name:?} because it's behind a immutable borrow" @@ -115,7 +115,7 @@ pub fn lowering_error_to_report( name, program_id, } => { - let path = file_paths[program_id].to_str().unwrap().to_string(); + let path = file_paths[program_id].display().to_string(); Report::build(ReportKind::Error, path.clone(), span.from) .with_code("UnrecognizedType") .with_label( @@ -131,7 +131,7 @@ pub fn lowering_error_to_report( id, program_id, } => { - let path = file_paths[program_id].to_str().unwrap().to_string(); + let path = file_paths[program_id].display().to_string(); Report::build(ReportKind::Error, path.clone(), span.from) .with_code("E_ID") .with_label( @@ -147,7 +147,7 @@ pub fn lowering_error_to_report( message, program_id, } => { - let path = file_paths[program_id].to_str().unwrap().to_string(); + let path = file_paths[program_id].display().to_string(); Report::build(ReportKind::Error, path.clone(), span.from) .with_code("NotYetImplemented") .with_label( @@ -163,7 +163,7 @@ pub fn lowering_error_to_report( expected, program_id, } => { - let path = file_paths[program_id].to_str().unwrap().to_string(); + let path = file_paths[program_id].display().to_string(); let mut labels = vec![Label::new((path.clone(), span.into())) .with_message(format!( "Unexpected type '{}', expected '{}'", @@ -190,7 +190,7 @@ pub fn lowering_error_to_report( name, program_id, } => { - let path = file_paths[program_id].to_str().unwrap().to_string(); + let path = file_paths[program_id].display().to_string(); Report::build(ReportKind::Error, path.clone(), span.from) .with_code("UseOfUndeclaredVariable") .with_label( @@ -205,7 +205,7 @@ pub fn lowering_error_to_report( name, program_id, } => { - let path = file_paths[program_id].to_str().unwrap().to_string(); + let path = file_paths[program_id].display().to_string(); Report::build(ReportKind::Error, path.clone(), span.from) .with_code("ExternFnWithBody") .with_label( @@ -216,11 +216,77 @@ pub fn lowering_error_to_report( .finish() } LoweringError::InternalError(msg, program_id) => { - let path = file_paths[program_id].to_str().unwrap().to_string(); + let path = file_paths[program_id].display().to_string(); Report::build(ReportKind::Error, path.clone(), 0) .with_code("InternalError") .with_message(msg) .finish() } + LoweringError::CallParamCountMismatch { + span, + found, + needs, + program_id, + } => { + let path = file_paths[program_id].display().to_string(); + Report::build(ReportKind::Error, path.clone(), span.from) + .with_code("CallParamCountMismatch") + .with_label( + Label::new((path, span.into())) + .with_message(format!( + "function call parameter count mismatch: found {}, needs {}.", + found, needs + )) + .with_color(colors.next()), + ) + .finish() + } + + LoweringError::NotMutable { + span, + declare_span, + program_id, + } => { + let path = file_paths[program_id].display().to_string(); + let mut report = Report::build(ReportKind::Error, path.clone(), span.from) + .with_code("NotMutable") + .with_label( + Label::new((path.clone(), span.into())) + .with_message("can't mutate this variable because it's not mutable") + .with_color(colors.next()), + ); + + if let Some(declare_span) = declare_span { + report = report.with_label( + Label::new((path, declare_span.into())) + .with_message("variable declared here") + .with_color(colors.next()), + ); + } + report.finish() + } + LoweringError::CantTakeMutableBorrow { + span, + declare_span, + program_id, + } => { + let path = file_paths[program_id].display().to_string(); + let mut report = Report::build(ReportKind::Error, path.clone(), span.from) + .with_code("CantTakeMutableBorrow") + .with_label( + Label::new((path.clone(), span.into())) + .with_message("can't take a mutate borrow to this variable because it's not declared mutable") + .with_color(colors.next()), + ); + + if let Some(declare_span) = declare_span { + report = report.with_label( + Label::new((path, declare_span.into())) + .with_message("variable declared here") + .with_color(colors.next()), + ); + } + report.finish() + } } } diff --git a/crates/concrete_driver/tests/checks.rs b/crates/concrete_driver/tests/checks.rs index 2a5e670..41a9b4a 100644 --- a/crates/concrete_driver/tests/checks.rs +++ b/crates/concrete_driver/tests/checks.rs @@ -35,14 +35,14 @@ fn import_not_found() { #[test] fn invalid_borrow_mut() { - let (source, name) = (include_str!("invalid_programs/refmut.con"), "refmut"); + let (source, name) = ( + include_str!("invalid_programs/invalid_borrow_mut.con"), + "invalid_borrow_mut", + ); let error = check_invalid_program(source, name); assert!( - matches!( - &error, - LoweringError::BorrowNotMutable { name, .. } if name == "a" - ), + matches!(&error, LoweringError::NotMutable { .. }), "{:#?}", error ); @@ -102,3 +102,81 @@ fn undeclared_var() { error ); } + +#[test] +fn call_param_count_mismatch() { + let (source, name) = ( + include_str!("invalid_programs/call_param_count_mismatch.con"), + "call_param_count_mismatch", + ); + let error = check_invalid_program(source, name); + + assert!( + matches!( + &error, + LoweringError::CallParamCountMismatch { found, needs, .. } if *found == 2 && *needs == 1 + ), + "{:#?}", + error + ); +} + +#[test] +fn call_param_type_mismatch() { + let (source, name) = ( + include_str!("invalid_programs/call_param_type_mismatch.con"), + "call_param_type_mismatch", + ); + let error = check_invalid_program(source, name); + + assert!( + matches!(&error, LoweringError::UnexpectedType { .. }), + "{:#?}", + error + ); +} + +#[test] +fn invalid_assign() { + let (source, name) = ( + include_str!("invalid_programs/invalid_assign.con"), + "invalid_assign", + ); + let error = check_invalid_program(source, name); + + assert!( + matches!(&error, LoweringError::UnexpectedType { .. }), + "{:#?}", + error + ); +} + +#[test] +fn immutable_mutation() { + let (source, name) = ( + include_str!("invalid_programs/immutable_mutation.con"), + "immutable_mutation", + ); + let error = check_invalid_program(source, name); + + assert!( + matches!(&error, LoweringError::NotMutable { .. }), + "{:#?}", + error + ); +} + +#[test] +fn mutable_nonmut_borrow() { + let (source, name) = ( + include_str!("invalid_programs/mutable_nonmut_borrow.con"), + "mutable_nonmut_borrow", + ); + let error = check_invalid_program(source, name); + + assert!( + matches!(&error, LoweringError::CantTakeMutableBorrow { .. }), + "{:#?}", + error + ); +} diff --git a/crates/concrete_driver/tests/invalid_programs/call_param_count_mismatch.con b/crates/concrete_driver/tests/invalid_programs/call_param_count_mismatch.con new file mode 100644 index 0000000..8fd3b1f --- /dev/null +++ b/crates/concrete_driver/tests/invalid_programs/call_param_count_mismatch.con @@ -0,0 +1,9 @@ +mod Test { + fn main() -> i32 { + return hello(1, 2); + } + + fn hello(a: i32) -> i32 { + return a * 2; + } +} diff --git a/crates/concrete_driver/tests/invalid_programs/call_param_type_mismatch.con b/crates/concrete_driver/tests/invalid_programs/call_param_type_mismatch.con new file mode 100644 index 0000000..6ae9715 --- /dev/null +++ b/crates/concrete_driver/tests/invalid_programs/call_param_type_mismatch.con @@ -0,0 +1,10 @@ +mod Test { + fn main() -> i32 { + let x: u32 = 1; + return hello(x); + } + + fn hello(a: i32) -> i32 { + return a * 2; + } +} diff --git a/crates/concrete_driver/tests/invalid_programs/immutable_mutation.con b/crates/concrete_driver/tests/invalid_programs/immutable_mutation.con new file mode 100644 index 0000000..9d42bab --- /dev/null +++ b/crates/concrete_driver/tests/invalid_programs/immutable_mutation.con @@ -0,0 +1,7 @@ +mod Simple { + fn main() -> i32 { + let x: i32 = 2; + x = 4; + return x; + } +} diff --git a/crates/concrete_driver/tests/invalid_programs/invalid_assign.con b/crates/concrete_driver/tests/invalid_programs/invalid_assign.con new file mode 100644 index 0000000..69ace67 --- /dev/null +++ b/crates/concrete_driver/tests/invalid_programs/invalid_assign.con @@ -0,0 +1,8 @@ +mod Simple { + fn main() -> i32 { + let mut x: i32 = 2; + let y: i64 = 4; + x = y; + return x; + } +} diff --git a/crates/concrete_driver/tests/invalid_programs/refmut.con b/crates/concrete_driver/tests/invalid_programs/invalid_borrow_mut.con similarity index 82% rename from crates/concrete_driver/tests/invalid_programs/refmut.con rename to crates/concrete_driver/tests/invalid_programs/invalid_borrow_mut.con index 526ea46..7cbfb46 100644 --- a/crates/concrete_driver/tests/invalid_programs/refmut.con +++ b/crates/concrete_driver/tests/invalid_programs/invalid_borrow_mut.con @@ -1,7 +1,7 @@ mod Simple { fn main() -> i32 { - let x: i32 = 1; + let mut x: i32 = 1; hello(&x); return x; } diff --git a/crates/concrete_driver/tests/invalid_programs/mutable_nonmut_borrow.con b/crates/concrete_driver/tests/invalid_programs/mutable_nonmut_borrow.con new file mode 100644 index 0000000..e0bf1f5 --- /dev/null +++ b/crates/concrete_driver/tests/invalid_programs/mutable_nonmut_borrow.con @@ -0,0 +1,8 @@ +mod Simple { + fn main() -> i32 { + let x: i32 = 2; + let y: &mut i32 = &mut x; + *y = 4; + return *y; + } +} diff --git a/crates/concrete_ir/src/lib.rs b/crates/concrete_ir/src/lib.rs index 54af5a7..401d4fb 100644 --- a/crates/concrete_ir/src/lib.rs +++ b/crates/concrete_ir/src/lib.rs @@ -179,6 +179,17 @@ pub enum Rvalue { Cast(Operand, Ty, Span), } +impl Rvalue { + pub fn get_local(&self) -> Option { + match self { + Rvalue::Use(op) => op.get_local(), + Rvalue::Ref(_, op) => Some(op.local), + Rvalue::Cast(op, _, _) => op.get_local(), + _ => None, + } + } +} + /// A operand is a value, either from a place in memory or constant data. #[derive(Debug, Clone)] pub enum Operand { @@ -186,6 +197,15 @@ pub enum Operand { Const(ConstData), } +impl Operand { + pub fn get_local(&self) -> Option { + match self { + Operand::Place(place) => Some(place.local), + Operand::Const(_) => None, + } + } +} + /// A place in memory, defined by the given local and it's projection (deref, field, index, etc). #[derive(Debug, Clone)] pub struct Place { @@ -217,15 +237,24 @@ pub struct Local { pub ty: Ty, /// The type of local. pub kind: LocalKind, + /// Whether this local is declared mutable. + pub mutable: bool, } impl Local { - pub fn new(span: Option, kind: LocalKind, ty: Ty, debug_name: Option) -> Self { + pub fn new( + span: Option, + kind: LocalKind, + ty: Ty, + debug_name: Option, + mutable: bool, + ) -> Self { Self { span, kind, ty, debug_name, + mutable, } } @@ -235,6 +264,19 @@ impl Local { ty, kind: LocalKind::Temp, debug_name: None, + mutable: false, + } + } + + pub fn is_mutable(&self) -> bool { + if self.mutable { + return true; + } + + match self.ty.kind { + TyKind::Ptr(_, is_mut) => matches!(is_mut, Mutability::Mut), + TyKind::Ref(_, is_mut) => matches!(is_mut, Mutability::Mut), + _ => false, } } } diff --git a/crates/concrete_ir/src/lowering.rs b/crates/concrete_ir/src/lowering.rs index 3829408..777c5dd 100644 --- a/crates/concrete_ir/src/lowering.rs +++ b/crates/concrete_ir/src/lowering.rs @@ -263,10 +263,13 @@ fn lower_func( .clone(); builder.ret_local = builder.body.locals.len(); - builder - .body - .locals - .push(Local::new(None, LocalKind::ReturnPointer, ret_ty, None)); + builder.body.locals.push(Local::new( + None, + LocalKind::ReturnPointer, + ret_ty, + None, + false, + )); for (arg, ty) in func.decl.params.iter().zip(args_ty) { builder @@ -277,6 +280,7 @@ fn lower_func( LocalKind::Arg, ty, Some(arg.name.name.clone()), + false, )); } @@ -294,6 +298,7 @@ fn lower_func( LocalKind::Temp, ty, Some(name.name.clone()), + info.is_mutable, )); } LetStmtTarget::Destructure(_) => todo!(), @@ -311,6 +316,7 @@ fn lower_func( LocalKind::Temp, ty, Some(name.name.clone()), + info.is_mutable, )); } LetStmtTarget::Destructure(_) => todo!(), @@ -694,12 +700,12 @@ fn lower_let(builder: &mut FnBodyBuilder, info: &LetStmt) -> Result<(), Lowering match &info.target { LetStmtTarget::Simple { name, r#type } => { let ty = lower_type(&builder.ctx, r#type, builder.local_module)?; - let (rvalue, rvalue_ty, _exp_span) = + let (rvalue, rvalue_ty, rvalue_span) = lower_expression(builder, &info.value, Some(ty.clone()))?; if ty.kind != rvalue_ty.kind { return Err(LoweringError::UnexpectedType { - span: info.span, + span: rvalue_span, found: rvalue_ty, expected: ty.clone(), program_id: builder.local_module.program_id, @@ -730,6 +736,14 @@ fn lower_let(builder: &mut FnBodyBuilder, info: &LetStmt) -> Result<(), Lowering fn lower_assign(builder: &mut FnBodyBuilder, info: &AssignStmt) -> Result<(), LoweringError> { let (mut place, mut ty, _path_span) = lower_path(builder, &info.target)?; + if !builder.body.locals[place.local].is_mutable() { + return Err(LoweringError::NotMutable { + span: info.span, + declare_span: builder.body.locals[place.local].span, + program_id: builder.body.id.program_id, + }); + } + for _ in 0..info.derefs { match &ty.kind { TyKind::Ref(inner, is_mut) | TyKind::Ptr(inner, is_mut) => { @@ -748,7 +762,17 @@ fn lower_assign(builder: &mut FnBodyBuilder, info: &AssignStmt) -> Result<(), Lo place.projection.push(PlaceElem::Deref); } - let (rvalue, _rvalue_ty, _exp_span) = lower_expression(builder, &info.value, Some(ty.clone()))?; + let (rvalue, rvalue_ty, rvalue_span) = + lower_expression(builder, &info.value, Some(ty.clone()))?; + + if ty.kind != rvalue_ty.kind { + return Err(LoweringError::UnexpectedType { + span: rvalue_span, + found: rvalue_ty, + expected: ty.clone(), + program_id: builder.local_module.program_id, + }); + } builder.statements.push(Statement { span: Some(info.target.first.span), @@ -945,7 +969,17 @@ fn lower_expression( }, None => None, }; - let (value, ty, _span) = lower_expression(builder, inner, type_hint)?; + let (value, ty, _ref_target_span) = lower_expression(builder, inner, type_hint)?; + + if let Some(local) = value.get_local() { + if *mutable && !builder.body.locals[local].mutable { + return Err(LoweringError::CantTakeMutableBorrow { + span: *asref_span, + declare_span: builder.body.locals[local].span, + program_id: builder.body.id.program_id, + }); + } + } let mutability = match mutable { false => Mutability::Not, @@ -1189,11 +1223,30 @@ fn lower_fn_call( } }; + if args_ty.len() != info.args.len() { + return Err(LoweringError::CallParamCountMismatch { + span: info.span, + found: info.args.len(), + needs: args_ty.len(), + program_id: builder.get_module_body().id.program_id, + }); + } + let mut args = Vec::new(); for (arg, arg_ty) in info.args.iter().zip(args_ty) { - let rvalue = lower_expression(builder, arg, Some(arg_ty.clone()))?; - args.push(rvalue.0); + let (rvalue, rvalue_ty, arg_span) = lower_expression(builder, arg, Some(arg_ty.clone()))?; + + if rvalue_ty.kind != arg_ty.kind { + return Err(LoweringError::UnexpectedType { + span: arg_span, + found: rvalue_ty, + expected: arg_ty, + program_id: builder.get_module_body().id.program_id, + }); + } + + args.push(rvalue); } let dest_local = builder.add_local(Local::temp(ret_ty.clone())); diff --git a/crates/concrete_ir/src/lowering/errors.rs b/crates/concrete_ir/src/lowering/errors.rs index 48d9a58..94b9c67 100644 --- a/crates/concrete_ir/src/lowering/errors.rs +++ b/crates/concrete_ir/src/lowering/errors.rs @@ -43,6 +43,18 @@ pub enum LoweringError { name: String, program_id: usize, }, + #[error("can't mutate this value because it's not declared mutable")] + NotMutable { + span: Span, + declare_span: Option, + program_id: usize, + }, + #[error("can't take a mutable borrow to this value because it's not declared mutable")] + CantTakeMutableBorrow { + span: Span, + declare_span: Option, + program_id: usize, + }, #[error("unrecognized type {name}")] UnrecognizedType { span: Span, @@ -76,4 +88,11 @@ pub enum LoweringError { }, #[error("internal error: {0}")] InternalError(String, usize), + #[error("function call parameter count mismatch, found {found}, needs {needs}")] + CallParamCountMismatch { + span: Span, + found: usize, + needs: usize, + program_id: usize, + }, } diff --git a/examples/arrays.con b/examples/arrays.con index 52ade15..1e29aac 100644 --- a/examples/arrays.con +++ b/examples/arrays.con @@ -1,7 +1,7 @@ mod Example { fn main() -> i32 { let mut array: [i32; 4] = [1, 9, 3, 4]; - let nested_array: [[i32; 2]; 2] = [[1, 2], [9, 9]]; + let mut nested_array: [[i32; 2]; 2] = [[1, 2], [9, 9]]; array[1] = 2; nested_array[1] = [3, 4]; diff --git a/examples/for.con b/examples/for.con index 1f2ddd0..154c7d5 100644 --- a/examples/for.con +++ b/examples/for.con @@ -6,7 +6,7 @@ mod Example { fn sum_to(limit: i64) -> i64 { let mut result: i64 = 0; - for (let n: i64 = 1; n <= limit; n = n + 1) { + for (let mut n: i64 = 1; n <= limit; n = n + 1) { result = result + n; } diff --git a/examples/for_while.con b/examples/for_while.con index d87f1d7..3da1adf 100644 --- a/examples/for_while.con +++ b/examples/for_while.con @@ -6,7 +6,7 @@ mod Example { fn sum_to(limit: i64) -> i64 { let mut result: i64 = 0; - let n: i64 = 1; + let mut n: i64 = 1; for (n <= limit) { result = result + n; n = n + 1; diff --git a/examples/hello_world_hacky.con b/examples/hello_world_hacky.con index ed27057..132fe07 100644 --- a/examples/hello_world_hacky.con +++ b/examples/hello_world_hacky.con @@ -6,29 +6,29 @@ mod HelloWorld { let origin: *mut u8 = malloc(12); let mut p: *mut u8 = origin; - *p = 'H'; + *p = 72; p = p + 1; - *p = 'e'; + *p = 101; p = p + 1; - *p = 'l'; + *p = 108; p = p + 1; - *p = 'l'; + *p = 108; p = p + 1; - *p = 'o'; + *p = 111; p = p + 1; - *p = ' '; + *p = 32; p = p + 1; - *p = 'W'; + *p = 87; p = p + 1; - *p = 'o'; + *p = 111; p = p + 1; - *p = 'r'; + *p = 114; p = p + 1; - *p = 'l'; + *p = 108; p = p + 1; - *p = 'd'; + *p = 100; p = p + 1; - *p = '\0'; + *p = 0; puts(origin); return 0; diff --git a/examples/if_if_false.con b/examples/if_if_false.con index a38c67d..456cd4c 100644 --- a/examples/if_if_false.con +++ b/examples/if_if_false.con @@ -1,6 +1,6 @@ mod Example { fn main() -> i32 { - let foo: i32 = 7; + let mut foo: i32 = 7; if true { if false {