Skip to content

Commit

Permalink
Recover from '=' instead of ':' in struct constructor/pattern
Browse files Browse the repository at this point in the history
  • Loading branch information
asterite committed Oct 7, 2024
1 parent c4273a0 commit 5f5936e
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 1 deletion.
34 changes: 34 additions & 0 deletions compiler/noirc_frontend/src/parser/parser/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
})
Expand Down Expand Up @@ -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 }";
Expand Down
36 changes: 35 additions & 1 deletion compiler/noirc_frontend/src/parser/parser/pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
})
Expand Down Expand Up @@ -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,
},
Expand Down Expand Up @@ -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";
Expand Down

0 comments on commit 5f5936e

Please sign in to comment.