Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions compiler/noirc_frontend/src/lexer/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ impl<'a> Lexer<'a> {
self
}

pub fn set_skip_whitespaces_flag(&mut self, flag: bool) {
self.skip_whitespaces = flag;
}

/// Iterates the cursor and returns the char at the new cursor position
fn next_char(&mut self) -> Option<char> {
let (position, ch) = self.chars.next()?;
Expand Down
28 changes: 26 additions & 2 deletions compiler/noirc_frontend/src/parser/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,8 +355,15 @@ impl<'a> Parser<'a> {
}

fn eat_attribute_start(&mut self) -> Option<bool> {
if matches!(self.token.token(), Token::AttributeStart { is_inner: false, .. }) {
if let Token::AttributeStart { is_inner: false, is_tag } = self.token.token() {
// We have parsed the attribute start token `#[`.
// Disable the "skip whitespaces" flag only for tag attributes so that the next `self.bump()`
// does not consume the whitespace following the upcoming token.
if *is_tag {
self.set_lexer_skip_whitespaces_flag(false);
}
let token = self.bump();
self.set_lexer_skip_whitespaces_flag(true);
match token.into_token() {
Token::AttributeStart { is_tag, .. } => Some(is_tag),
_ => unreachable!(),
Expand All @@ -367,8 +374,15 @@ impl<'a> Parser<'a> {
}

fn eat_inner_attribute_start(&mut self) -> Option<bool> {
if matches!(self.token.token(), Token::AttributeStart { is_inner: true, .. }) {
if let Token::AttributeStart { is_inner: true, is_tag } = self.token.token() {
// We have parsed the inner attribute start token `#![`.
// Disable the "skip whitespaces" flag only for tag attributes so that the next `self.bump()`
// does not consume the whitespace following the upcoming token.
if *is_tag {
self.set_lexer_skip_whitespaces_flag(false);
}
let token = self.bump();
self.set_lexer_skip_whitespaces_flag(true);
match token.into_token() {
Token::AttributeStart { is_tag, .. } => Some(is_tag),
_ => unreachable!(),
Expand Down Expand Up @@ -479,6 +493,16 @@ impl<'a> Parser<'a> {
self.at(Token::Keyword(keyword))
}

fn at_whitespace(&self) -> bool {
matches!(self.token.token(), Token::Whitespace(_))
}

fn set_lexer_skip_whitespaces_flag(&mut self, flag: bool) {
if let TokenStream::Lexer(lexer) = &mut self.tokens {
lexer.set_skip_whitespaces_flag(flag);
};
}

fn next_is(&self, token: Token) -> bool {
self.next_token.token() == &token
}
Expand Down
62 changes: 62 additions & 0 deletions compiler/noirc_frontend/src/parser/parser/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ impl Parser<'_> {
let mut contents = String::new();

let mut brackets_count = 1; // 1 because of the starting `#[`
// Note: Keep trailing whitespace tokens.
// If we skip them, only non-whitespace tokens are parsed.
// When converting those tokens into a `String` for the tag attribute,
// the result will lose whitespace and no longer match the original content.
self.set_lexer_skip_whitespaces_flag(false);

while !self.at_eof() {
if self.at(Token::LeftBracket) {
Expand All @@ -126,6 +131,11 @@ impl Parser<'_> {
self.bump();
}

self.set_lexer_skip_whitespaces_flag(true);
while self.at_whitespace() {
self.bump();
}

let location = self.location_since(start_location);
let kind = SecondaryAttributeKind::Tag(contents);
let attr = SecondaryAttribute { kind, location };
Expand Down Expand Up @@ -790,4 +800,56 @@ mod tests {
};
assert!(matches!(attr.kind, SecondaryAttributeKind::Deprecated(None)));
}

#[test]
fn parses_inner_tag_attribute_with_whitespace() {
let src = "#!['hello world]";
let mut parser = Parser::for_str_with_dummy_file(src);
let SecondaryAttributeKind::Tag(contents) = parser.parse_inner_attribute().unwrap().kind
else {
panic!("Expected inner tag attribute");
};
expect_no_errors(&parser.errors);
assert_eq!(contents, "hello world");
}

#[test]
fn parses_inner_tag_attribute_with_multiple_whitespaces() {
let src = "#!['x as u32]";
let mut parser = Parser::for_str_with_dummy_file(src);
let SecondaryAttributeKind::Tag(contents) = parser.parse_inner_attribute().unwrap().kind
else {
panic!("Expected inner tag attribute");
};
expect_no_errors(&parser.errors);
assert_eq!(contents, "x as u32");
}
#[test]
fn parses_tag_attribute_with_multiple_whitespaces() {
let src = "#['y as i16]";
let mut parser = Parser::for_str_with_dummy_file(src);
let (attribute, _span) = parser.parse_attribute().unwrap();
expect_no_errors(&parser.errors);
let Attribute::Secondary(attribute) = attribute else {
panic!("Expected secondary attribute");
};
let SecondaryAttributeKind::Tag(contents) = attribute.kind else {
panic!("Expected meta attribute");
};
assert_eq!(contents, "y as i16");
}
#[test]
fn parses_tag_attribute_with_whitespace() {
let src = "#['foo bar]";
let mut parser = Parser::for_str_with_dummy_file(src);
let (attribute, _span) = parser.parse_attribute().unwrap();
expect_no_errors(&parser.errors);
let Attribute::Secondary(attribute) = attribute else {
panic!("Expected secondary attribute");
};
let SecondaryAttributeKind::Tag(contents) = attribute.kind else {
panic!("Expected meta attribute");
};
assert_eq!(contents, "foo bar");
}
}
7 changes: 7 additions & 0 deletions tooling/nargo_fmt/src/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,13 @@ impl<'a> Formatter<'a> {
self.write(&self.source[span.start() as usize..span.end() as usize]);
}

/// Writes whatever is in the given span relative to the file's source that's being formatted
/// but trims the whitespaces at the end.
pub(crate) fn write_source_span_trimmed(&mut self, span: Span) {
let source = self.source[span.start() as usize..span.end() as usize].trim_end();
self.write(source);
}

/// Writes the current indentation to the buffer, but only if the buffer
/// is empty or it ends with a newline (otherwise we'd be indenting when not needed).
pub(crate) fn write_indentation(&mut self) {
Expand Down
19 changes: 18 additions & 1 deletion tooling/nargo_fmt/src/formatter/attribute.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use noirc_errors::Span;
use noirc_frontend::token::{
Attribute, Attributes, FunctionAttribute, FunctionAttributeKind, FuzzingScope, MetaAttribute,
MetaAttributeName, SecondaryAttribute, SecondaryAttributeKind, TestScope, Token,
Expand Down Expand Up @@ -97,7 +98,7 @@ impl Formatter<'_> {
self.format_one_arg_attribute();
}
SecondaryAttributeKind::Tag(_) => {
self.write_and_skip_span_without_formatting(attribute.location.span);
self.format_tag_attribute(attribute.location.span);
}
SecondaryAttributeKind::Meta(meta_attribute) => {
self.format_meta_attribute(meta_attribute);
Expand Down Expand Up @@ -248,6 +249,15 @@ impl Formatter<'_> {
}
self.write_right_bracket(); // ]
}

/// Removes the trailing whitespaces from the tag attribute.
fn format_tag_attribute(&mut self, span: Span) {
self.write_source_span_trimmed(span);

while self.token_span.start() < span.end() && self.token != Token::EOF {
self.bump();
}
}
}

#[cfg(test)]
Expand All @@ -267,6 +277,13 @@ mod tests {
assert_format(src, expected);
}

#[test]
fn format_inner_tag_attribute_new_line() {
let src = "#!['bar] \n";
let expected = "#!['bar]\n";
assert_format(src, expected);
}

#[test]
fn format_deprecated_attribute() {
let src = " #[ deprecated ] ";
Expand Down
Loading