diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..3215080 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,15 @@ +codecov: + require_ci_to_pass: yes + +coverage: + status: + project: + default: + target: auto # Automatically sets the target based on the previous commits + threshold: 0% # Allows any decrease in coverage without failing the check + base: auto + patch: + default: + target: auto # Automatically sets the target based on the previous commits + threshold: 0% # Allows any decrease in coverage without failing the check + base: auto diff --git a/crates/concrete_check/src/linearity_check.rs b/crates/concrete_check/src/linearity_check.rs index fbe228a..aa0ce46 100644 --- a/crates/concrete_check/src/linearity_check.rs +++ b/crates/concrete_check/src/linearity_check.rs @@ -268,15 +268,13 @@ impl LinearityChecker { context: &str, ) -> Result { // Assuming you have a method to get all variable names and types - //let vars = &mut self.state_tbl.vars; - //TODO check if we can avoid cloning let vars = state_tbl.vars.clone(); println!("Check_expr vars {:?}", vars); for (name, _info) in vars.iter() { //self.check_var_in_expr(depth, &name, &info.ty, expr)?; state_tbl = self .check_var_in_expr(state_tbl, depth, name, expr, context) - .unwrap(); + .expect("Linearity error"); } Ok(state_tbl) } @@ -662,6 +660,7 @@ impl LinearityChecker { stmt: &Statement, context: &str, ) -> Result { + let mut errors: Vec = Vec::new(); match stmt { Statement::Let(binding) => { // Handle let bindings, possibly involving pattern matching @@ -721,28 +720,25 @@ impl LinearityChecker { Ok(state_tbl) } Statement::Return(return_stmt) => { - let errors: Vec = Vec::new(); + if let Some(return_stmt) = &return_stmt.value { + state_tbl = self.check_expr(state_tbl, depth, return_stmt, "return")?; + } + // Ensure that all variables are properly consumed for (name, var_info) in state_tbl.vars.iter() { - //FIXME implement Checking of consumed variables - /* match var_info.state { - VarState::Consumed => (), // If consumed, do nothing. + VarState::Consumed => (), // If consumed, no action needed _ => match var_info.ty { - Type::WriteRef(_) | Type::SpanMut(_) => (), // Write references and write spans can be dropped implicitly. - _ if self.universe_linear_ish(var_info.ty) => { - // If the type is linear-ish and the variable is not consumed, raise an error. - errors.push(Err(LinearityError::VariableNotConsumed { - variable_name: name.clone(), - message: format!("The variable {} is not consumed by the time of the return statement. Did you forget to call a destructor, or destructure the contents?", name), - })); - () + // Type::WriteRef(_) | Type::SpanMut(_) => (), // These can be dropped implicitly + _ if self.is_universe_linear_ish(&var_info.ty) => { + // Collect error if a variable that needs to be consumed hasn't been + errors.push(LinearityError::VariableNotConsumed { + variable: name.clone(), + }); } - _ => () - } + _ => (), + }, } - */ } - //if errors.len() > 0 { if !errors.is_empty() { Err(errors[0].clone()) } else { @@ -784,6 +780,10 @@ impl LinearityChecker { ) } + fn is_universe_linear_ish(&self, ty: &str) -> bool { + *ty == *"Linear".to_string() + } + fn check_var_in_expr( &self, state_tbl: StateTbl, @@ -796,7 +796,7 @@ impl LinearityChecker { if let Some(info) = info { //Only checks Linearity for types of name Linear // TODO improve this approach - if info.ty == *"Linear".to_string() { + if self.is_universe_linear_ish(&info.ty) { let state = &info.state; let apps = self.count_in_expression(name, expr); // Assume count function implementation let Appearances { @@ -921,7 +921,7 @@ impl LinearityChecker { pub fn linearity_check_program( programs: &Vec<(PathBuf, String, Program)>, _session: &Session, -) -> Result { +) -> Result<(), LinearityError> { tracing::debug!("Starting linearity check"); let checker = LinearityChecker::new(); for (_path, name, program) in programs { @@ -990,5 +990,5 @@ pub fn linearity_check_program( } } } - Ok("OK".to_string()) + Ok(()) } diff --git a/crates/concrete_check/src/linearity_check/errors.rs b/crates/concrete_check/src/linearity_check/errors.rs index d72968d..686c86f 100644 --- a/crates/concrete_check/src/linearity_check/errors.rs +++ b/crates/concrete_check/src/linearity_check/errors.rs @@ -2,7 +2,7 @@ use thiserror::Error; -#[derive(Debug, Error, Clone)] +#[derive(Debug, Error, Clone, PartialEq)] pub enum LinearityError { #[error("Variable {variable} not consumed")] NotConsumed { variable: String }, @@ -24,6 +24,8 @@ pub enum LinearityError { UnhandledStateOrCount { variable: String }, #[error("Consumed variable {variable} in loop created outside the loop")] ConsumedVariableInLoop { variable: String }, + #[error("Variable {variable} not consumed")] + VariableNotConsumed { variable: String }, #[error("Unspecified Linearity error. Variable {variable} generated {message}")] Unspecified { variable: String, message: String }, #[error("Variable {variable} not found")] diff --git a/crates/concrete_driver/src/lib.rs b/crates/concrete_driver/src/lib.rs index 76ac10e..6e222de 100644 --- a/crates/concrete_driver/src/lib.rs +++ b/crates/concrete_driver/src/lib.rs @@ -12,6 +12,7 @@ use concrete_session::{ Session, }; use config::{Package, Profile}; +use core::panic; use db::Database; use git2::{IndexAddOption, Repository}; use owo_colors::OwoColorize; @@ -110,18 +111,18 @@ pub struct BuildArgs { #[command(author, version, about = "concrete compiler", long_about = None)] pub struct CompilerArgs { /// The input file. - input: PathBuf, + pub input: PathBuf, /// The output file. pub output: PathBuf, /// Build for release with all optimizations. #[arg(short, long, default_value_t = false)] - release: bool, + pub release: bool, /// Set the optimization level, 0,1,2,3 #[arg(short = 'O', long)] - optlevel: Option, + pub optlevel: Option, /// Always add debug info #[arg(long)] @@ -129,35 +130,35 @@ pub struct CompilerArgs { /// Build as a library. #[arg(short, long, default_value_t = false)] - library: bool, + pub library: bool, /// Also output the ast. #[arg(long, default_value_t = false)] - ast: bool, + pub ast: bool, /// Also output the ir. #[arg(long, default_value_t = false)] - ir: bool, + pub ir: bool, /// Also output the llvm ir file. #[arg(long, default_value_t = false)] - llvm: bool, + pub llvm: bool, /// Also output the mlir file #[arg(long, default_value_t = false)] - mlir: bool, + pub mlir: bool, /// Also output the asm file. #[arg(long, default_value_t = false)] - asm: bool, + pub asm: bool, /// Also output the object file. #[arg(long, default_value_t = false)] - object: bool, + pub object: bool, /// This option is for checking the program for linearity. #[arg(long, default_value_t = false)] - check: bool, + pub check: bool, } pub fn main() -> Result<()> { @@ -605,15 +606,14 @@ pub fn compile(args: &CompilerArgs) -> Result { #[allow(unused_variables)] if args.check { - let linearity_result = - match concrete_check::linearity_check::linearity_check_program(&programs, &session) { - Ok(ir) => ir, - Err(error) => { - //TODO improve reporting - println!("Linearity check failed: {:#?}", error); - std::process::exit(1); - } - }; + //let linearity_result = + match concrete_check::linearity_check::linearity_check_program(&programs, &session) { + Ok(ir) => ir, + Err(error) => { + //TODO improve reporting + panic!("Linearity check failed: {:#?}", error); + } + }; } if args.ir { diff --git a/crates/concrete_driver/tests/common.rs b/crates/concrete_driver/tests/common.rs index 2290e5c..079a08f 100644 --- a/crates/concrete_driver/tests/common.rs +++ b/crates/concrete_driver/tests/common.rs @@ -6,7 +6,9 @@ use std::{ }; use ariadne::Source; +use concrete_ast::Program; use concrete_driver::linker::{link_binary, link_shared_lib}; +use concrete_driver::CompilerArgs; use concrete_ir::lowering::lower_programs; use concrete_parser::{error::Diagnostics, ProgramSource}; use concrete_session::{ @@ -39,7 +41,41 @@ pub fn compile_program( library: bool, optlevel: OptLevel, ) -> Result> { + let mut input_path = std::env::current_dir()?; + input_path = input_path.join(source); + let build_dir = std::env::current_dir()?; + let output = build_dir.join(source); + + let compile_args = CompilerArgs { + input: input_path.clone(), + output: output.clone(), + release: false, + ast: false, + optlevel: None, + debug_info: None, + library, + ir: false, + llvm: false, + mlir: false, + asm: false, + object: false, + check: false, + }; + compile_program_with_args(source, name, library, optlevel, &compile_args) +} + +pub fn compile_program_with_args( + source: &str, + name: &str, + library: bool, + optlevel: OptLevel, + args: &CompilerArgs, +) -> Result> { + let mut programs_for_check: Vec<(PathBuf, String, Program)> = Vec::new(); + let db = concrete_driver::db::Database::default(); + // Real source for programs_for_check + let real_source = source.to_string(); let source = ProgramSource::new(&db, source.to_string(), name.to_string()); tracing::debug!("source code:\n{}", source.input(&db)); let mut program = match concrete_parser::parse_ast(&db, source) { @@ -59,10 +95,15 @@ pub fn compile_program( let test_dir = tempfile::tempdir()?; let test_dir_path = test_dir.path(); - let input_file = test_dir_path.join(name).with_extension(".con"); + let input_file = test_dir_path.join(name).with_extension("con"); + + //Build Vec for programs_for_check before moving program + if args.check { + programs_for_check.push((input_file.clone(), real_source, program.clone())); + } + std::fs::write(&input_file, source.input(&db))?; program.file_path = Some(input_file.clone()); - let output_file = test_dir_path.join(name); let output_file = if library { output_file.with_extension(Session::get_platform_library_ext()) @@ -84,6 +125,23 @@ pub fn compile_program( file_paths: vec![input_file], }; + // By now only used check for being able to run tests with linearity checking + let mut linearity_errors = Vec::new(); + //#[allow(unused_variables)] + if args.check { + match concrete_check::linearity_check::linearity_check_program( + &programs_for_check, + &session, + ) { + Ok(ir) => ir, + Err(error) => { + //TODO improve reporting + println!("Linearity check failed: {:#?}", error); + linearity_errors.push(error); + } + }; + } + let program_ir = lower_programs(&[program])?; let object_path = concrete_codegen_mlir::compile(&session, &program_ir)?; @@ -101,12 +159,25 @@ pub fn compile_program( &session.output_file.with_extension(""), )?; } + if !linearity_errors.is_empty() { + let error = build_test_linearity_error(&linearity_errors[0]); + Err(error) + } else { + Ok(CompileResult { + folder: test_dir, + object_file: object_path, + binary_file: session.output_file, + }) + } +} - Ok(CompileResult { - folder: test_dir, - object_file: object_path, - binary_file: session.output_file, - }) +pub fn build_test_linearity_error( + linearity_error: &concrete_check::linearity_check::errors::LinearityError, +) -> Box { + let mut ret = "Linearity check failed<".to_string(); + ret.push_str(&linearity_error.to_string()); + ret.push('>'); + Box::new(TestError(ret.into())) } pub fn run_program(program: &Path) -> Result { @@ -119,12 +190,31 @@ pub fn run_program(program: &Path) -> Result { #[track_caller] pub fn compile_and_run(source: &str, name: &str, library: bool, optlevel: OptLevel) -> i32 { let result = compile_program(source, name, library, optlevel).expect("failed to compile"); - let output = run_program(&result.binary_file).expect("failed to run"); - output.status.code().unwrap() } +#[allow(unused)] // false positive +//#[cfg(test)] //TODO It should solve the warning but it doesn't +#[track_caller] +pub fn compile_and_run_with_args( + source: &str, + name: &str, + library: bool, + optlevel: OptLevel, + args: &CompilerArgs, +) -> Result> { + let compile_result = compile_program_with_args(source, name, library, optlevel, args); + match compile_result { + //Err(e) => Err(std::error::Error::new(std::io::ErrorKind::Other, e.to_string())), + Err(e) => Err(e), + Ok(result) => { + let run_output = run_program(&result.binary_file)?; + Ok(run_output) + } + } +} + #[allow(unused)] // false positive #[track_caller] pub fn compile_and_run_output( diff --git a/crates/concrete_driver/tests/examples.rs b/crates/concrete_driver/tests/examples.rs index d2c73f4..7522666 100644 --- a/crates/concrete_driver/tests/examples.rs +++ b/crates/concrete_driver/tests/examples.rs @@ -1,4 +1,7 @@ use crate::common::{compile_and_run, compile_and_run_output}; +use common::{build_test_linearity_error, compile_and_run_with_args}; +use concrete_check::linearity_check::errors::LinearityError; +use concrete_driver::CompilerArgs; use concrete_session::config::OptLevel; use test_case::test_case; @@ -22,9 +25,6 @@ mod common; #[test_case(include_str!("../../../examples/for_while.con"), "for_while", false, 10 ; "for_while.con")] #[test_case(include_str!("../../../examples/arrays.con"), "arrays", false, 5 ; "arrays.con")] #[test_case(include_str!("../../../examples/constants.con"), "constants", false, 20 ; "constants.con")] -#[test_case(include_str!("../../../examples/linearExample01.con"), "linearity", false, 2 ; "linearExample01.con")] -#[test_case(include_str!("../../../examples/linearExample02.con"), "linearity", false, 2 ; "linearExample02.con")] -#[test_case(include_str!("../../../examples/linearExample03if.con"), "linearity", false, 0 ; "linearExample03if.con")] fn example_tests(source: &str, name: &str, is_library: bool, status_code: i32) { assert_eq!( status_code, @@ -44,6 +44,83 @@ fn example_tests(source: &str, name: &str, is_library: bool, status_code: i32) { ); } +#[test_case(include_str!("../../../examples/linearExample01.con"), "linearity", false, 2; "linearExample01.con")] +#[test_case(include_str!("../../../examples/linearExample02.con"), "linearity", false, 2 ; "linearExample02.con")] +#[test_case(include_str!("../../../examples/linearExample03if.con"), "linearity", false, 0 ; "linearExample03if.con")] +fn example_tests_with_check( + source: &str, + name: &str, + is_library: bool, + status_code: i32, +) -> Result<(), Box> { + let mut input_path = std::env::current_dir()?; + input_path = input_path.join(source); + let build_dir = std::env::current_dir()?; + let output = build_dir.join(source); + let compile_args = CompilerArgs { + input: input_path.clone(), + output: output.clone(), + release: false, + ast: false, + optlevel: None, + debug_info: None, + library: false, + ir: false, + llvm: false, + mlir: false, + asm: false, + object: false, + check: true, + }; + example_tests_with_args(source, name, is_library, status_code, compile_args) +} + +fn example_tests_with_args( + source: &str, + name: &str, + is_library: bool, + expected_status_code: i32, + compile_args: CompilerArgs, +) -> Result<(), Box> { + let not_consumed_xy_error = build_test_linearity_error(&LinearityError::VariableNotConsumed { + variable: "xy".to_string(), + }); + + //let compile_result = crate::common::compile_program_with_args(source, name, is_library, OptLevel::None, &compile_args); + let result_1 = + compile_and_run_with_args(source, name, is_library, OptLevel::None, &compile_args); + match result_1 { + Ok(output) => assert_eq!(expected_status_code, output.status.code().unwrap()), + Err(e) => assert_eq!(not_consumed_xy_error.to_string(), e.to_string()), + } + let result_2 = + compile_and_run_with_args(source, name, is_library, OptLevel::Less, &compile_args); + match result_2 { + Ok(output) => assert_eq!(expected_status_code, output.status.code().unwrap()), + Err(e) => assert_eq!(not_consumed_xy_error.to_string(), e.to_string()), + } + + let result_3 = + compile_and_run_with_args(source, name, is_library, OptLevel::Default, &compile_args); + match result_3 { + Ok(output) => assert_eq!(expected_status_code, output.status.code().unwrap()), + Err(e) => assert_eq!(not_consumed_xy_error.to_string(), e.to_string()), + } + + let result_4 = compile_and_run_with_args( + source, + name, + is_library, + OptLevel::Aggressive, + &compile_args, + ); + match result_4 { + Ok(output) => assert_eq!(expected_status_code, output.status.code().unwrap()), + Err(e) => assert_eq!(not_consumed_xy_error.to_string(), e.to_string()), + } + Ok(()) +} + #[test_case(include_str!("../../../examples/hello_world_hacky.con"), "hello_world_hacky", false, "Hello World\n" ; "hello_world_hacky.con")] #[test_case(include_str!("../../../examples/hello_world_array.con"), "hello_world_array", false, "hello world!\n" ; "hello_world_array.con")] fn example_tests_with_output(source: &str, name: &str, is_library: bool, result: &str) {