diff --git a/compiler/noirc_frontend/src/hir/comptime/display.rs b/compiler/noirc_frontend/src/hir/comptime/display.rs index 38bc6a21f6f..f64263feb73 100644 --- a/compiler/noirc_frontend/src/hir/comptime/display.rs +++ b/compiler/noirc_frontend/src/hir/comptime/display.rs @@ -262,7 +262,8 @@ impl<'interner> TokenPrettyPrinter<'interner> { | Token::Ampersand | Token::SliceStart | Token::ShiftLeft - | Token::ShiftRight => { + | Token::ShiftRight + | Token::LogicalAnd => { self.last_was_op = true; write!(f, " {token}") } diff --git a/compiler/noirc_frontend/src/lexer/errors.rs b/compiler/noirc_frontend/src/lexer/errors.rs index 859419b748a..004eea05439 100644 --- a/compiler/noirc_frontend/src/lexer/errors.rs +++ b/compiler/noirc_frontend/src/lexer/errors.rs @@ -26,8 +26,6 @@ pub enum LexerErrorKind { MalformedFuzzAttribute { location: Location }, #[error("{:?} is not a valid inner attribute", found)] InvalidInnerAttribute { location: Location, found: String }, - #[error("Logical and used instead of bitwise and")] - LogicalAnd { location: Location }, #[error("Unterminated block comment")] UnterminatedBlockComment { location: Location }, #[error("Unterminated string literal")] @@ -72,7 +70,6 @@ impl LexerErrorKind { LexerErrorKind::MalformedTestAttribute { location, .. } => *location, LexerErrorKind::MalformedFuzzAttribute { location, .. } => *location, LexerErrorKind::InvalidInnerAttribute { location, .. } => *location, - LexerErrorKind::LogicalAnd { location } => *location, LexerErrorKind::UnterminatedBlockComment { location } => *location, LexerErrorKind::UnterminatedStringLiteral { location } => *location, LexerErrorKind::InvalidFormatString { location, .. } => *location, @@ -136,11 +133,6 @@ impl LexerErrorKind { format!(" {found} is not a valid inner attribute"), *location, ), - LexerErrorKind::LogicalAnd { location } => ( - "Noir has no logical-and (&&) operator since short-circuiting is much less efficient when compiling to circuits".to_string(), - "Try `&` instead, or use `if` only if you require short-circuiting".to_string(), - *location, - ), LexerErrorKind::UnterminatedBlockComment { location } => ("Unterminated block comment".to_string(), "Unterminated block comment".to_string(), *location), LexerErrorKind::UnterminatedStringLiteral { location } => ("Unterminated string literal".to_string(), "Unterminated string literal".to_string(), *location), diff --git a/compiler/noirc_frontend/src/lexer/lexer.rs b/compiler/noirc_frontend/src/lexer/lexer.rs index a6657d63866..6985276a5f7 100644 --- a/compiler/noirc_frontend/src/lexer/lexer.rs +++ b/compiler/noirc_frontend/src/lexer/lexer.rs @@ -102,10 +102,9 @@ impl<'a> Lexer<'a> { fn ampersand(&mut self) -> SpannedTokenResult { if self.peek_char_is('&') { - // When we issue this error the first '&' will already be consumed - // and the next token issued will be the next '&'. - let span = Span::inclusive(self.position, self.position + 1); - Err(LexerErrorKind::LogicalAnd { location: self.location(span) }) + let start = self.position; + self.next_char(); + Ok(Token::LogicalAnd.into_span(start, start + 1)) } else if self.peek_char_is('[') { self.single_char_token(Token::SliceStart) } else { diff --git a/compiler/noirc_frontend/src/lexer/token.rs b/compiler/noirc_frontend/src/lexer/token.rs index 976dde05776..582beda4aee 100644 --- a/compiler/noirc_frontend/src/lexer/token.rs +++ b/compiler/noirc_frontend/src/lexer/token.rs @@ -111,6 +111,8 @@ pub enum BorrowedToken<'input> { DollarSign, /// = Assign, + /// && + LogicalAnd, #[allow(clippy::upper_case_acronyms)] EOF, @@ -238,6 +240,8 @@ pub enum Token { Assign, /// $ DollarSign, + /// && + LogicalAnd, #[allow(clippy::upper_case_acronyms)] EOF, @@ -315,6 +319,7 @@ pub fn token_to_borrowed_token(token: &Token) -> BorrowedToken<'_> { Token::Assign => BorrowedToken::Assign, Token::Bang => BorrowedToken::Bang, Token::DollarSign => BorrowedToken::DollarSign, + Token::LogicalAnd => BorrowedToken::LogicalAnd, Token::EOF => BorrowedToken::EOF, Token::Invalid(c) => BorrowedToken::Invalid(*c), Token::Whitespace(s) => BorrowedToken::Whitespace(s), @@ -551,6 +556,7 @@ impl fmt::Display for Token { Token::Assign => write!(f, "="), Token::Bang => write!(f, "!"), Token::DollarSign => write!(f, "$"), + Token::LogicalAnd => write!(f, "&&"), Token::EOF => write!(f, "end of input"), Token::Invalid(c) => write!(f, "{c}"), Token::Whitespace(ref s) => write!(f, "{s}"), diff --git a/compiler/noirc_frontend/src/parser/errors.rs b/compiler/noirc_frontend/src/parser/errors.rs index 1e78504e202..1c3c65cfe1e 100644 --- a/compiler/noirc_frontend/src/parser/errors.rs +++ b/compiler/noirc_frontend/src/parser/errors.rs @@ -123,6 +123,8 @@ pub enum ParserErrorReason { MissingAngleBrackets, #[error("Expected value, found built-in type `{typ}`")] ExpectedValueFoundBuiltInType { typ: UnresolvedType }, + #[error("Logical and used instead of bitwise and")] + LogicalAnd, } /// Represents a parsing error, or a parsing error in the making. @@ -321,6 +323,13 @@ impl<'a> From<&'a ParserError> for Diagnostic { let secondary = "Types that don't start with an identifier need to be surrounded with angle brackets: `<`, `>`".to_string(); Diagnostic::simple_error(format!("{reason}"), secondary, error.location()) } + ParserErrorReason::LogicalAnd => { + let primary = "Noir has no logical-and (&&) operator since short-circuiting is much less efficient when compiling to circuits".to_string(); + let secondary = + "Try `&` instead, or use `if` only if you require short-circuiting" + .to_string(); + Diagnostic::simple_error(primary, secondary, error.location) + } other => { Diagnostic::simple_error(format!("{other}"), String::new(), error.location()) } diff --git a/compiler/noirc_frontend/src/parser/parser/expression.rs b/compiler/noirc_frontend/src/parser/parser/expression.rs index ce050e344d1..4594e3f529d 100644 --- a/compiler/noirc_frontend/src/parser/parser/expression.rs +++ b/compiler/noirc_frontend/src/parser/parser/expression.rs @@ -1836,6 +1836,21 @@ mod tests { assert_eq!(infix_expr.to_string(), expected_src); } + #[test] + fn errors_on_logical_and() { + let src = " + 1 && 2 + ^^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str_with_dummy_file(&src); + let expression = parser.parse_expression_or_error(); + assert_eq!(expression.to_string(), "(1 & 2)"); + + let reason = get_single_error_reason(&parser.errors, span); + assert!(matches!(reason, ParserErrorReason::LogicalAnd)); + } + #[test] fn parses_empty_lambda() { let src = "|| 1"; diff --git a/compiler/noirc_frontend/src/parser/parser/infix.rs b/compiler/noirc_frontend/src/parser/parser/infix.rs index c900228cc86..f4db01fd9de 100644 --- a/compiler/noirc_frontend/src/parser/parser/infix.rs +++ b/compiler/noirc_frontend/src/parser/parser/infix.rs @@ -2,6 +2,7 @@ use noirc_errors::{Located, Location}; use crate::{ ast::{BinaryOpKind, Expression, ExpressionKind, InfixExpression}, + parser::ParserErrorReason, token::Token, }; @@ -49,6 +50,9 @@ impl<'a> Parser<'a> { None } else if parser.eat(Token::Ampersand) { Some(BinaryOpKind::And) + } else if parser.eat(Token::LogicalAnd) { + parser.push_error(ParserErrorReason::LogicalAnd, parser.previous_token_location); + Some(BinaryOpKind::And) } else { None } diff --git a/compiler/noirc_frontend/src/parser/parser/types.rs b/compiler/noirc_frontend/src/parser/parser/types.rs index e8cd39fa3ca..d07294e2c9d 100644 --- a/compiler/noirc_frontend/src/parser/parser/types.rs +++ b/compiler/noirc_frontend/src/parser/parser/types.rs @@ -59,7 +59,7 @@ impl Parser<'_> { return Some(typ); } - if let Some(typ) = self.parses_mutable_reference_type() { + if let Some(typ) = self.parse_reference_type() { return Some(typ); } @@ -371,7 +371,20 @@ impl Parser<'_> { None } - fn parses_mutable_reference_type(&mut self) -> Option { + fn parse_reference_type(&mut self) -> Option { + let start_location = self.current_token_location; + + // This is '&&', which in this context is a double reference type + if self.eat(Token::LogicalAnd) { + let mutable = self.eat_keyword(Keyword::Mut); + let inner_type = + UnresolvedTypeData::Reference(Box::new(self.parse_type_or_error()), mutable); + let inner_type = + UnresolvedType { typ: inner_type, location: self.location_since(start_location) }; + let typ = UnresolvedTypeData::Reference(Box::new(inner_type), false /* mutable */); + return Some(typ); + } + // The `&` may be lexed as a slice start if this is an array or slice type if self.eat(Token::Ampersand) || self.eat(Token::SliceStart) { let mutable = self.eat_keyword(Keyword::Mut); @@ -627,6 +640,26 @@ mod tests { assert!(matches!(typ.typ, UnresolvedTypeData::FieldElement)); } + #[test] + fn parses_double_reference_type() { + let src = "&&Field"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::Reference(typ, false) = typ.typ else { + panic!("Expected a reference type") + }; + assert_eq!(typ.typ.to_string(), "&Field"); + } + + #[test] + fn parses_double_reference_mutable_type() { + let src = "&&mut Field"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::Reference(typ, false) = typ.typ else { + panic!("Expected a reference type") + }; + assert_eq!(typ.typ.to_string(), "&mut Field"); + } + #[test] fn parses_named_type_no_generics() { let src = "foo::Bar"; @@ -670,6 +703,20 @@ mod tests { assert_eq!(expr.to_string(), "10"); } + #[test] + fn parses_reference_to_array_type() { + let src = "&[Field; 10]"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::Reference(typ, false) = typ.typ else { + panic!("Expected a reference typ"); + }; + let UnresolvedTypeData::Array(expr, typ) = typ.typ else { + panic!("Expected an array type") + }; + assert!(matches!(typ.typ, UnresolvedTypeData::FieldElement)); + assert_eq!(expr.to_string(), "10"); + } + #[test] fn parses_empty_function_type() { let src = "fn() -> Field";