diff --git a/compiler/noirc_frontend/src/lexer/lexer.rs b/compiler/noirc_frontend/src/lexer/lexer.rs index 2d3719a49f6..a25fe6e8d80 100644 --- a/compiler/noirc_frontend/src/lexer/lexer.rs +++ b/compiler/noirc_frontend/src/lexer/lexer.rs @@ -256,9 +256,8 @@ impl<'a> Lexer<'a> { if self.peek_char_is('=') { self.next_char(); Ok(Token::LessEqual.into_span(start, start + 1)) - } else if self.peek_char_is('<') { - self.next_char(); - Ok(Token::ShiftLeft.into_span(start, start + 1)) + // Note: There is deliberately no case for ShiftLeft. We always lex << as + // two separate Less tokens to help the parser parse nested generic types. } else { Ok(prev_token.into_single_span(start)) } @@ -963,7 +962,8 @@ mod tests { Token::Star, Token::Assign, Token::Equal, - Token::ShiftLeft, + Token::Less, + Token::Less, Token::Greater, Token::Greater, Token::EOF, diff --git a/compiler/noirc_frontend/src/parser/parser/infix.rs b/compiler/noirc_frontend/src/parser/parser/infix.rs index 10a94932782..c4a48101c78 100644 --- a/compiler/noirc_frontend/src/parser/parser/infix.rs +++ b/compiler/noirc_frontend/src/parser/parser/infix.rs @@ -118,7 +118,8 @@ impl Parser<'_> { parse_infix!( self, Parser::parse_shift, - if self.eat(Token::Less) { + if self.next_token.token() != &Token::LessEqual && self.eat(Token::Less) { + // Make sure to skip the `<<=` case, as `<<=` is lexed as `< <=`. BinaryOpKind::Less } else if self.eat(Token::LessEqual) { BinaryOpKind::LessEqual @@ -141,7 +142,12 @@ impl Parser<'_> { parse_infix!( self, Parser::parse_add_or_subtract, - if !self.next_is(Token::Assign) && self.eat(Token::ShiftLeft) { + if self.at(Token::Less) && self.next_is(Token::Less) { + // Left-shift (<<) is issued as two separate < tokens by the lexer as this makes it easier + // to parse nested generic types. For normal expressions however, it means we have to manually + // parse two less-than tokens as a single left-shift here. + self.bump(); + self.bump(); BinaryOpKind::ShiftLeft } else if self.at(Token::Greater) && self.next_is(Token::Greater) { // Right-shift (>>) is issued as two separate > tokens by the lexer as this makes it easier diff --git a/compiler/noirc_frontend/src/parser/parser/statement.rs b/compiler/noirc_frontend/src/parser/parser/statement.rs index 90d12c37327..5d303913ea7 100644 --- a/compiler/noirc_frontend/src/parser/parser/statement.rs +++ b/compiler/noirc_frontend/src/parser/parser/statement.rs @@ -234,13 +234,15 @@ impl Parser<'_> { Token::Percent => Some(BinaryOpKind::Modulo), Token::Ampersand => Some(BinaryOpKind::And), Token::Caret => Some(BinaryOpKind::Xor), - Token::ShiftLeft => Some(BinaryOpKind::ShiftLeft), Token::Pipe => Some(BinaryOpKind::Or), _ => None, } } else if self.at(Token::Greater) && self.next_is(Token::GreaterEqual) { // >>= Some(BinaryOpKind::ShiftRight) + } else if self.at(Token::Less) && self.next_is(Token::LessEqual) { + // <<= + Some(BinaryOpKind::ShiftLeft) } else { None }; diff --git a/compiler/noirc_frontend/src/tests/traits/trait_associated_items.rs b/compiler/noirc_frontend/src/tests/traits/trait_associated_items.rs index dff4dd5121f..09add80e7a0 100644 --- a/compiler/noirc_frontend/src/tests/traits/trait_associated_items.rs +++ b/compiler/noirc_frontend/src/tests/traits/trait_associated_items.rs @@ -1628,9 +1628,8 @@ fn associated_type_shorthand_in_param_position() { assert_no_errors(src); } -/// TODO(https://github.com/noir-lang/noir/issues/11549): remove should_panic once fixed +/// Regression test for https://github.com/noir-lang/noir/issues/11549 #[test] -#[should_panic(expected = "Expected no errors")] fn nested_associated_type_access_fails() { // Bug: nested associated type resolution fails let src = r#" diff --git a/compiler/noirc_frontend/src/tests/traits/trait_bounds.rs b/compiler/noirc_frontend/src/tests/traits/trait_bounds.rs index 9996e545b87..06fc3b2a796 100644 --- a/compiler/noirc_frontend/src/tests/traits/trait_bounds.rs +++ b/compiler/noirc_frontend/src/tests/traits/trait_bounds.rs @@ -650,6 +650,28 @@ fn where_clause_on_self_type_with_generic() { assert_no_errors(src); } +// Regression test for https://github.com/noir-lang/noir/issues/11553 +#[test] +fn nested_angle_brackets_in_type_position() { + let src = r#" + pub trait HasKey { + type Key; + } + + pub struct Store { + key: K, + } + + pub fn make_store(key: ::Key) -> Store<::Key> + where + T: HasKey, + { + Store { key } + } + "#; + assert_no_errors(src); +} + #[test] fn where_clause_on_constructed_generic_type() { let src = r#" @@ -1316,9 +1338,8 @@ fn where_clause_on_associated_type_of_generic_in_trait_impl() { assert_no_errors(src); } -/// TODO(https://github.com/noir-lang/noir/issues/11553): remove should_panic once fixed +/// Regression test for https://github.com/noir-lang/noir/issues/11553 #[test] -#[should_panic(expected = "Expected no errors")] fn associated_type_as_generic_trait_param_with_nested_angle_brackets() { // Bug: Parser fails on << in type position: Store<::Key> let src = r#" diff --git a/tooling/nargo_fmt/src/formatter/expression.rs b/tooling/nargo_fmt/src/formatter/expression.rs index e9f35ef3c7a..ba890ff9896 100644 --- a/tooling/nargo_fmt/src/formatter/expression.rs +++ b/tooling/nargo_fmt/src/formatter/expression.rs @@ -811,8 +811,14 @@ impl ChunkFormatter<'_, '_> { group.space_or_line(); group.text(self.chunk(|formatter| { - let tokens_count = - if infix.operator.contents == BinaryOpKind::ShiftRight { 2 } else { 1 }; + let tokens_count = if matches!( + infix.operator.contents, + BinaryOpKind::ShiftRight | BinaryOpKind::ShiftLeft + ) { + 2 + } else { + 1 + }; for _ in 0..tokens_count { formatter.write_current_token(); formatter.bump();