diff --git a/compiler/parser-lossless/src/grammar.lalrpop b/compiler/parser-lossless/src/grammar.lalrpop index 68f3b1cca6c..03c143c3873 100644 --- a/compiler/parser-lossless/src/grammar.lalrpop +++ b/compiler/parser-lossless/src/grammar.lalrpop @@ -861,7 +861,7 @@ extern { Arrow => LalrToken{ token: Token::Arrow, .. }, BigArrow => LalrToken{ token: Token::BigArrow, .. }, At => LalrToken{ token: Token::At, .. }, - // Underscore => tokens::Token::Underscore, + Underscore => LalrToken { token: Token::Underscore, .. }, // Keywords True => LalrToken { token: Token::True, .. }, diff --git a/compiler/parser-lossless/src/tokens.rs b/compiler/parser-lossless/src/tokens.rs index db9f072eeab..6ec1cfd1a07 100644 --- a/compiler/parser-lossless/src/tokens.rs +++ b/compiler/parser-lossless/src/tokens.rs @@ -101,6 +101,11 @@ pub enum Token { // with an LR(1) parser - we potentially get shift-reduce conflicts and other ambiguities between // member accesses, program ids, tuple accesses, etc. We could make it work but let's just cut to the // chase here. + + // Catch identifiers starting with underscore + #[regex(r"_[a-zA-Z][a-zA-Z0-9_]*", priority = 10)] + InvalidLeadingUnderscoreIdent, + #[regex(r"[a-zA-Z][a-zA-Z0-9_]*", id_variant)] // We need to special case `group::abc` and `signature::abc` as otherwise these are keywords. #[token(r"group::[a-zA-Z][a-zA-Z0-9_]*", |_| IdVariants::Path)] @@ -514,6 +519,9 @@ impl<'a> Iterator for Lexer<'a> { if matches!(token, Token::Bidi) { self.handler.emit_err(ParserError::lexer_bidi_override_span(span)); return None; + } else if matches!(token, Token::InvalidLeadingUnderscoreIdent) { + self.handler.emit_err(ParserError::identifier_cannot_start_with_underscore(span)); + return None; } else if matches!(token, Token::Integer) { let (s, radix) = if let Some(s) = text.strip_prefix("0x") { (s, 16) diff --git a/errors/src/errors/parser/parser_errors.rs b/errors/src/errors/parser/parser_errors.rs index 388f3efa411..40731c09a2e 100644 --- a/errors/src/errors/parser/parser_errors.rs +++ b/errors/src/errors/parser/parser_errors.rs @@ -471,4 +471,11 @@ create_messages!( msg: format!("Digit {digit} invalid in radix {radix} (token {token})."), help: None, } + + @formatted + identifier_cannot_start_with_underscore { + args: (), + msg: "Identifiers cannot start with an underscore.", + help: Some("Identifiers must start with a letter.".to_string()), + } ); diff --git a/tests/expectations/parser/identifiers/leading_underscore_expression_fail.out b/tests/expectations/parser/identifiers/leading_underscore_expression_fail.out new file mode 100644 index 00000000000..2439c9e9c56 --- /dev/null +++ b/tests/expectations/parser/identifiers/leading_underscore_expression_fail.out @@ -0,0 +1,7 @@ +Error [EPAR0370053]: Identifiers cannot start with an underscore. + --> test:5:16 + | + 5 | return _value; + | ^^^^^^ + | + = Identifiers must start with a letter. diff --git a/tests/expectations/parser/identifiers/leading_underscore_fail.out b/tests/expectations/parser/identifiers/leading_underscore_fail.out new file mode 100644 index 00000000000..9f591a07eac --- /dev/null +++ b/tests/expectations/parser/identifiers/leading_underscore_fail.out @@ -0,0 +1,7 @@ +Error [EPAR0370053]: Identifiers cannot start with an underscore. + --> test:4:13 + | + 4 | let _c: u32 = a + b; + | ^^ + | + = Identifiers must start with a letter. diff --git a/tests/tests/parser/identifiers/leading_underscore_expression_fail.leo b/tests/tests/parser/identifiers/leading_underscore_expression_fail.leo new file mode 100644 index 00000000000..b7baa8ee4e7 --- /dev/null +++ b/tests/tests/parser/identifiers/leading_underscore_expression_fail.leo @@ -0,0 +1,12 @@ +program test.aleo { + // Tests underscore in expression context + transition main() -> u32 { + // this should error when lexer sees _value + return _value; + } + + function foo(_param: u32) -> u32 { + return _param; + } +} + diff --git a/tests/tests/parser/identifiers/leading_underscore_fail.leo b/tests/tests/parser/identifiers/leading_underscore_fail.leo new file mode 100644 index 00000000000..99029d9a2df --- /dev/null +++ b/tests/tests/parser/identifiers/leading_underscore_fail.leo @@ -0,0 +1,13 @@ +program test.aleo { + transition main(a: u32, b: u32) -> u32 { + // single leading underscore, typed + let _c: u32 = a + b; + + // single leading underscore, untyped + let _temp = 10u32; + + // using underscore-prefixed variable in expression + return _c + _temp; + } +} +