diff --git a/compiler/noirc_frontend/src/parser/parser/expression.rs b/compiler/noirc_frontend/src/parser/parser/expression.rs index e2b942faebf..1a150d881de 100644 --- a/compiler/noirc_frontend/src/parser/parser/expression.rs +++ b/compiler/noirc_frontend/src/parser/parser/expression.rs @@ -428,6 +428,12 @@ impl<'a> Parser<'a> { Some(if self.eat_colon() { let expression = self.parse_expression_or_error(); (ident, expression) + } else if self.at(Token::Assign) { + // If we find '=' instead of ':', assume the user meant ':`, error and continue + self.expected_token(Token::Colon); + self.bump(); + let expression = self.parse_expression_or_error(); + (ident, expression) } else { (ident.clone(), ident.into()) }) @@ -1337,6 +1343,34 @@ mod tests { assert_eq!(expr.to_string(), "2"); } + #[test] + fn parses_constructor_with_fields_recovers_if_assign_instead_of_colon() { + let src = " + Foo { x = 1, y } + ^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + let expr = parser.parse_expression_or_error(); + + let error = get_single_error(&parser.errors, span); + assert_eq!(error.to_string(), "Expected a : but found ="); + + let ExpressionKind::Constructor(mut constructor) = expr.kind else { + panic!("Expected constructor"); + }; + assert_eq!(constructor.typ.to_string(), "Foo"); + assert_eq!(constructor.fields.len(), 2); + + let (name, expr) = constructor.fields.remove(0); + assert_eq!(name.to_string(), "x"); + assert_eq!(expr.to_string(), "1"); + + let (name, expr) = constructor.fields.remove(0); + assert_eq!(name.to_string(), "y"); + assert_eq!(expr.to_string(), "y"); + } + #[test] fn parses_parses_if_true() { let src = "if true { 1 }"; diff --git a/compiler/noirc_frontend/src/parser/parser/pattern.rs b/compiler/noirc_frontend/src/parser/parser/pattern.rs index a10fe18fd79..72dc0f2ea07 100644 --- a/compiler/noirc_frontend/src/parser/parser/pattern.rs +++ b/compiler/noirc_frontend/src/parser/parser/pattern.rs @@ -217,6 +217,11 @@ impl<'a> Parser<'a> { Some(if self.eat_colon() { (ident, self.parse_pattern_or_error()) + } else if self.at(Token::Assign) { + // If we find '=' instead of ':', assume the user meant ':`, error and continue + self.expected_token(Token::Colon); + self.bump(); + (ident, self.parse_pattern_or_error()) } else { (ident.clone(), Pattern::Identifier(ident)) }) @@ -252,7 +257,8 @@ mod tests { ast::Pattern, parser::{ parser::tests::{ - expect_no_errors, get_single_error_reason, get_source_with_error_span, + expect_no_errors, get_single_error, get_single_error_reason, + get_source_with_error_span, }, Parser, ParserErrorReason, }, @@ -342,6 +348,34 @@ mod tests { assert_eq!(pattern.to_string(), "y"); } + #[test] + fn parses_struct_pattern_recovers_if_assign_instead_of_colon() { + let src = " + foo::Bar { x = one, y } + ^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + let pattern = parser.parse_pattern_or_error(); + + let error = get_single_error(&parser.errors, span); + assert_eq!(error.to_string(), "Expected a : but found ="); + + let Pattern::Struct(path, mut patterns, _) = pattern else { + panic!("Expected a struct pattern") + }; + assert_eq!(path.to_string(), "foo::Bar"); + assert_eq!(patterns.len(), 2); + + let (ident, pattern) = patterns.remove(0); + assert_eq!(ident.to_string(), "x"); + assert_eq!(pattern.to_string(), "one"); + + let (ident, pattern) = patterns.remove(0); + assert_eq!(ident.to_string(), "y"); + assert_eq!(pattern.to_string(), "y"); + } + #[test] fn parses_unclosed_struct_pattern() { let src = "foo::Bar { x";