diff --git a/crates/lsp/src/lib.rs b/crates/lsp/src/lib.rs index 802ecab579..7e48f4b4d5 100644 --- a/crates/lsp/src/lib.rs +++ b/crates/lsp/src/lib.rs @@ -199,8 +199,8 @@ fn on_code_lens_request( let tests = context .get_all_test_functions_in_crate_matching(&crate_id, FunctionNameMatch::Anything); - for (func_name, func_id) in tests { - let location = context.function_meta(&func_id).name.location; + for (func_name, test_function) in tests { + let location = context.function_meta(&test_function.get_id()).name.location; let file_id = location.file; // Ignore diagnostics for any file that wasn't the file we saved diff --git a/crates/nargo_cli/src/cli/test_cmd.rs b/crates/nargo_cli/src/cli/test_cmd.rs index 94c8ff86dc..ddf3493372 100644 --- a/crates/nargo_cli/src/cli/test_cmd.rs +++ b/crates/nargo_cli/src/cli/test_cmd.rs @@ -7,8 +7,7 @@ use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelec use noirc_driver::{compile_no_check, CompileOptions}; use noirc_frontend::{ graph::CrateName, - hir::{Context, FunctionNameMatch}, - node_interner::FuncId, + hir::{def_map::TestFunction, Context, FunctionNameMatch}, }; use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; @@ -123,30 +122,70 @@ fn run_tests( fn run_test( backend: &B, test_name: &str, - main: FuncId, + test_function: TestFunction, context: &Context, show_output: bool, config: &CompileOptions, ) -> Result<(), CliError> { - let mut program = compile_no_check(context, config, main).map_err(|err| { + let report_error = |err| { noirc_errors::reporter::report_all(&context.file_manager, &[err], config.deny_warnings); - CliError::Generic(format!("Test '{test_name}' failed to compile")) - })?; - - // Note: We could perform this test using the unoptimized ACIR as generated by `compile_no_check`. - program.circuit = optimize_circuit(backend, program.circuit).unwrap().0; - - // Run the backend to ensure the PWG evaluates functions like std::hash::pedersen, - // otherwise constraints involving these expressions will not error. - match execute_circuit(backend, program.circuit, WitnessMap::new(), show_output) { - Ok(_) => Ok(()), - Err(error) => { - let writer = StandardStream::stderr(ColorChoice::Always); - let mut writer = writer.lock(); - writer.set_color(ColorSpec::new().set_fg(Some(Color::Red))).ok(); - writeln!(writer, "failed").ok(); - writer.reset().ok(); - Err(error.into()) + Err(CliError::Generic(format!("Test '{test_name}' failed to compile"))) + }; + let write_error = |err| { + let writer = StandardStream::stderr(ColorChoice::Always); + let mut writer = writer.lock(); + writer.set_color(ColorSpec::new().set_fg(Some(Color::Red))).ok(); + writeln!(writer, "failed").ok(); + writer.reset().ok(); + Err(err) + }; + + let program = compile_no_check(context, config, test_function.get_id()); + match program { + Ok(mut program) => { + // Note: We don't need to use the optimized ACIR here + program.circuit = optimize_circuit(backend, program.circuit).unwrap().0; + + // Run the backend to ensure the PWG evaluates functions like std::hash::pedersen, + // otherwise constraints involving these expressions will not error. + let circuit_execution = + execute_circuit(backend, program.circuit, WitnessMap::new(), show_output); + + if test_function.should_fail() { + match circuit_execution { + Ok(_) => { + write_error(CliError::Generic(format!("Test '{test_name}' should fail"))) + } + Err(_) => Ok(()), + } + } else { + match circuit_execution { + Ok(_) => Ok(()), + Err(error) => write_error(error.into()), + } + } + } + // Test function failed to compile + // + // Note: This could be because the compiler was able to deduce + // that a constraint was never satisfiable. + // An example of this is the program `assert(false)` + // In that case, we check if the test function should fail, and if so, we return Ok. + Err(err) => { + // The test has failed compilation, but it should never fail. Report error. + if !test_function.should_fail() { + return report_error(err); + } + + // The test has failed compilation, check if it is because the program is never satisfiable. + // If it is never satisfiable, then this is the expected behavior. + let program_is_never_satisfiable = err.diagnostic.message.contains("Failed constraint"); + if program_is_never_satisfiable { + return Ok(()); + } + + // The test has failed compilation, but its a compilation error. Report error + report_error(err) } } } diff --git a/crates/noirc_frontend/src/ast/function.rs b/crates/noirc_frontend/src/ast/function.rs index 377e6aa77a..cedb48b171 100644 --- a/crates/noirc_frontend/src/ast/function.rs +++ b/crates/noirc_frontend/src/ast/function.rs @@ -83,7 +83,7 @@ impl From for NoirFunction { let kind = match fd.attribute { Some(Attribute::Builtin(_)) => FunctionKind::Builtin, Some(Attribute::Foreign(_)) => FunctionKind::LowLevel, - Some(Attribute::Test) => FunctionKind::Normal, + Some(Attribute::Test { .. }) => FunctionKind::Normal, Some(Attribute::Oracle(_)) => FunctionKind::Oracle, Some(Attribute::Deprecated(_)) | None => FunctionKind::Normal, Some(Attribute::Custom(_)) => FunctionKind::Normal, diff --git a/crates/noirc_frontend/src/hir/def_map/mod.rs b/crates/noirc_frontend/src/hir/def_map/mod.rs index 2dc8c5ec96..f97d7e9bf5 100644 --- a/crates/noirc_frontend/src/hir/def_map/mod.rs +++ b/crates/noirc_frontend/src/hir/def_map/mod.rs @@ -3,7 +3,7 @@ use crate::hir::def_collector::dc_crate::DefCollector; use crate::hir::Context; use crate::node_interner::{FuncId, NodeInterner}; use crate::parser::{parse_program, ParsedModule}; -use crate::token::Attribute; +use crate::token::{Attribute, TestScope}; use arena::{Arena, Index}; use fm::{FileId, FileManager}; use noirc_errors::{FileDiagnostic, Location}; @@ -129,12 +129,18 @@ impl CrateDefMap { pub fn get_all_test_functions<'a>( &'a self, interner: &'a NodeInterner, - ) -> impl Iterator + 'a { + ) -> impl Iterator + 'a { self.modules.iter().flat_map(|(_, module)| { - module - .value_definitions() - .filter_map(|id| id.as_function()) - .filter(|id| interner.function_meta(id).attributes == Some(Attribute::Test)) + module.value_definitions().filter_map(|id| { + if let Some(func_id) = id.as_function() { + match interner.function_meta(&func_id).attributes { + Some(Attribute::Test(scope)) => Some(TestFunction::new(func_id, scope)), + _ => None, + } + } else { + None + } + }) }) } @@ -221,3 +227,28 @@ impl std::ops::IndexMut for CrateDefMap { &mut self.modules[local_module_id.0] } } + +pub struct TestFunction { + id: FuncId, + scope: TestScope, +} + +impl TestFunction { + fn new(id: FuncId, scope: TestScope) -> Self { + TestFunction { id, scope } + } + + /// Returns the function id of the test function + pub fn get_id(&self) -> FuncId { + self.id + } + + /// Returns true if the test function has been specified to fail + /// This is done by annotating the function with `#[test(should_fail)]` + pub fn should_fail(&self) -> bool { + match self.scope { + TestScope::ShouldFail => true, + TestScope::None => false, + } + } +} diff --git a/crates/noirc_frontend/src/hir/mod.rs b/crates/noirc_frontend/src/hir/mod.rs index d0b24e90a7..76bbe59f51 100644 --- a/crates/noirc_frontend/src/hir/mod.rs +++ b/crates/noirc_frontend/src/hir/mod.rs @@ -11,6 +11,8 @@ use def_map::{Contract, CrateDefMap}; use fm::FileManager; use std::collections::HashMap; +use self::def_map::TestFunction; + /// Helper object which groups together several useful context objects used /// during name resolution. Once name resolution is finished, only the /// def_interner is required for type inference and monomorphization. @@ -107,22 +109,22 @@ impl Context { &self, crate_id: &CrateId, pattern: FunctionNameMatch, - ) -> Vec<(String, FuncId)> { + ) -> Vec<(String, TestFunction)> { let interner = &self.def_interner; let def_map = self.def_map(crate_id).expect("The local crate should be analyzed already"); def_map .get_all_test_functions(interner) - .filter_map(|id| { - let fully_qualified_name = self.fully_qualified_function_name(crate_id, &id); + .filter_map(|test_function| { + let fully_qualified_name = + self.fully_qualified_function_name(crate_id, &test_function.get_id()); match &pattern { - FunctionNameMatch::Anything => Some((fully_qualified_name, id)), - FunctionNameMatch::Exact(pattern) => { - (&fully_qualified_name == pattern).then_some((fully_qualified_name, id)) - } - FunctionNameMatch::Contains(pattern) => { - fully_qualified_name.contains(pattern).then_some((fully_qualified_name, id)) - } + FunctionNameMatch::Anything => Some((fully_qualified_name, test_function)), + FunctionNameMatch::Exact(pattern) => (&fully_qualified_name == pattern) + .then_some((fully_qualified_name, test_function)), + FunctionNameMatch::Contains(pattern) => fully_qualified_name + .contains(pattern) + .then_some((fully_qualified_name, test_function)), } }) .collect() diff --git a/crates/noirc_frontend/src/hir/resolution/resolver.rs b/crates/noirc_frontend/src/hir/resolution/resolver.rs index 6ce06d4a1a..47d624e028 100644 --- a/crates/noirc_frontend/src/hir/resolution/resolver.rs +++ b/crates/noirc_frontend/src/hir/resolution/resolver.rs @@ -700,7 +700,8 @@ impl<'a> Resolver<'a> { self.push_err(ResolverError::DistinctNotAllowed { ident: func.name_ident().clone() }); } - if attributes == Some(Attribute::Test) && !parameters.is_empty() { + if matches!(attributes, Some(Attribute::Test { .. })) && !parameters.is_empty() + { self.push_err(ResolverError::TestFunctionHasParameters { span: func.name_ident().span(), }); diff --git a/crates/noirc_frontend/src/lexer/lexer.rs b/crates/noirc_frontend/src/lexer/lexer.rs index fc72f43674..75dba3cad1 100644 --- a/crates/noirc_frontend/src/lexer/lexer.rs +++ b/crates/noirc_frontend/src/lexer/lexer.rs @@ -379,297 +379,344 @@ impl<'a> Iterator for Lexer<'a> { } } -#[test] -fn test_single_double_char() { - let input = "! != + ( ) { } [ ] | , ; : :: < <= > >= & - -> . .. % / * = == << >>"; - - let expected = vec![ - Token::Bang, - Token::NotEqual, - Token::Plus, - Token::LeftParen, - Token::RightParen, - Token::LeftBrace, - Token::RightBrace, - Token::LeftBracket, - Token::RightBracket, - Token::Pipe, - Token::Comma, - Token::Semicolon, - Token::Colon, - Token::DoubleColon, - Token::Less, - Token::LessEqual, - Token::Greater, - Token::GreaterEqual, - Token::Ampersand, - Token::Minus, - Token::Arrow, - Token::Dot, - Token::DoubleDot, - Token::Percent, - Token::Slash, - Token::Star, - Token::Assign, - Token::Equal, - Token::ShiftLeft, - Token::Greater, - Token::Greater, - Token::EOF, - ]; - - let mut lexer = Lexer::new(input); - - for token in expected.into_iter() { - let got = lexer.next_token().unwrap(); - assert_eq!(got, token); +#[cfg(test)] +mod tests { + use super::*; + use crate::token::TestScope; + #[test] + fn test_single_double_char() { + let input = "! != + ( ) { } [ ] | , ; : :: < <= > >= & - -> . .. % / * = == << >>"; + + let expected = vec![ + Token::Bang, + Token::NotEqual, + Token::Plus, + Token::LeftParen, + Token::RightParen, + Token::LeftBrace, + Token::RightBrace, + Token::LeftBracket, + Token::RightBracket, + Token::Pipe, + Token::Comma, + Token::Semicolon, + Token::Colon, + Token::DoubleColon, + Token::Less, + Token::LessEqual, + Token::Greater, + Token::GreaterEqual, + Token::Ampersand, + Token::Minus, + Token::Arrow, + Token::Dot, + Token::DoubleDot, + Token::Percent, + Token::Slash, + Token::Star, + Token::Assign, + Token::Equal, + Token::ShiftLeft, + Token::Greater, + Token::Greater, + Token::EOF, + ]; + + let mut lexer = Lexer::new(input); + + for token in expected.into_iter() { + let got = lexer.next_token().unwrap(); + assert_eq!(got, token); + } } -} -#[test] -fn invalid_attribute() { - let input = "#"; - let mut lexer = Lexer::new(input); + #[test] + fn invalid_attribute() { + let input = "#"; + let mut lexer = Lexer::new(input); - let token = lexer.next().unwrap(); - assert!(token.is_err()); -} + let token = lexer.next().unwrap(); + assert!(token.is_err()); + } -#[test] -fn deprecated_attribute() { - let input = r#"#[deprecated]"#; - let mut lexer = Lexer::new(input); + #[test] + fn deprecated_attribute() { + let input = r#"#[deprecated]"#; + let mut lexer = Lexer::new(input); - let token = lexer.next().unwrap().unwrap(); - assert_eq!(token.token(), &Token::Attribute(Attribute::Deprecated(None))); -} + let token = lexer.next().unwrap().unwrap(); + assert_eq!(token.token(), &Token::Attribute(Attribute::Deprecated(None))); + } -#[test] -fn deprecated_attribute_with_note() { - let input = r#"#[deprecated("hello")]"#; - let mut lexer = Lexer::new(input); + #[test] + fn deprecated_attribute_with_note() { + let input = r#"#[deprecated("hello")]"#; + let mut lexer = Lexer::new(input); - let token = lexer.next().unwrap().unwrap(); - assert_eq!(token.token(), &Token::Attribute(Attribute::Deprecated("hello".to_string().into()))); -} + let token = lexer.next().unwrap().unwrap(); + assert_eq!( + token.token(), + &Token::Attribute(Attribute::Deprecated("hello".to_string().into())) + ); + } -#[test] -fn custom_attribute() { - let input = r#"#[custom(hello)]"#; - let mut lexer = Lexer::new(input); + #[test] + fn custom_attribute() { + let input = r#"#[custom(hello)]"#; + let mut lexer = Lexer::new(input); - let token = lexer.next().unwrap().unwrap(); - assert_eq!(token.token(), &Token::Attribute(Attribute::Custom("custom(hello)".to_string()))); -} + let token = lexer.next().unwrap().unwrap(); + assert_eq!( + token.token(), + &Token::Attribute(Attribute::Custom("custom(hello)".to_string())) + ); + } -#[test] -fn test_custom_gate_syntax() { - let input = "#[foreign(sha256)]#[foreign(blake2s)]#[builtin(sum)]"; + #[test] + fn test_attribute() { + let input = r#"#[test]"#; + let mut lexer = Lexer::new(input); - let expected = vec![ - Token::Attribute(Attribute::Foreign("sha256".to_string())), - Token::Attribute(Attribute::Foreign("blake2s".to_string())), - Token::Attribute(Attribute::Builtin("sum".to_string())), - ]; + let token = lexer.next().unwrap().unwrap(); + assert_eq!(token.token(), &Token::Attribute(Attribute::Test(TestScope::None))); + } + #[test] + fn test_attribute_with_valid_scope() { + let input = r#"#[test(should_fail)]"#; + let mut lexer = Lexer::new(input); - let mut lexer = Lexer::new(input); - for token in expected.into_iter() { - let got = lexer.next_token().unwrap(); - assert_eq!(got, token); + let token = lexer.next().unwrap().unwrap(); + assert_eq!(token.token(), &Token::Attribute(Attribute::Test(TestScope::ShouldFail))); } -} -#[test] -fn test_int_type() { - let input = "u16 i16 i108 u104.5"; + #[test] + fn test_attribute_with_invalid_scope() { + let input = r#"#[test(invalid_scope)]"#; + let mut lexer = Lexer::new(input); - let expected = vec![ - Token::IntType(IntType::Unsigned(16)), - Token::IntType(IntType::Signed(16)), - Token::IntType(IntType::Signed(108)), - Token::IntType(IntType::Unsigned(104)), - Token::Dot, - Token::Int(5_i128.into()), - ]; + let token = lexer.next().unwrap(); + let err = match token { + Ok(_) => panic!("test has an invalid scope, so expected an error"), + Err(err) => err, + }; + + // Check if error is MalformedFuncAttribute and found is "foo" + let sub_string = match err { + LexerErrorKind::MalformedFuncAttribute { found, .. } => found, + _ => panic!("expected malformed func attribute error"), + }; - let mut lexer = Lexer::new(input); - for token in expected.into_iter() { - let got = lexer.next_token().unwrap(); - assert_eq!(got, token); + assert_eq!(sub_string, "test(invalid_scope)"); } -} -#[test] -fn test_arithmetic_sugar() { - let input = "+= -= *= /= %="; - - let expected = vec![ - Token::Plus, - Token::Assign, - Token::Minus, - Token::Assign, - Token::Star, - Token::Assign, - Token::Slash, - Token::Assign, - Token::Percent, - Token::Assign, - ]; - - let mut lexer = Lexer::new(input); - for token in expected.into_iter() { - let got = lexer.next_token().unwrap(); - assert_eq!(got, token); + #[test] + fn test_custom_gate_syntax() { + let input = "#[foreign(sha256)]#[foreign(blake2s)]#[builtin(sum)]"; + + let expected = vec![ + Token::Attribute(Attribute::Foreign("sha256".to_string())), + Token::Attribute(Attribute::Foreign("blake2s".to_string())), + Token::Attribute(Attribute::Builtin("sum".to_string())), + ]; + + let mut lexer = Lexer::new(input); + for token in expected.into_iter() { + let got = lexer.next_token().unwrap(); + assert_eq!(got, token); + } } -} -#[test] -fn unterminated_block_comment() { - let input = "/*/"; + #[test] + fn test_int_type() { + let input = "u16 i16 i108 u104.5"; - let mut lexer = Lexer::new(input); - let token = lexer.next().unwrap(); + let expected = vec![ + Token::IntType(IntType::Unsigned(16)), + Token::IntType(IntType::Signed(16)), + Token::IntType(IntType::Signed(108)), + Token::IntType(IntType::Unsigned(104)), + Token::Dot, + Token::Int(5_i128.into()), + ]; - assert!(token.is_err()); -} + let mut lexer = Lexer::new(input); + for token in expected.into_iter() { + let got = lexer.next_token().unwrap(); + assert_eq!(got, token); + } + } -#[test] -fn test_comment() { - let input = "// hello + #[test] + fn test_arithmetic_sugar() { + let input = "+= -= *= /= %="; + + let expected = vec![ + Token::Plus, + Token::Assign, + Token::Minus, + Token::Assign, + Token::Star, + Token::Assign, + Token::Slash, + Token::Assign, + Token::Percent, + Token::Assign, + ]; + + let mut lexer = Lexer::new(input); + for token in expected.into_iter() { + let got = lexer.next_token().unwrap(); + assert_eq!(got, token); + } + } + + #[test] + fn unterminated_block_comment() { + let input = "/*/"; + + let mut lexer = Lexer::new(input); + let token = lexer.next().unwrap(); + + assert!(token.is_err()); + } + + #[test] + fn test_comment() { + let input = "// hello let x = 5 "; - let expected = vec![ - Token::Keyword(Keyword::Let), - Token::Ident("x".to_string()), - Token::Assign, - Token::Int(FieldElement::from(5_i128)), - ]; - - let mut lexer = Lexer::new(input); - for token in expected.into_iter() { - let first_lexer_output = lexer.next_token().unwrap(); - assert_eq!(first_lexer_output, token); + let expected = vec![ + Token::Keyword(Keyword::Let), + Token::Ident("x".to_string()), + Token::Assign, + Token::Int(FieldElement::from(5_i128)), + ]; + + let mut lexer = Lexer::new(input); + for token in expected.into_iter() { + let first_lexer_output = lexer.next_token().unwrap(); + assert_eq!(first_lexer_output, token); + } } -} -#[test] -fn test_block_comment() { - let input = " + #[test] + fn test_block_comment() { + let input = " /* comment */ let x = 5 /* comment */ "; - let expected = vec![ - Token::Keyword(Keyword::Let), - Token::Ident("x".to_string()), - Token::Assign, - Token::Int(FieldElement::from(5_i128)), - ]; - - let mut lexer = Lexer::new(input); - for token in expected.into_iter() { - let first_lexer_output = lexer.next_token().unwrap(); - assert_eq!(first_lexer_output, token); + let expected = vec![ + Token::Keyword(Keyword::Let), + Token::Ident("x".to_string()), + Token::Assign, + Token::Int(FieldElement::from(5_i128)), + ]; + + let mut lexer = Lexer::new(input); + for token in expected.into_iter() { + let first_lexer_output = lexer.next_token().unwrap(); + assert_eq!(first_lexer_output, token); + } } -} -#[test] -fn test_nested_block_comments() { - let input = " + #[test] + fn test_nested_block_comments() { + let input = " /* /* */ /** */ /*! */ */ let x = 5 /* /* */ /** */ /*! */ */ "; - let expected = vec![ - Token::Keyword(Keyword::Let), - Token::Ident("x".to_string()), - Token::Assign, - Token::Int(FieldElement::from(5_i128)), - ]; - - let mut lexer = Lexer::new(input); - for token in expected.into_iter() { - let first_lexer_output = lexer.next_token().unwrap(); - assert_eq!(first_lexer_output, token); + let expected = vec![ + Token::Keyword(Keyword::Let), + Token::Ident("x".to_string()), + Token::Assign, + Token::Int(FieldElement::from(5_i128)), + ]; + + let mut lexer = Lexer::new(input); + for token in expected.into_iter() { + let first_lexer_output = lexer.next_token().unwrap(); + assert_eq!(first_lexer_output, token); + } } -} -#[test] -fn test_eat_string_literal() { - let input = "let _word = \"hello\""; - - let expected = vec![ - Token::Keyword(Keyword::Let), - Token::Ident("_word".to_string()), - Token::Assign, - Token::Str("hello".to_string()), - ]; - let mut lexer = Lexer::new(input); - - for token in expected.into_iter() { - let got = lexer.next_token().unwrap(); - assert_eq!(got, token); + #[test] + fn test_eat_string_literal() { + let input = "let _word = \"hello\""; + + let expected = vec![ + Token::Keyword(Keyword::Let), + Token::Ident("_word".to_string()), + Token::Assign, + Token::Str("hello".to_string()), + ]; + let mut lexer = Lexer::new(input); + + for token in expected.into_iter() { + let got = lexer.next_token().unwrap(); + assert_eq!(got, token); + } } -} -#[test] -fn test_eat_hex_int() { - let input = "0x05"; + #[test] + fn test_eat_hex_int() { + let input = "0x05"; - let expected = vec![Token::Int(5_i128.into())]; - let mut lexer = Lexer::new(input); + let expected = vec![Token::Int(5_i128.into())]; + let mut lexer = Lexer::new(input); - for token in expected.into_iter() { - let got = lexer.next_token().unwrap(); - assert_eq!(got, token); + for token in expected.into_iter() { + let got = lexer.next_token().unwrap(); + assert_eq!(got, token); + } } -} -#[test] -fn test_span() { - let input = "let x = 5"; + #[test] + fn test_span() { + let input = "let x = 5"; - // Let - let start_position = Position::default(); - let let_position = start_position + 2; - let let_token = Token::Keyword(Keyword::Let).into_span(start_position, let_position); + // Let + let start_position = Position::default(); + let let_position = start_position + 2; + let let_token = Token::Keyword(Keyword::Let).into_span(start_position, let_position); - // Skip whitespace - let whitespace_position = let_position + 1; + // Skip whitespace + let whitespace_position = let_position + 1; - // Identifier position - let ident_position = whitespace_position + 1; - let ident_token = Token::Ident("x".to_string()).into_single_span(ident_position); + // Identifier position + let ident_position = whitespace_position + 1; + let ident_token = Token::Ident("x".to_string()).into_single_span(ident_position); - // Skip whitespace - let whitespace_position = ident_position + 1; + // Skip whitespace + let whitespace_position = ident_position + 1; - // Assign position - let assign_position = whitespace_position + 1; - let assign_token = Token::Assign.into_single_span(assign_position); + // Assign position + let assign_position = whitespace_position + 1; + let assign_token = Token::Assign.into_single_span(assign_position); - // Skip whitespace - let whitespace_position = assign_position + 1; + // Skip whitespace + let whitespace_position = assign_position + 1; - // Int position - let int_position = whitespace_position + 1; - let int_token = Token::Int(5_i128.into()).into_single_span(int_position); + // Int position + let int_position = whitespace_position + 1; + let int_token = Token::Int(5_i128.into()).into_single_span(int_position); - let expected = vec![let_token, ident_token, assign_token, int_token]; - let mut lexer = Lexer::new(input); + let expected = vec![let_token, ident_token, assign_token, int_token]; + let mut lexer = Lexer::new(input); - for spanned_token in expected.into_iter() { - let got = lexer.next_token().unwrap(); - assert_eq!(got.to_span(), spanned_token.to_span()); - assert_eq!(got, spanned_token); + for spanned_token in expected.into_iter() { + let got = lexer.next_token().unwrap(); + assert_eq!(got.to_span(), spanned_token.to_span()); + assert_eq!(got, spanned_token); + } } -} -#[test] -fn test_basic_language_syntax() { - let input = " + #[test] + fn test_basic_language_syntax() { + let input = " let five = 5; let ten : Field = 10; let mul = fn(x, y) { @@ -679,60 +726,61 @@ fn test_basic_language_syntax() { assert(ten + five == 15); "; - let expected = vec![ - Token::Keyword(Keyword::Let), - Token::Ident("five".to_string()), - Token::Assign, - Token::Int(5_i128.into()), - Token::Semicolon, - Token::Keyword(Keyword::Let), - Token::Ident("ten".to_string()), - Token::Colon, - Token::Keyword(Keyword::Field), - Token::Assign, - Token::Int(10_i128.into()), - Token::Semicolon, - Token::Keyword(Keyword::Let), - Token::Ident("mul".to_string()), - Token::Assign, - Token::Keyword(Keyword::Fn), - Token::LeftParen, - Token::Ident("x".to_string()), - Token::Comma, - Token::Ident("y".to_string()), - Token::RightParen, - Token::LeftBrace, - Token::Ident("x".to_string()), - Token::Star, - Token::Ident("y".to_string()), - Token::Semicolon, - Token::RightBrace, - Token::Semicolon, - Token::Keyword(Keyword::Constrain), - Token::Ident("mul".to_string()), - Token::LeftParen, - Token::Ident("five".to_string()), - Token::Comma, - Token::Ident("ten".to_string()), - Token::RightParen, - Token::Equal, - Token::Int(50_i128.into()), - Token::Semicolon, - Token::Keyword(Keyword::Assert), - Token::LeftParen, - Token::Ident("ten".to_string()), - Token::Plus, - Token::Ident("five".to_string()), - Token::Equal, - Token::Int(15_i128.into()), - Token::RightParen, - Token::Semicolon, - Token::EOF, - ]; - let mut lexer = Lexer::new(input); - - for token in expected.into_iter() { - let got = lexer.next_token().unwrap(); - assert_eq!(got, token); + let expected = vec![ + Token::Keyword(Keyword::Let), + Token::Ident("five".to_string()), + Token::Assign, + Token::Int(5_i128.into()), + Token::Semicolon, + Token::Keyword(Keyword::Let), + Token::Ident("ten".to_string()), + Token::Colon, + Token::Keyword(Keyword::Field), + Token::Assign, + Token::Int(10_i128.into()), + Token::Semicolon, + Token::Keyword(Keyword::Let), + Token::Ident("mul".to_string()), + Token::Assign, + Token::Keyword(Keyword::Fn), + Token::LeftParen, + Token::Ident("x".to_string()), + Token::Comma, + Token::Ident("y".to_string()), + Token::RightParen, + Token::LeftBrace, + Token::Ident("x".to_string()), + Token::Star, + Token::Ident("y".to_string()), + Token::Semicolon, + Token::RightBrace, + Token::Semicolon, + Token::Keyword(Keyword::Constrain), + Token::Ident("mul".to_string()), + Token::LeftParen, + Token::Ident("five".to_string()), + Token::Comma, + Token::Ident("ten".to_string()), + Token::RightParen, + Token::Equal, + Token::Int(50_i128.into()), + Token::Semicolon, + Token::Keyword(Keyword::Assert), + Token::LeftParen, + Token::Ident("ten".to_string()), + Token::Plus, + Token::Ident("five".to_string()), + Token::Equal, + Token::Int(15_i128.into()), + Token::RightParen, + Token::Semicolon, + Token::EOF, + ]; + let mut lexer = Lexer::new(input); + + for token in expected.into_iter() { + let got = lexer.next_token().unwrap(); + assert_eq!(got, token); + } } } diff --git a/crates/noirc_frontend/src/lexer/token.rs b/crates/noirc_frontend/src/lexer/token.rs index 6291ac4de1..5a47d4ece7 100644 --- a/crates/noirc_frontend/src/lexer/token.rs +++ b/crates/noirc_frontend/src/lexer/token.rs @@ -316,6 +316,36 @@ impl IntType { } } +/// TestScope is used to specify additional annotations for test functions +#[derive(PartialEq, Eq, Hash, Debug, Clone, PartialOrd, Ord)] +pub enum TestScope { + /// If a test has a scope of ShouldFail, then it is expected to fail + ShouldFail, + /// No scope is applied and so the test must pass + None, +} + +impl TestScope { + fn lookup_str(string: &str) -> Option { + match string { + "should_fail" => Some(TestScope::ShouldFail), + _ => None, + } + } + fn as_str(&self) -> &'static str { + match self { + TestScope::ShouldFail => "should_fail", + TestScope::None => "", + } + } +} + +impl fmt::Display for TestScope { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "({})", self.as_str()) + } +} + #[derive(PartialEq, Eq, Hash, Debug, Clone, PartialOrd, Ord)] // Attributes are special language markers in the target language // An example of one is `#[SHA256]` . Currently only Foreign attributes are supported @@ -325,17 +355,17 @@ pub enum Attribute { Builtin(String), Oracle(String), Deprecated(Option), - Test, + Test(TestScope), Custom(String), } impl fmt::Display for Attribute { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { + match self { Attribute::Foreign(ref k) => write!(f, "#[foreign({k})]"), Attribute::Builtin(ref k) => write!(f, "#[builtin({k})]"), Attribute::Oracle(ref k) => write!(f, "#[oracle({k})]"), - Attribute::Test => write!(f, "#[test]"), + Attribute::Test(scope) => write!(f, "#[test{}]", scope), Attribute::Deprecated(None) => write!(f, "#[deprecated]"), Attribute::Deprecated(Some(ref note)) => write!(f, r#"#[deprecated("{note}")]"#), Attribute::Custom(ref k) => write!(f, "#[{k}]"), @@ -391,7 +421,16 @@ impl Attribute { Attribute::Deprecated(name.trim_matches('"').to_string().into()) } - ["test"] => Attribute::Test, + ["test"] => Attribute::Test(TestScope::None), + ["test", name] => { + validate(name)?; + let malformed_scope = + LexerErrorKind::MalformedFuncAttribute { span, found: word.to_owned() }; + match TestScope::lookup_str(name) { + Some(scope) => Attribute::Test(scope), + None => return Err(malformed_scope), + } + } tokens => { tokens.iter().try_for_each(|token| validate(token))?; Attribute::Custom(word.to_owned()) @@ -431,7 +470,7 @@ impl AsRef for Attribute { Attribute::Builtin(string) => string, Attribute::Oracle(string) => string, Attribute::Deprecated(Some(string)) => string, - Attribute::Test | Attribute::Deprecated(None) => "", + Attribute::Test { .. } | Attribute::Deprecated(None) => "", Attribute::Custom(string) => string, } }