diff --git a/compiler/noirc_driver/src/lib.rs b/compiler/noirc_driver/src/lib.rs index c4e2491a5fe..d10029d81e6 100644 --- a/compiler/noirc_driver/src/lib.rs +++ b/compiler/noirc_driver/src/lib.rs @@ -456,9 +456,8 @@ fn compile_contract_inner( .secondary .iter() .filter_map(|attr| match attr { - SecondaryAttribute::Tag(attribute) | SecondaryAttribute::Meta(attribute) => { - Some(attribute.contents.clone()) - } + SecondaryAttribute::Tag(attribute) => Some(attribute.contents.clone()), + SecondaryAttribute::Meta(attribute) => Some(attribute.to_string()), _ => None, }) .collect(); diff --git a/compiler/noirc_frontend/src/ast/expression.rs b/compiler/noirc_frontend/src/ast/expression.rs index 67ddffe3277..17fae63bc35 100644 --- a/compiler/noirc_frontend/src/ast/expression.rs +++ b/compiler/noirc_frontend/src/ast/expression.rs @@ -504,7 +504,7 @@ impl FunctionDefinition { } pub fn is_test(&self) -> bool { - if let Some(attribute) = &self.attributes.function { + if let Some(attribute) = self.attributes.function() { matches!(attribute, FunctionAttribute::Test(..)) } else { false diff --git a/compiler/noirc_frontend/src/ast/function.rs b/compiler/noirc_frontend/src/ast/function.rs index 70d2b3dbb39..52ca5630d01 100644 --- a/compiler/noirc_frontend/src/ast/function.rs +++ b/compiler/noirc_frontend/src/ast/function.rs @@ -78,7 +78,7 @@ impl NoirFunction { &self.def.attributes } pub fn function_attribute(&self) -> Option<&FunctionAttribute> { - self.def.attributes.function.as_ref() + self.def.attributes.function() } pub fn secondary_attributes(&self) -> &[SecondaryAttribute] { self.def.attributes.secondary.as_ref() @@ -109,7 +109,7 @@ impl NoirFunction { impl From for NoirFunction { fn from(fd: FunctionDefinition) -> Self { // The function type is determined by the existence of a function attribute - let kind = match fd.attributes.function { + let kind = match fd.attributes.function() { Some(FunctionAttribute::Builtin(_)) => FunctionKind::Builtin, Some(FunctionAttribute::Foreign(_)) => FunctionKind::LowLevel, Some(FunctionAttribute::Test { .. }) => FunctionKind::Normal, diff --git a/compiler/noirc_frontend/src/ast/visitor.rs b/compiler/noirc_frontend/src/ast/visitor.rs index 9e12b29677c..f149c998eca 100644 --- a/compiler/noirc_frontend/src/ast/visitor.rs +++ b/compiler/noirc_frontend/src/ast/visitor.rs @@ -16,7 +16,7 @@ use crate::{ InternedUnresolvedTypeData, QuotedTypeId, }, parser::{Item, ItemKind, ParsedSubModule}, - token::{CustomAttribute, SecondaryAttribute, Tokens}, + token::{MetaAttribute, SecondaryAttribute, Tokens}, ParsedModule, QuotedType, }; @@ -474,7 +474,9 @@ pub trait Visitor { true } - fn visit_custom_attribute(&mut self, _: &CustomAttribute, _target: AttributeTarget) {} + fn visit_meta_attribute(&mut self, _: &MetaAttribute, _target: AttributeTarget) -> bool { + true + } } impl ParsedModule { @@ -1441,15 +1443,22 @@ impl SecondaryAttribute { } pub fn accept_children(&self, target: AttributeTarget, visitor: &mut impl Visitor) { - if let SecondaryAttribute::Meta(custom) = self { - custom.accept(target, visitor); + if let SecondaryAttribute::Meta(meta_attribute) = self { + meta_attribute.accept(target, visitor); } } } -impl CustomAttribute { +impl MetaAttribute { pub fn accept(&self, target: AttributeTarget, visitor: &mut impl Visitor) { - visitor.visit_custom_attribute(self, target); + if visitor.visit_meta_attribute(self, target) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + self.name.accept(visitor); + visit_expressions(&self.arguments, visitor); } } diff --git a/compiler/noirc_frontend/src/elaborator/comptime.rs b/compiler/noirc_frontend/src/elaborator/comptime.rs index 13f51abe6ba..279adc331ea 100644 --- a/compiler/noirc_frontend/src/elaborator/comptime.rs +++ b/compiler/noirc_frontend/src/elaborator/comptime.rs @@ -19,10 +19,9 @@ use crate::{ resolution::errors::ResolverError, }, hir_def::expr::{HirExpression, HirIdent}, - lexer::Lexer, node_interner::{DefinitionKind, DependencyId, FuncId, NodeInterner, StructId, TraitId}, - parser::{Item, ItemKind, Parser}, - token::SecondaryAttribute, + parser::{Item, ItemKind}, + token::{MetaAttribute, SecondaryAttribute}, Type, TypeBindings, UnificationError, }; @@ -162,10 +161,9 @@ impl<'context> Elaborator<'context> { if let SecondaryAttribute::Meta(attribute) = attribute { self.elaborate_in_comptime_context(|this| { if let Err(error) = this.run_comptime_attribute_name_on_item( - &attribute.contents, + attribute, item.clone(), span, - attribute.contents_span, attribute_context, generated_items, ) { @@ -177,27 +175,21 @@ impl<'context> Elaborator<'context> { fn run_comptime_attribute_name_on_item( &mut self, - attribute: &str, + attribute: &MetaAttribute, item: Value, span: Span, - attribute_span: Span, attribute_context: AttributeContext, generated_items: &mut CollectedItems, ) -> Result<(), (CompilationError, FileId)> { self.file = attribute_context.attribute_file; self.local_module = attribute_context.attribute_module; - let location = Location::new(attribute_span, self.file); - let Some((function, arguments)) = Self::parse_attribute(attribute, location)? else { - return Err(( - ResolverError::UnableToParseAttribute { - attribute: attribute.to_string(), - span: attribute_span, - } - .into(), - self.file, - )); + let location = Location::new(attribute.span, self.file); + let function = Expression { + kind: ExpressionKind::Variable(attribute.name.clone()), + span: attribute.span, }; + let arguments = attribute.arguments.clone(); // Elaborate the function, rolling back any errors generated in case it is unknown let error_count = self.errors.len(); @@ -211,7 +203,7 @@ impl<'context> Elaborator<'context> { return Err(( ResolverError::AttributeFunctionIsNotAPath { function: function_string, - span: attribute_span, + span: attribute.span, } .into(), self.file, @@ -223,7 +215,7 @@ impl<'context> Elaborator<'context> { return Err(( ResolverError::AttributeFunctionNotInScope { name: function_string, - span: attribute_span, + span: attribute.span, } .into(), self.file, @@ -269,38 +261,6 @@ impl<'context> Elaborator<'context> { Ok(()) } - /// Parses an attribute in the form of a function call (e.g. `#[foo(a b, c d)]`) into - /// the function and quoted arguments called (e.g. `("foo", vec![(a b, location), (c d, location)])`) - #[allow(clippy::type_complexity)] - pub(crate) fn parse_attribute( - annotation: &str, - location: Location, - ) -> Result)>, (CompilationError, FileId)> { - let (tokens, mut lexing_errors) = Lexer::lex(annotation); - if !lexing_errors.is_empty() { - return Err((lexing_errors.swap_remove(0).into(), location.file)); - } - - let Some(expression) = Parser::for_tokens(tokens).parse_option(Parser::parse_expression) - else { - return Ok(None); - }; - - let (mut func, mut arguments) = match expression.kind { - ExpressionKind::Call(call) => (*call.func, call.arguments), - ExpressionKind::Variable(_) => (expression, Vec::new()), - _ => return Ok(None), - }; - - func.span = func.span.shift_by(location.span.start()); - - for argument in &mut arguments { - argument.span = argument.span.shift_by(location.span.start()); - } - - Ok(Some((func, arguments))) - } - fn handle_attribute_arguments( interpreter: &mut Interpreter, item: &Value, diff --git a/compiler/noirc_frontend/src/elaborator/lints.rs b/compiler/noirc_frontend/src/elaborator/lints.rs index 57db2359772..8b8c331b9d3 100644 --- a/compiler/noirc_frontend/src/elaborator/lints.rs +++ b/compiler/noirc_frontend/src/elaborator/lints.rs @@ -67,7 +67,7 @@ pub(super) fn low_level_function_outside_stdlib( crate_id: CrateId, ) -> Option { let is_low_level_function = - modifiers.attributes.function.as_ref().map_or(false, |func| func.is_low_level()); + modifiers.attributes.function().map_or(false, |func| func.is_low_level()); if !crate_id.is_stdlib() && is_low_level_function { let ident = func_meta_name_ident(func, modifiers); Some(ResolverError::LowLevelFunctionOutsideOfStdlib { ident }) @@ -81,8 +81,7 @@ pub(super) fn oracle_not_marked_unconstrained( func: &FuncMeta, modifiers: &FunctionModifiers, ) -> Option { - let is_oracle_function = - modifiers.attributes.function.as_ref().map_or(false, |func| func.is_oracle()); + let is_oracle_function = modifiers.attributes.function().map_or(false, |func| func.is_oracle()); if is_oracle_function && !modifiers.is_unconstrained { let ident = func_meta_name_ident(func, modifiers); Some(ResolverError::OracleMarkedAsConstrained { ident }) @@ -105,8 +104,7 @@ pub(super) fn oracle_called_from_constrained_function( } let function_attributes = interner.function_attributes(called_func); - let is_oracle_call = - function_attributes.function.as_ref().map_or(false, |func| func.is_oracle()); + let is_oracle_call = function_attributes.function().map_or(false, |func| func.is_oracle()); if is_oracle_call { Some(ResolverError::UnconstrainedOracleReturnToConstrained { span }) } else { diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index b58d552d866..824be3a4ed6 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -38,7 +38,7 @@ use crate::{ DefinitionKind, DependencyId, ExprId, FuncId, FunctionModifiers, GlobalId, NodeInterner, ReferenceId, StructId, TraitId, TraitImplId, TypeAliasId, }, - token::{CustomAttribute, SecondaryAttribute}, + token::SecondaryAttribute, Shared, Type, TypeVariable, }; @@ -839,11 +839,6 @@ impl<'context> Elaborator<'context> { None }; - let attributes = func.secondary_attributes().iter(); - let attributes = - attributes.filter_map(|secondary_attribute| secondary_attribute.as_custom()); - let attributes: Vec = attributes.cloned().collect(); - let meta = FuncMeta { name: name_ident, kind: func.kind, @@ -867,7 +862,6 @@ impl<'context> Elaborator<'context> { function_body: FunctionBody::Unresolved(func.kind, body, func.def.span), self_type: self.self_type.clone(), source_file: self.file, - custom_attributes: attributes, }; self.interner.push_fn_meta(meta, func_id); diff --git a/compiler/noirc_frontend/src/hir/comptime/display.rs b/compiler/noirc_frontend/src/hir/comptime/display.rs index bfbe39df241..560d11cfa2e 100644 --- a/compiler/noirc_frontend/src/hir/comptime/display.rs +++ b/compiler/noirc_frontend/src/hir/comptime/display.rs @@ -281,8 +281,7 @@ impl<'interner> TokenPrettyPrinter<'interner> { | Token::Whitespace(_) | Token::LineComment(..) | Token::BlockComment(..) - | Token::Attribute(..) - | Token::InnerAttribute(..) + | Token::AttributeStart { .. } | Token::Invalid(_) => { if last_was_alphanumeric { write!(f, " ")?; diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs index 7205242ead9..aa84b6fb597 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs @@ -223,7 +223,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { location: Location, ) -> IResult { let attributes = self.elaborator.interner.function_attributes(&function); - let func_attrs = attributes.function.as_ref() + let func_attrs = attributes.function() .expect("all builtin functions must contain a function attribute which contains the opcode which it links to"); if let Some(builtin) = func_attrs.builtin() { diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index d8842215a29..9590b65c6e0 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -37,7 +37,7 @@ use crate::{ hir_def::{self}, node_interner::{DefinitionKind, NodeInterner, TraitImplKind}, parser::{Parser, StatementOrExpressionOrLValue}, - token::{Attribute, SecondaryAttribute, Token}, + token::{Attribute, Token}, Kind, QuotedType, ResolvedGeneric, Shared, Type, TypeVariable, }; @@ -348,24 +348,9 @@ fn struct_def_add_attribute( let (self_argument, attribute) = check_two_arguments(arguments, location)?; let attribute_location = attribute.1; let attribute = get_str(interner, attribute)?; - - let mut tokens = lex(&format!("#[{}]", attribute)); - if tokens.len() != 1 { - return Err(InterpreterError::InvalidAttribute { - attribute: attribute.to_string(), - location: attribute_location, - }); - } - - let token = tokens.remove(0); - let Token::Attribute(attribute) = token else { - return Err(InterpreterError::InvalidAttribute { - attribute: attribute.to_string(), - location: attribute_location, - }); - }; - - let Attribute::Secondary(attribute) = attribute else { + let attribute = format!("#[{}]", attribute); + let mut parser = Parser::for_str(&attribute); + let Some((Attribute::Secondary(attribute), _span)) = parser.parse_attribute() else { return Err(InterpreterError::InvalidAttribute { attribute: attribute.to_string(), location: attribute_location, @@ -374,7 +359,7 @@ fn struct_def_add_attribute( let struct_id = get_struct(self_argument)?; interner.update_struct_attributes(struct_id, |attributes| { - attributes.push(attribute.clone()); + attributes.push(attribute); }); Ok(Value::Unit) @@ -2225,17 +2210,9 @@ fn function_def_add_attribute( let (self_argument, attribute) = check_two_arguments(arguments, location)?; let attribute_location = attribute.1; let attribute = get_str(interpreter.elaborator.interner, attribute)?; - - let mut tokens = lex(&format!("#[{}]", attribute)); - if tokens.len() != 1 { - return Err(InterpreterError::InvalidAttribute { - attribute: attribute.to_string(), - location: attribute_location, - }); - } - - let token = tokens.remove(0); - let Token::Attribute(attribute) = token else { + let attribute = format!("#[{}]", attribute); + let mut parser = Parser::for_str(&attribute); + let Some((attribute, _span)) = parser.parse_attribute() else { return Err(InterpreterError::InvalidAttribute { attribute: attribute.to_string(), location: attribute_location, @@ -2249,21 +2226,13 @@ fn function_def_add_attribute( match &attribute { Attribute::Function(attribute) => { - function_modifiers.attributes.function = Some(attribute.clone()); + function_modifiers.attributes.set_function(attribute.clone()); } Attribute::Secondary(attribute) => { function_modifiers.attributes.secondary.push(attribute.clone()); } } - if let Attribute::Secondary( - SecondaryAttribute::Tag(attribute) | SecondaryAttribute::Meta(attribute), - ) = attribute - { - let func_meta = interpreter.elaborator.interner.function_meta_mut(&func_id); - func_meta.custom_attributes.push(attribute); - } - Ok(Value::Unit) } @@ -2295,7 +2264,7 @@ fn function_def_has_named_attribute( let name = &*get_str(interner, name)?; let modifiers = interner.function_modifiers(&func_id); - if let Some(attribute) = &modifiers.attributes.function { + if let Some(attribute) = modifiers.attributes.function() { if name == attribute.name() { return Ok(Value::Bool(true)); } diff --git a/compiler/noirc_frontend/src/hir/def_map/mod.rs b/compiler/noirc_frontend/src/hir/def_map/mod.rs index 60ee2c52842..de94f73b44b 100644 --- a/compiler/noirc_frontend/src/hir/def_map/mod.rs +++ b/compiler/noirc_frontend/src/hir/def_map/mod.rs @@ -169,7 +169,7 @@ impl CrateDefMap { module.value_definitions().filter_map(|id| { if let Some(func_id) = id.as_function() { let attributes = interner.function_attributes(&func_id); - match &attributes.function { + match attributes.function() { Some(FunctionAttribute::Test(scope)) => { let location = interner.function_meta(&func_id).name.location; Some(TestFunction::new(func_id, scope.clone(), location)) diff --git a/compiler/noirc_frontend/src/hir_def/function.rs b/compiler/noirc_frontend/src/hir_def/function.rs index 6ecfdefe996..db6c3507b15 100644 --- a/compiler/noirc_frontend/src/hir_def/function.rs +++ b/compiler/noirc_frontend/src/hir_def/function.rs @@ -9,7 +9,7 @@ use crate::ast::{BlockExpression, FunctionKind, FunctionReturnType, Visibility}; use crate::graph::CrateId; use crate::hir::def_map::LocalModuleId; use crate::node_interner::{ExprId, NodeInterner, StructId, TraitId, TraitImplId}; -use crate::token::CustomAttribute; + use crate::{ResolvedGeneric, Type}; /// A Hir function is a block expression with a list of statements. @@ -164,9 +164,6 @@ pub struct FuncMeta { /// If this function is from an impl (trait or regular impl), this /// is the object type of the impl. Otherwise this is None. pub self_type: Option, - - /// Custom attributes attached to this function. - pub custom_attributes: Vec, } #[derive(Debug, Clone)] diff --git a/compiler/noirc_frontend/src/lexer/lexer.rs b/compiler/noirc_frontend/src/lexer/lexer.rs index 91ae544ddf0..b99fd993425 100644 --- a/compiler/noirc_frontend/src/lexer/lexer.rs +++ b/compiler/noirc_frontend/src/lexer/lexer.rs @@ -1,4 +1,4 @@ -use crate::token::{Attribute, DocStyle}; +use crate::token::DocStyle; use super::{ errors::LexerErrorKind, @@ -139,7 +139,7 @@ impl<'a> Lexer<'a> { Some('f') => self.eat_format_string_or_alpha_numeric(), Some('r') => self.eat_raw_string_or_alpha_numeric(), Some('q') => self.eat_quote_or_alpha_numeric(), - Some('#') => self.eat_attribute(), + Some('#') => self.eat_attribute_start(), Some(ch) if ch.is_ascii_alphanumeric() || ch == '_' => self.eat_alpha_numeric(ch), Some(ch) => { // We don't report invalid tokens in the source as errors until parsing to @@ -282,7 +282,7 @@ impl<'a> Lexer<'a> { } } - fn eat_attribute(&mut self) -> SpannedTokenResult { + fn eat_attribute_start(&mut self) -> SpannedTokenResult { let start = self.position; let is_inner = if self.peek_char_is('!') { @@ -306,40 +306,9 @@ impl<'a> Lexer<'a> { self.next_char(); } - let contents_start = self.position + 1; - - let word = self.eat_while(None, |ch| ch != ']'); - - let contents_end = self.position; - - if !self.peek_char_is(']') { - return Err(LexerErrorKind::UnexpectedCharacter { - span: Span::single_char(self.position), - expected: "]".to_owned(), - found: self.next_char(), - }); - } - self.next_char(); - let end = self.position; - let span = Span::inclusive(start, end); - let contents_span = Span::inclusive(contents_start, contents_end); - - let attribute = Attribute::lookup_attribute(&word, span, contents_span, is_tag)?; - if is_inner { - match attribute { - Attribute::Function(attribute) => Err(LexerErrorKind::InvalidInnerAttribute { - span: Span::from(start..end), - found: attribute.to_string(), - }), - Attribute::Secondary(attribute) => { - Ok(Token::InnerAttribute(attribute).into_span(start, end)) - } - } - } else { - Ok(Token::Attribute(attribute).into_span(start, end)) - } + Ok(Token::AttributeStart { is_inner, is_tag }.into_span(start, end)) } //XXX(low): Can increase performance if we use iterator semantic and utilize some of the methods on String. See below @@ -725,7 +694,6 @@ mod tests { use iter_extended::vecmap; use super::*; - use crate::token::{CustomAttribute, FunctionAttribute, SecondaryAttribute, TestScope}; #[test] fn test_single_multi_char() { @@ -785,173 +753,39 @@ mod tests { } #[test] - fn deprecated_attribute() { - let input = r#"#[deprecated]"#; - let mut lexer = Lexer::new(input); - - let token = lexer.next_token().unwrap(); - assert_eq!( - token.token(), - &Token::Attribute(Attribute::Secondary(SecondaryAttribute::Deprecated(None))) - ); - } - - #[test] - fn test_attribute_with_common_punctuation() { - let input = - r#"#[test(should_fail_with = "stmt. q? exclaim! & symbols, 1% shouldn't fail")]"#; - let mut lexer = Lexer::new(input); - - let token = lexer.next_token().unwrap().token().clone(); - assert_eq!( - token, - Token::Attribute(Attribute::Function(FunctionAttribute::Test( - TestScope::ShouldFailWith { - reason: "stmt. q? exclaim! & symbols, 1% shouldn't fail".to_owned().into() - } - ))) - ); - } - - #[test] - fn deprecated_attribute_with_note() { - let input = r#"#[deprecated("hello")]"#; - let mut lexer = Lexer::new(input); - - let token = lexer.next_token().unwrap(); - assert_eq!( - token.token(), - &Token::Attribute(Attribute::Secondary(crate::token::SecondaryAttribute::Deprecated( - "hello".to_string().into() - ))) - ); - } - - #[test] - fn test_custom_gate_syntax() { - let input = "#[foreign(sha256)]#[foreign(blake2s)]#[builtin(sum)]"; - - let expected = vec![ - Token::Attribute(Attribute::Function(FunctionAttribute::Foreign("sha256".to_string()))), - Token::Attribute(Attribute::Function(FunctionAttribute::Foreign( - "blake2s".to_string(), - ))), - Token::Attribute(Attribute::Function(FunctionAttribute::Builtin("sum".to_string()))), - ]; - - let mut lexer = Lexer::new(input); - for token in expected.into_iter() { - let got = lexer.next_token().unwrap(); - assert_eq!(got, token); - } - } - - #[test] - fn tag_attribute() { - let input = r#"#['custom(hello)]"#; + fn test_attribute_start() { + let input = r#"#[something]"#; let mut lexer = Lexer::new(input); let token = lexer.next_token().unwrap(); - assert_eq!( - token.token(), - &Token::Attribute(Attribute::Secondary(SecondaryAttribute::Tag(CustomAttribute { - contents: "custom(hello)".to_string(), - span: Span::from(0..17), - contents_span: Span::from(3..16) - }))) - ); + assert_eq!(token.token(), &Token::AttributeStart { is_inner: false, is_tag: false }); } #[test] - fn test_attribute() { - let input = r#"#[test]"#; + fn test_attribute_start_with_tag() { + let input = r#"#['something]"#; let mut lexer = Lexer::new(input); let token = lexer.next_token().unwrap(); - assert_eq!( - token.token(), - &Token::Attribute(Attribute::Function(FunctionAttribute::Test(TestScope::None))) - ); + assert_eq!(token.token(), &Token::AttributeStart { is_inner: false, is_tag: true }); } #[test] - fn fold_attribute() { - let input = r#"#[fold]"#; - - let mut lexer = Lexer::new(input); - let token = lexer.next_token().unwrap(); - - assert_eq!(token.token(), &Token::Attribute(Attribute::Function(FunctionAttribute::Fold))); - } - - #[test] - fn contract_library_method_attribute() { - let input = r#"#[contract_library_method]"#; - let mut lexer = Lexer::new(input); - - let token = lexer.next_token().unwrap(); - assert_eq!( - token.token(), - &Token::Attribute(Attribute::Secondary(SecondaryAttribute::ContractLibraryMethod)) - ); - } - - #[test] - fn test_attribute_with_valid_scope() { - let input = r#"#[test(should_fail)]"#; - let mut lexer = Lexer::new(input); - - let token = lexer.next_token().unwrap(); - assert_eq!( - token.token(), - &Token::Attribute(Attribute::Function(FunctionAttribute::Test( - TestScope::ShouldFailWith { reason: None } - ))) - ); - } - - #[test] - fn test_attribute_with_valid_scope_should_fail_with() { - let input = r#"#[test(should_fail_with = "hello")]"#; + fn test_inner_attribute_start() { + let input = r#"#![something]"#; let mut lexer = Lexer::new(input); let token = lexer.next_token().unwrap(); - assert_eq!( - token.token(), - &Token::Attribute(Attribute::Function(FunctionAttribute::Test( - TestScope::ShouldFailWith { reason: Some("hello".to_owned()) } - ))) - ); + assert_eq!(token.token(), &Token::AttributeStart { is_inner: true, is_tag: false }); } #[test] - fn test_attribute_with_invalid_scope() { - let input = r#"#[test(invalid_scope)]"#; - let mut lexer = Lexer::new(input); - - let token = lexer.next().unwrap(); - let err = match token { - Ok(_) => panic!("test has an invalid scope, so expected an error"), - Err(err) => err, - }; - - assert!(matches!(err, LexerErrorKind::MalformedTestAttribute { .. })); - } - - #[test] - fn test_inner_attribute() { - let input = r#"#![something]"#; + fn test_inner_attribute_start_with_tag() { + let input = r#"#!['something]"#; let mut lexer = Lexer::new(input); let token = lexer.next_token().unwrap(); - assert_eq!( - token.token(), - &Token::InnerAttribute(SecondaryAttribute::Meta(CustomAttribute { - contents: "something".to_string(), - span: Span::from(0..13), - contents_span: Span::from(3..12), - })) - ); + assert_eq!(token.token(), &Token::AttributeStart { is_inner: true, is_tag: true }); } #[test] diff --git a/compiler/noirc_frontend/src/lexer/token.rs b/compiler/noirc_frontend/src/lexer/token.rs index 593e78d01a0..17efdecf6ca 100644 --- a/compiler/noirc_frontend/src/lexer/token.rs +++ b/compiler/noirc_frontend/src/lexer/token.rs @@ -1,8 +1,9 @@ use acvm::FieldElement; use noirc_errors::{Position, Span, Spanned}; -use std::fmt; +use std::fmt::{self, Display}; use crate::{ + ast::{Expression, Path}, lexer::errors::LexerErrorKind, node_interner::{ ExprId, InternedExpressionKind, InternedPattern, InternedStatementKind, @@ -28,8 +29,10 @@ pub enum BorrowedToken<'input> { FmtStr(&'input str), Keyword(Keyword), IntType(IntType), - Attribute(Attribute), - InnerAttribute(SecondaryAttribute), + AttributeStart { + is_inner: bool, + is_tag: bool, + }, LineComment(&'input str, Option), BlockComment(&'input str, Option), Quote(&'input Tokens), @@ -137,8 +140,10 @@ pub enum Token { FmtStr(String), Keyword(Keyword), IntType(IntType), - Attribute(Attribute), - InnerAttribute(SecondaryAttribute), + AttributeStart { + is_inner: bool, + is_tag: bool, + }, LineComment(String, Option), BlockComment(String, Option), // A `quote { ... }` along with the tokens in its token stream. @@ -254,8 +259,9 @@ pub fn token_to_borrowed_token(token: &Token) -> BorrowedToken<'_> { Token::FmtStr(ref b) => BorrowedToken::FmtStr(b), Token::RawStr(ref b, hashes) => BorrowedToken::RawStr(b, *hashes), Token::Keyword(k) => BorrowedToken::Keyword(*k), - Token::Attribute(ref a) => BorrowedToken::Attribute(a.clone()), - Token::InnerAttribute(ref a) => BorrowedToken::InnerAttribute(a.clone()), + Token::AttributeStart { is_inner, is_tag } => { + BorrowedToken::AttributeStart { is_inner: *is_inner, is_tag: *is_tag } + } Token::LineComment(ref s, _style) => BorrowedToken::LineComment(s, *_style), Token::BlockComment(ref s, _style) => BorrowedToken::BlockComment(s, *_style), Token::Quote(stream) => BorrowedToken::Quote(stream), @@ -376,8 +382,17 @@ impl fmt::Display for Token { write!(f, "r{h}{b:?}{h}") } Token::Keyword(k) => write!(f, "{k}"), - Token::Attribute(ref a) => write!(f, "{a}"), - Token::InnerAttribute(ref a) => write!(f, "#![{}]", a.contents()), + Token::AttributeStart { is_inner, is_tag } => { + write!(f, "#")?; + if is_inner { + write!(f, "!")?; + } + write!(f, "[")?; + if is_tag { + write!(f, "'")?; + } + Ok(()) + } Token::LineComment(ref s, style) => match style { Some(DocStyle::Inner) => write!(f, "//!{s}"), Some(DocStyle::Outer) => write!(f, "///{s}"), @@ -503,8 +518,6 @@ impl Token { | Token::RawStr(..) | Token::FmtStr(_) => TokenKind::Literal, Token::Keyword(_) => TokenKind::Keyword, - Token::Attribute(_) => TokenKind::Attribute, - Token::InnerAttribute(_) => TokenKind::InnerAttribute, Token::UnquoteMarker(_) => TokenKind::UnquoteMarker, Token::Quote(_) => TokenKind::Quote, Token::QuotedType(_) => TokenKind::QuotedType, @@ -622,25 +635,6 @@ pub enum TestScope { None, } -impl TestScope { - fn lookup_str(string: &str) -> Option { - match string.trim() { - "should_fail" => Some(TestScope::ShouldFailWith { reason: None }), - s if s.starts_with("should_fail_with") => { - let parts: Vec<&str> = s.splitn(2, '=').collect(); - if parts.len() == 2 { - let reason = parts[1].trim(); - let reason = reason.trim_matches('"'); - Some(TestScope::ShouldFailWith { reason: Some(reason.to_string()) }) - } else { - None - } - } - _ => None, - } - } -} - impl fmt::Display for TestScope { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { @@ -653,13 +647,13 @@ impl fmt::Display for TestScope { } } -#[derive(PartialEq, Eq, Hash, Debug, Clone, PartialOrd, Ord)] +#[derive(PartialEq, Eq, Debug, Clone)] // Attributes are special language markers in the target language // An example of one is `#[SHA256]` . Currently only Foreign attributes are supported // Calls to functions which have the foreign attribute are executed in the host language pub struct Attributes { // Each function can have a single Primary Attribute - pub function: Option, + pub function: Option<(FunctionAttribute, usize /* index in list */)>, // Each function can have many Secondary Attributes pub secondary: Vec, } @@ -669,6 +663,15 @@ impl Attributes { Self { function: None, secondary: Vec::new() } } + pub fn function(&self) -> Option<&FunctionAttribute> { + self.function.as_ref().map(|(attr, _)| attr) + } + + pub fn set_function(&mut self, function: FunctionAttribute) { + // Assume the index in the list doesn't matter anymore at this point + self.function = Some((function, 0)); + } + /// Returns true if one of the secondary attributes is `contract_library_method` /// /// This is useful for finding out if we should compile a contract method @@ -680,7 +683,7 @@ impl Attributes { } pub fn is_test_function(&self) -> bool { - matches!(self.function, Some(FunctionAttribute::Test(_))) + matches!(self.function(), Some(FunctionAttribute::Test(_))) } /// True if these attributes mean the given function is an entry point function if it was @@ -708,11 +711,11 @@ impl Attributes { } pub fn is_foldable(&self) -> bool { - self.function.as_ref().map_or(false, |func_attribute| func_attribute.is_foldable()) + self.function().map_or(false, |func_attribute| func_attribute.is_foldable()) } pub fn is_no_predicates(&self) -> bool { - self.function.as_ref().map_or(false, |func_attribute| func_attribute.is_no_predicates()) + self.function().map_or(false, |func_attribute| func_attribute.is_no_predicates()) } pub fn has_varargs(&self) -> bool { @@ -727,7 +730,7 @@ impl Attributes { /// An Attribute can be either a Primary Attribute or a Secondary Attribute /// A Primary Attribute can alter the function type, thus there can only be one /// A secondary attribute has no effect and is either consumed by a library or used as a notice for the developer -#[derive(PartialEq, Eq, Hash, Debug, Clone, PartialOrd, Ord)] +#[derive(PartialEq, Eq, Debug, Clone)] pub enum Attribute { Function(FunctionAttribute), Secondary(SecondaryAttribute), @@ -742,116 +745,6 @@ impl fmt::Display for Attribute { } } -impl Attribute { - /// If the string is a fixed attribute return that, else - /// return the custom attribute - pub(crate) fn lookup_attribute( - word: &str, - span: Span, - contents_span: Span, - is_tag: bool, - ) -> Result { - // See if we can parse the word into "name ( contents )". - // We first split into "first_segment ( rest". - let word_segments = if let Some((first_segment, rest)) = word.trim().split_once('(') { - // Now we try to remove the final ")" (it must be at the end, if it exists) - if let Some(middle) = rest.strip_suffix(')') { - vec![first_segment.trim(), middle.trim()] - } else { - vec![word] - } - } else { - vec![word] - }; - - let validate = |slice: &str| { - let is_valid = slice - .chars() - .all(|ch| { - ch.is_ascii_alphabetic() - || ch.is_numeric() - || ch.is_ascii_punctuation() - || ch == ' ' - }) - .then_some(()); - - is_valid.ok_or(LexerErrorKind::MalformedFuncAttribute { span, found: word.to_owned() }) - }; - - if is_tag { - return Ok(Attribute::Secondary(SecondaryAttribute::Tag(CustomAttribute { - contents: word.to_owned(), - span, - contents_span, - }))); - } - - let attribute = match &word_segments[..] { - // Primary Attributes - ["foreign", name] => { - validate(name)?; - Attribute::Function(FunctionAttribute::Foreign(name.to_string())) - } - ["builtin", name] => { - validate(name)?; - Attribute::Function(FunctionAttribute::Builtin(name.to_string())) - } - ["oracle", name] => { - validate(name)?; - Attribute::Function(FunctionAttribute::Oracle(name.to_string())) - } - ["test"] => Attribute::Function(FunctionAttribute::Test(TestScope::None)), - ["recursive"] => Attribute::Function(FunctionAttribute::Recursive), - ["fold"] => Attribute::Function(FunctionAttribute::Fold), - ["no_predicates"] => Attribute::Function(FunctionAttribute::NoPredicates), - ["inline_always"] => Attribute::Function(FunctionAttribute::InlineAlways), - ["test", name] => { - validate(name)?; - match TestScope::lookup_str(name) { - Some(scope) => Attribute::Function(FunctionAttribute::Test(scope)), - None => return Err(LexerErrorKind::MalformedTestAttribute { span }), - } - } - ["field", name] => { - validate(name)?; - Attribute::Secondary(SecondaryAttribute::Field(name.to_string())) - } - // Secondary attributes - ["deprecated"] => Attribute::Secondary(SecondaryAttribute::Deprecated(None)), - ["contract_library_method"] => { - Attribute::Secondary(SecondaryAttribute::ContractLibraryMethod) - } - ["abi", tag] => Attribute::Secondary(SecondaryAttribute::Abi(tag.to_string())), - ["export"] => Attribute::Secondary(SecondaryAttribute::Export), - ["deprecated", name] => { - if !name.starts_with('"') && !name.ends_with('"') { - return Err(LexerErrorKind::MalformedFuncAttribute { - span, - found: word.to_owned(), - }); - } - - Attribute::Secondary(SecondaryAttribute::Deprecated( - name.trim_matches('"').to_string().into(), - )) - } - ["varargs"] => Attribute::Secondary(SecondaryAttribute::Varargs), - ["use_callers_scope"] => Attribute::Secondary(SecondaryAttribute::UseCallersScope), - ["allow", tag] => Attribute::Secondary(SecondaryAttribute::Allow(tag.to_string())), - tokens => { - tokens.iter().try_for_each(|token| validate(token))?; - Attribute::Secondary(SecondaryAttribute::Meta(CustomAttribute { - contents: word.to_owned(), - span, - contents_span, - })) - } - }; - - Ok(attribute) - } -} - /// Primary Attributes are those which a function can only have one of. /// They change the FunctionKind and thus have direct impact on the IR output #[derive(PartialEq, Eq, Hash, Debug, Clone, PartialOrd, Ord)] @@ -950,7 +843,7 @@ impl fmt::Display for FunctionAttribute { /// Secondary attributes are those which a function can have many of. /// They are not able to change the `FunctionKind` and thus do not have direct impact on the IR output /// They are often consumed by libraries or used as notices for the developer -#[derive(PartialEq, Eq, Hash, Debug, Clone, PartialOrd, Ord)] +#[derive(PartialEq, Eq, Debug, Clone)] pub enum SecondaryAttribute { Deprecated(Option), // This is an attribute to specify that a function @@ -964,7 +857,7 @@ pub enum SecondaryAttribute { Tag(CustomAttribute), /// An attribute expected to run a comptime function of the same name: #[foo] - Meta(CustomAttribute), + Meta(MetaAttribute), Abi(String), @@ -981,14 +874,6 @@ pub enum SecondaryAttribute { } impl SecondaryAttribute { - pub(crate) fn as_custom(&self) -> Option<&CustomAttribute> { - if let Self::Tag(attribute) = self { - Some(attribute) - } else { - None - } - } - pub(crate) fn name(&self) -> Option { match self { SecondaryAttribute::Deprecated(_) => Some("deprecated".to_string()), @@ -998,7 +883,7 @@ impl SecondaryAttribute { SecondaryAttribute::Export => Some("export".to_string()), SecondaryAttribute::Field(_) => Some("field".to_string()), SecondaryAttribute::Tag(custom) => custom.name(), - SecondaryAttribute::Meta(custom) => custom.name(), + SecondaryAttribute::Meta(meta) => Some(meta.name.last_name().to_string()), SecondaryAttribute::Abi(_) => Some("abi".to_string()), SecondaryAttribute::Varargs => Some("varargs".to_string()), SecondaryAttribute::UseCallersScope => Some("use_callers_scope".to_string()), @@ -1024,7 +909,7 @@ impl SecondaryAttribute { format!("deprecated({note:?})") } SecondaryAttribute::Tag(ref attribute) => format!("'{}", attribute.contents), - SecondaryAttribute::Meta(ref attribute) => attribute.contents.to_string(), + SecondaryAttribute::Meta(ref meta) => meta.to_string(), SecondaryAttribute::ContractLibraryMethod => "contract_library_method".to_string(), SecondaryAttribute::Export => "export".to_string(), SecondaryAttribute::Field(ref k) => format!("field({k})"), @@ -1042,6 +927,25 @@ impl fmt::Display for SecondaryAttribute { } } +#[derive(PartialEq, Eq, Debug, Clone)] +pub struct MetaAttribute { + pub name: Path, + pub arguments: Vec, + pub span: Span, +} + +impl Display for MetaAttribute { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.arguments.is_empty() { + write!(f, "{}", self.name) + } else { + let args = + self.arguments.iter().map(ToString::to_string).collect::>().join(", "); + write!(f, "{}({})", self.name, args) + } + } +} + #[derive(PartialEq, Eq, Hash, Debug, Clone, PartialOrd, Ord)] pub struct CustomAttribute { pub contents: String, @@ -1063,39 +967,6 @@ impl CustomAttribute { } } -impl AsRef for FunctionAttribute { - fn as_ref(&self) -> &str { - match self { - FunctionAttribute::Foreign(string) => string, - FunctionAttribute::Builtin(string) => string, - FunctionAttribute::Oracle(string) => string, - FunctionAttribute::Test { .. } => "", - FunctionAttribute::Recursive => "", - FunctionAttribute::Fold => "", - FunctionAttribute::NoPredicates => "", - FunctionAttribute::InlineAlways => "", - } - } -} - -impl AsRef for SecondaryAttribute { - fn as_ref(&self) -> &str { - match self { - SecondaryAttribute::Deprecated(Some(string)) => string, - SecondaryAttribute::Deprecated(None) => "", - SecondaryAttribute::Tag(attribute) => &attribute.contents, - SecondaryAttribute::Meta(attribute) => &attribute.contents, - SecondaryAttribute::Field(string) - | SecondaryAttribute::Abi(string) - | SecondaryAttribute::Allow(string) => string, - SecondaryAttribute::ContractLibraryMethod => "", - SecondaryAttribute::Export => "", - SecondaryAttribute::Varargs => "", - SecondaryAttribute::UseCallersScope => "", - } - } -} - /// Note that `self` is not present - it is a contextual keyword rather than a true one as it is /// only special within `impl`s. Otherwise `self` functions as a normal identifier. #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone, PartialOrd, Ord, strum_macros::EnumIter)] diff --git a/compiler/noirc_frontend/src/monomorphization/ast.rs b/compiler/noirc_frontend/src/monomorphization/ast.rs index 1b4bafd9d78..bfee31bdfb4 100644 --- a/compiler/noirc_frontend/src/monomorphization/ast.rs +++ b/compiler/noirc_frontend/src/monomorphization/ast.rs @@ -237,13 +237,11 @@ pub enum InlineType { impl From<&Attributes> for InlineType { fn from(attributes: &Attributes) -> Self { - attributes.function.as_ref().map_or(InlineType::default(), |func_attribute| { - match func_attribute { - FunctionAttribute::Fold => InlineType::Fold, - FunctionAttribute::NoPredicates => InlineType::NoPredicates, - FunctionAttribute::InlineAlways => InlineType::InlineAlways, - _ => InlineType::default(), - } + attributes.function().map_or(InlineType::default(), |func_attribute| match func_attribute { + FunctionAttribute::Fold => InlineType::Fold, + FunctionAttribute::NoPredicates => InlineType::NoPredicates, + FunctionAttribute::InlineAlways => InlineType::InlineAlways, + _ => InlineType::default(), }) } } diff --git a/compiler/noirc_frontend/src/monomorphization/mod.rs b/compiler/noirc_frontend/src/monomorphization/mod.rs index 3ca4c5651ec..b51d7d1a656 100644 --- a/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -222,14 +222,14 @@ impl<'interner> Monomorphizer<'interner> { let attributes = self.interner.function_attributes(&id); match self.interner.function_meta(&id).kind { FunctionKind::LowLevel => { - let attribute = attributes.function.as_ref().expect("all low level functions must contain a function attribute which contains the opcode which it links to"); + let attribute = attributes.function().expect("all low level functions must contain a function attribute which contains the opcode which it links to"); let opcode = attribute.foreign().expect( "ice: function marked as foreign, but attribute kind does not match this", ); Definition::LowLevel(opcode.to_string()) } FunctionKind::Builtin => { - let attribute = attributes.function.as_ref().expect("all builtin functions must contain a function attribute which contains the opcode which it links to"); + let attribute = attributes.function().expect("all builtin functions must contain a function attribute which contains the opcode which it links to"); let opcode = attribute.builtin().expect( "ice: function marked as builtin, but attribute kind does not match this", ); @@ -241,7 +241,7 @@ impl<'interner> Monomorphizer<'interner> { Definition::Function(id) } FunctionKind::Oracle => { - let attribute = attributes.function.as_ref().expect("all oracle functions must contain a function attribute which contains the opcode which it links to"); + let attribute = attributes.function().expect("all oracle functions must contain a function attribute which contains the opcode which it links to"); let opcode = attribute.oracle().expect( "ice: function marked as builtin, but attribute kind does not match this", ); diff --git a/compiler/noirc_frontend/src/parser/errors.rs b/compiler/noirc_frontend/src/parser/errors.rs index 3315b38a351..63471efac43 100644 --- a/compiler/noirc_frontend/src/parser/errors.rs +++ b/compiler/noirc_frontend/src/parser/errors.rs @@ -95,6 +95,15 @@ pub enum ParserErrorReason { AssociatedTypesNotAllowedInPaths, #[error("Associated types are not allowed on a method call")] AssociatedTypesNotAllowedInMethodCalls, + #[error( + "Wrong number of arguments for attribute `{}`. Expected {}, found {}", + name, + if min == max { min.to_string() } else { format!("between {} and {}", min, max) }, + found + )] + WrongNumberOfAttributeArguments { name: String, min: usize, max: usize, found: usize }, + #[error("The `deprecated` attribute expects a string argument")] + DeprecatedAttributeExpectsAStringArgument, } /// Represents a parsing error, or a parsing error in the making. diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index 0030144b5e1..f369839ddd4 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -318,6 +318,30 @@ impl<'a> Parser<'a> { } } + fn eat_attribute_start(&mut self) -> Option { + if matches!(self.token.token(), Token::AttributeStart { is_inner: false, .. }) { + let token = self.bump(); + match token.into_token() { + Token::AttributeStart { is_tag, .. } => Some(is_tag), + _ => unreachable!(), + } + } else { + None + } + } + + fn eat_inner_attribute_start(&mut self) -> Option { + if matches!(self.token.token(), Token::AttributeStart { is_inner: true, .. }) { + let token = self.bump(); + match token.into_token() { + Token::AttributeStart { is_tag, .. } => Some(is_tag), + _ => unreachable!(), + } + } else { + None + } + } + fn eat_comma(&mut self) -> bool { self.eat(Token::Comma) } diff --git a/compiler/noirc_frontend/src/parser/parser/attributes.rs b/compiler/noirc_frontend/src/parser/parser/attributes.rs index ffba74003b7..55919708f8c 100644 --- a/compiler/noirc_frontend/src/parser/parser/attributes.rs +++ b/compiler/noirc_frontend/src/parser/parser/attributes.rs @@ -1,32 +1,87 @@ use noirc_errors::Span; +use crate::ast::{Expression, ExpressionKind, Ident, Literal, Path}; +use crate::lexer::errors::LexerErrorKind; +use crate::parser::labels::ParsingRuleLabel; use crate::parser::ParserErrorReason; -use crate::token::SecondaryAttribute; -use crate::token::{Attribute, Token, TokenKind}; +use crate::token::{Attribute, FunctionAttribute, MetaAttribute, TestScope, Token}; +use crate::token::{CustomAttribute, SecondaryAttribute}; use super::parse_many::without_separator; use super::Parser; impl<'a> Parser<'a> { - /// InnerAttribute = inner_attribute + /// InnerAttribute = '#![' SecondaryAttribute ']' pub(super) fn parse_inner_attribute(&mut self) -> Option { - let token = self.eat_kind(TokenKind::InnerAttribute)?; - match token.into_token() { - Token::InnerAttribute(attribute) => Some(attribute), - _ => unreachable!(), + let start_span = self.current_token_span; + let is_tag = self.eat_inner_attribute_start()?; + let attribute = if is_tag { + self.parse_tag_attribute(start_span) + } else { + self.parse_non_tag_attribute(start_span) + }; + + match attribute { + Attribute::Function(function_attribute) => { + self.errors.push( + LexerErrorKind::InvalidInnerAttribute { + span: self.span_since(start_span), + found: function_attribute.to_string(), + } + .into(), + ); + None + } + Attribute::Secondary(secondary_attribute) => Some(secondary_attribute), } } - /// Attributes = attribute* + /// Attributes = Attribute* pub(super) fn parse_attributes(&mut self) -> Vec<(Attribute, Span)> { self.parse_many("attributes", without_separator(), Self::parse_attribute) } - fn parse_attribute(&mut self) -> Option<(Attribute, Span)> { - self.eat_kind(TokenKind::Attribute).map(|token| match token.into_token() { - Token::Attribute(attribute) => (attribute, self.previous_token_span), - _ => unreachable!(), - }) + /// Attribute = '#[' (FunctionAttribute | SecondaryAttribute) ']' + /// + /// FunctionAttribute + /// = 'builtin' '(' AttributeValue ')' + /// | 'fold' + /// | 'foreign' '(' AttributeValue ')' + /// | 'inline_always' + /// | 'no_predicates' + /// | 'oracle' '(' AttributeValue ')' + /// | 'recursive' + /// | 'test' + /// | 'test' '(' 'should_fail' ')' + /// | 'test' '(' 'should_fail_with' '=' string ')' + /// + /// SecondaryAttribute + /// = 'abi' '(' AttributeValue ')' + /// | 'allow' '(' AttributeValue ')' + /// | 'deprecated' + /// | 'deprecated' '(' string ')' + /// | 'contract_library_method' + /// | 'export' + /// | 'field' '(' AttributeValue ')' + /// | 'use_callers_scope' + /// | 'varargs' + /// | MetaAttribute + /// + /// MetaAttribute + /// = Path Arguments? + /// + /// AttributeValue + /// = Path + /// | integer + pub(crate) fn parse_attribute(&mut self) -> Option<(Attribute, Span)> { + let start_span = self.current_token_span; + let is_tag = self.eat_attribute_start()?; + let attribute = if is_tag { + self.parse_tag_attribute(start_span) + } else { + self.parse_non_tag_attribute(start_span) + }; + Some((attribute, self.span_since(start_span))) } pub(super) fn validate_secondary_attributes( @@ -44,17 +99,319 @@ impl<'a> Parser<'a> { }) .collect() } + + fn parse_tag_attribute(&mut self, start_span: Span) -> Attribute { + let contents_start_span = self.current_token_span; + let mut contents_span = contents_start_span; + let mut contents = String::new(); + + let mut brackets_count = 1; // 1 because of the starting `#[` + + while !self.at_eof() { + if self.at(Token::LeftBracket) { + brackets_count += 1; + } else if self.at(Token::RightBracket) { + brackets_count -= 1; + if brackets_count == 0 { + contents_span = self.span_since(contents_start_span); + self.bump(); + break; + } + } + + contents.push_str(&self.token.to_string()); + self.bump(); + } + + Attribute::Secondary(SecondaryAttribute::Tag(CustomAttribute { + contents, + span: self.span_since(start_span), + contents_span, + })) + } + + fn parse_non_tag_attribute(&mut self, start_span: Span) -> Attribute { + if matches!(&self.token.token(), Token::Keyword(..)) + && (self.next_is(Token::LeftParen) || self.next_is(Token::RightBracket)) + { + // This is a Meta attribute with the syntax `keyword(arg1, arg2, .., argN)` + let path = Path::from_single(self.token.to_string(), self.current_token_span); + self.bump(); + self.parse_meta_attribute(path, start_span) + } else if let Some(path) = self.parse_path_no_turbofish() { + if let Some(ident) = path.as_ident() { + if ident.0.contents == "test" { + // The test attribute is the only secondary attribute that has `a = b` in its syntax + // (`should_fail_with = "..."``) so we parse it differently. + self.parse_test_attribute(start_span) + } else { + // Every other attribute has the form `name(arg1, arg2, .., argN)` + self.parse_ident_attribute_other_than_test(ident, start_span) + } + } else { + // This is a Meta attribute with the syntax `path(arg1, arg2, .., argN)` + self.parse_meta_attribute(path, start_span) + } + } else { + self.expected_label(ParsingRuleLabel::Path); + self.parse_tag_attribute(start_span) + } + } + + fn parse_meta_attribute(&mut self, name: Path, start_span: Span) -> Attribute { + let arguments = self.parse_arguments().unwrap_or_default(); + self.skip_until_right_bracket(); + Attribute::Secondary(SecondaryAttribute::Meta(MetaAttribute { + name, + arguments, + span: self.span_since(start_span), + })) + } + + fn parse_ident_attribute_other_than_test( + &mut self, + ident: &Ident, + start_span: Span, + ) -> Attribute { + let arguments = self.parse_arguments().unwrap_or_default(); + self.skip_until_right_bracket(); + match ident.0.contents.as_str() { + "abi" => self.parse_single_name_attribute(ident, arguments, start_span, |name| { + Attribute::Secondary(SecondaryAttribute::Abi(name)) + }), + "allow" => self.parse_single_name_attribute(ident, arguments, start_span, |name| { + Attribute::Secondary(SecondaryAttribute::Allow(name)) + }), + "builtin" => self.parse_single_name_attribute(ident, arguments, start_span, |name| { + Attribute::Function(FunctionAttribute::Builtin(name)) + }), + "deprecated" => self.parse_deprecated_attribute(ident, arguments), + "contract_library_method" => { + let attr = Attribute::Secondary(SecondaryAttribute::ContractLibraryMethod); + self.parse_no_args_attribute(ident, arguments, attr) + } + "export" => { + let attr = Attribute::Secondary(SecondaryAttribute::Export); + self.parse_no_args_attribute(ident, arguments, attr) + } + "field" => self.parse_single_name_attribute(ident, arguments, start_span, |name| { + Attribute::Secondary(SecondaryAttribute::Field(name)) + }), + "fold" => { + let attr = Attribute::Function(FunctionAttribute::Fold); + self.parse_no_args_attribute(ident, arguments, attr) + } + "foreign" => self.parse_single_name_attribute(ident, arguments, start_span, |name| { + Attribute::Function(FunctionAttribute::Foreign(name)) + }), + "inline_always" => { + let attr = Attribute::Function(FunctionAttribute::InlineAlways); + self.parse_no_args_attribute(ident, arguments, attr) + } + "no_predicates" => { + let attr = Attribute::Function(FunctionAttribute::NoPredicates); + self.parse_no_args_attribute(ident, arguments, attr) + } + "oracle" => self.parse_single_name_attribute(ident, arguments, start_span, |name| { + Attribute::Function(FunctionAttribute::Oracle(name)) + }), + "recursive" => { + let attr = Attribute::Function(FunctionAttribute::Recursive); + self.parse_no_args_attribute(ident, arguments, attr) + } + "use_callers_scope" => { + let attr = Attribute::Secondary(SecondaryAttribute::UseCallersScope); + self.parse_no_args_attribute(ident, arguments, attr) + } + "varargs" => { + let attr = Attribute::Secondary(SecondaryAttribute::Varargs); + self.parse_no_args_attribute(ident, arguments, attr) + } + _ => Attribute::Secondary(SecondaryAttribute::Meta(MetaAttribute { + name: Path::from_ident(ident.clone()), + arguments, + span: self.span_since(start_span), + })), + } + } + + fn parse_deprecated_attribute( + &mut self, + ident: &Ident, + mut arguments: Vec, + ) -> Attribute { + if arguments.is_empty() { + return Attribute::Secondary(SecondaryAttribute::Deprecated(None)); + } + + if arguments.len() > 1 { + self.push_error( + ParserErrorReason::WrongNumberOfAttributeArguments { + name: ident.to_string(), + min: 0, + max: 1, + found: arguments.len(), + }, + ident.span(), + ); + return Attribute::Secondary(SecondaryAttribute::Deprecated(None)); + } + + let argument = arguments.remove(0); + let ExpressionKind::Literal(Literal::Str(message)) = argument.kind else { + self.push_error( + ParserErrorReason::DeprecatedAttributeExpectsAStringArgument, + argument.span, + ); + return Attribute::Secondary(SecondaryAttribute::Deprecated(None)); + }; + + Attribute::Secondary(SecondaryAttribute::Deprecated(Some(message))) + } + + fn parse_test_attribute(&mut self, start_span: Span) -> Attribute { + let scope = if self.eat_left_paren() { + let scope = if let Some(ident) = self.eat_ident() { + match ident.0.contents.as_str() { + "should_fail" => Some(TestScope::ShouldFailWith { reason: None }), + "should_fail_with" => { + self.eat_or_error(Token::Assign); + if let Some(reason) = self.eat_str() { + Some(TestScope::ShouldFailWith { reason: Some(reason) }) + } else { + Some(TestScope::ShouldFailWith { reason: None }) + } + } + _ => None, + } + } else { + None + }; + self.eat_or_error(Token::RightParen); + scope + } else { + Some(TestScope::None) + }; + + self.skip_until_right_bracket(); + + let scope = if let Some(scope) = scope { + scope + } else { + self.errors.push( + LexerErrorKind::MalformedTestAttribute { span: self.span_since(start_span) }.into(), + ); + TestScope::None + }; + + Attribute::Function(FunctionAttribute::Test(scope)) + } + + fn parse_single_name_attribute( + &mut self, + ident: &Ident, + mut arguments: Vec, + start_span: Span, + f: F, + ) -> Attribute + where + F: FnOnce(String) -> Attribute, + { + if arguments.len() != 1 { + self.push_error( + ParserErrorReason::WrongNumberOfAttributeArguments { + name: ident.to_string(), + min: 1, + max: 1, + found: arguments.len(), + }, + self.current_token_span, + ); + return f(String::new()); + } + + let argument = arguments.remove(0); + match argument.kind { + ExpressionKind::Variable(..) | ExpressionKind::Literal(Literal::Integer(..)) => { + f(argument.to_string()) + } + _ => { + let span = self.span_since(start_span); + self.errors.push( + LexerErrorKind::MalformedFuncAttribute { span, found: argument.to_string() } + .into(), + ); + f(String::new()) + } + } + } + + fn parse_no_args_attribute( + &mut self, + ident: &Ident, + arguments: Vec, + attribute: Attribute, + ) -> Attribute { + if !arguments.is_empty() { + self.push_error( + ParserErrorReason::WrongNumberOfAttributeArguments { + name: ident.to_string(), + min: 0, + max: 0, + found: arguments.len(), + }, + ident.span(), + ); + } + + attribute + } + + fn skip_until_right_bracket(&mut self) { + let mut brackets_count = 1; // 1 because of the starting `#[` + + while !self.at_eof() { + if self.at(Token::LeftBracket) { + brackets_count += 1; + } else if self.at(Token::RightBracket) { + brackets_count -= 1; + if brackets_count == 0 { + self.bump(); + break; + } + } + + self.expected_token(Token::RightBracket); + self.bump(); + } + } } #[cfg(test)] mod tests { + use noirc_errors::Span; + use crate::{ parser::{parser::tests::expect_no_errors, Parser}, token::{Attribute, FunctionAttribute, SecondaryAttribute, TestScope}, }; + fn parse_inner_secondary_attribute_no_errors(src: &str, expected: SecondaryAttribute) { + let mut parser = Parser::for_str(src); + let attribute = parser.parse_inner_attribute(); + expect_no_errors(&parser.errors); + assert_eq!(attribute.unwrap(), expected); + } + + fn parse_attribute_no_errors(src: &str, expected: Attribute) { + let mut parser = Parser::for_str(src); + let (attribute, _span) = parser.parse_attribute().unwrap(); + expect_no_errors(&parser.errors); + assert_eq!(attribute, expected); + } + #[test] - fn parses_inner_attribute() { + fn parses_inner_attribute_as_tag() { let src = "#!['hello]"; let mut parser = Parser::for_str(src); let Some(SecondaryAttribute::Tag(custom)) = parser.parse_inner_attribute() else { @@ -62,6 +419,217 @@ mod tests { }; expect_no_errors(&parser.errors); assert_eq!(custom.contents, "hello"); + assert_eq!(custom.span, Span::from(0..src.len() as u32)); + assert_eq!(custom.contents_span, Span::from(4..src.len() as u32 - 1)); + } + + #[test] + fn parses_inner_attribute_as_tag_with_nested_brackets() { + let src = "#!['hello[1]]"; + let mut parser = Parser::for_str(src); + let Some(SecondaryAttribute::Tag(custom)) = parser.parse_inner_attribute() else { + panic!("Expected inner tag attribute"); + }; + expect_no_errors(&parser.errors); + assert_eq!(custom.contents, "hello[1]"); + } + + #[test] + fn parses_inner_attribute_deprecated() { + let src = "#![deprecated]"; + let expected = SecondaryAttribute::Deprecated(None); + parse_inner_secondary_attribute_no_errors(src, expected); + } + + #[test] + fn parses_inner_attribute_deprecated_with_message() { + let src = "#![deprecated(\"use something else\")]"; + let expected = SecondaryAttribute::Deprecated(Some("use something else".to_string())); + parse_inner_secondary_attribute_no_errors(src, expected); + } + + #[test] + fn parses_inner_attribute_contract_library_method() { + let src = "#![contract_library_method]"; + let expected = SecondaryAttribute::ContractLibraryMethod; + parse_inner_secondary_attribute_no_errors(src, expected); + } + + #[test] + fn parses_inner_attribute_export() { + let src = "#![export]"; + let expected = SecondaryAttribute::Export; + parse_inner_secondary_attribute_no_errors(src, expected); + } + + #[test] + fn parses_inner_attribute_varargs() { + let src = "#![varargs]"; + let expected = SecondaryAttribute::Varargs; + parse_inner_secondary_attribute_no_errors(src, expected); + } + + #[test] + fn parses_inner_attribute_use_callers_scope() { + let src = "#![use_callers_scope]"; + let expected = SecondaryAttribute::UseCallersScope; + parse_inner_secondary_attribute_no_errors(src, expected); + } + + #[test] + fn parses_attribute_abi() { + let src = "#[abi(foo)]"; + let expected = Attribute::Secondary(SecondaryAttribute::Abi("foo".to_string())); + parse_attribute_no_errors(src, expected); + } + + #[test] + fn parses_attribute_foreign() { + let src = "#[foreign(foo)]"; + let expected = Attribute::Function(FunctionAttribute::Foreign("foo".to_string())); + parse_attribute_no_errors(src, expected); + } + + #[test] + fn parses_attribute_builtin() { + let src = "#[builtin(foo)]"; + let expected = Attribute::Function(FunctionAttribute::Builtin("foo".to_string())); + parse_attribute_no_errors(src, expected); + } + + #[test] + fn parses_attribute_oracle() { + let src = "#[oracle(foo)]"; + let expected = Attribute::Function(FunctionAttribute::Oracle("foo".to_string())); + parse_attribute_no_errors(src, expected); + } + + #[test] + fn parses_attribute_recursive() { + let src = "#[recursive]"; + let expected = Attribute::Function(FunctionAttribute::Recursive); + parse_attribute_no_errors(src, expected); + } + + #[test] + fn parses_attribute_fold() { + let src = "#[fold]"; + let expected = Attribute::Function(FunctionAttribute::Fold); + parse_attribute_no_errors(src, expected); + } + + #[test] + fn parses_attribute_no_predicates() { + let src = "#[no_predicates]"; + let expected = Attribute::Function(FunctionAttribute::NoPredicates); + parse_attribute_no_errors(src, expected); + } + + #[test] + fn parses_attribute_inline_always() { + let src = "#[inline_always]"; + let expected = Attribute::Function(FunctionAttribute::InlineAlways); + parse_attribute_no_errors(src, expected); + } + + #[test] + fn parses_attribute_field() { + let src = "#[field(bn254)]"; + let expected = Attribute::Secondary(SecondaryAttribute::Field("bn254".to_string())); + parse_attribute_no_errors(src, expected); + } + + #[test] + fn parses_attribute_field_with_integer() { + let src = "#[field(23)]"; + let expected = Attribute::Secondary(SecondaryAttribute::Field("23".to_string())); + parse_attribute_no_errors(src, expected); + } + + #[test] + fn parses_attribute_allow() { + let src = "#[allow(unused_vars)]"; + let expected = Attribute::Secondary(SecondaryAttribute::Allow("unused_vars".to_string())); + parse_attribute_no_errors(src, expected); + } + + #[test] + fn parses_attribute_test_no_scope() { + let src = "#[test]"; + let expected = Attribute::Function(FunctionAttribute::Test(TestScope::None)); + parse_attribute_no_errors(src, expected); + } + + #[test] + fn parses_attribute_test_should_fail() { + let src = "#[test(should_fail)]"; + let expected = Attribute::Function(FunctionAttribute::Test(TestScope::ShouldFailWith { + reason: None, + })); + parse_attribute_no_errors(src, expected); + } + + #[test] + fn parses_attribute_test_should_fail_with() { + let src = "#[test(should_fail_with = \"reason\")]"; + let expected = Attribute::Function(FunctionAttribute::Test(TestScope::ShouldFailWith { + reason: Some("reason".to_string()), + })); + parse_attribute_no_errors(src, expected); + } + + #[test] + fn parses_meta_attribute_single_identifier_no_arguments() { + let src = "#[foo]"; + let mut parser = Parser::for_str(src); + let (attribute, _span) = parser.parse_attribute().unwrap(); + expect_no_errors(&parser.errors); + let Attribute::Secondary(SecondaryAttribute::Meta(meta)) = attribute else { + panic!("Expected meta attribute"); + }; + assert_eq!(meta.name.to_string(), "foo"); + assert!(meta.arguments.is_empty()); + } + + #[test] + fn parses_meta_attribute_single_identifier_as_keyword() { + let src = "#[dep]"; + let mut parser = Parser::for_str(src); + let (attribute, _span) = parser.parse_attribute().unwrap(); + expect_no_errors(&parser.errors); + let Attribute::Secondary(SecondaryAttribute::Meta(meta)) = attribute else { + panic!("Expected meta attribute"); + }; + assert_eq!(meta.name.to_string(), "dep"); + assert!(meta.arguments.is_empty()); + } + + #[test] + fn parses_meta_attribute_single_identifier_with_arguments() { + let src = "#[foo(1, 2, 3)]"; + let mut parser = Parser::for_str(src); + let (attribute, _span) = parser.parse_attribute().unwrap(); + expect_no_errors(&parser.errors); + let Attribute::Secondary(SecondaryAttribute::Meta(meta)) = attribute else { + panic!("Expected meta attribute"); + }; + assert_eq!(meta.name.to_string(), "foo"); + assert_eq!(meta.arguments.len(), 3); + assert_eq!(meta.arguments[0].to_string(), "1"); + } + + #[test] + fn parses_meta_attribute_path_with_arguments() { + let src = "#[foo::bar(1, 2, 3)]"; + let mut parser = Parser::for_str(src); + let (attribute, _span) = parser.parse_attribute().unwrap(); + expect_no_errors(&parser.errors); + let Attribute::Secondary(SecondaryAttribute::Meta(meta)) = attribute else { + panic!("Expected meta attribute"); + }; + assert_eq!(meta.name.to_string(), "foo::bar"); + assert_eq!(meta.arguments.len(), 3); + assert_eq!(meta.arguments[0].to_string(), "1"); } #[test] diff --git a/compiler/noirc_frontend/src/parser/parser/function.rs b/compiler/noirc_frontend/src/parser/parser/function.rs index a60bc6e7c1d..438757b5d79 100644 --- a/compiler/noirc_frontend/src/parser/parser/function.rs +++ b/compiler/noirc_frontend/src/parser/parser/function.rs @@ -246,22 +246,23 @@ impl<'a> Parser<'a> { } fn validate_attributes(&mut self, attributes: Vec<(Attribute, Span)>) -> Attributes { - let mut primary = None; + let mut function = None; let mut secondary = Vec::new(); - for (attribute, span) in attributes { + for (index, (attribute, span)) in attributes.into_iter().enumerate() { match attribute { Attribute::Function(attr) => { - if primary.is_some() { + if function.is_none() { + function = Some((attr, index)); + } else { self.push_error(ParserErrorReason::MultipleFunctionAttributesFound, span); } - primary = Some(attr); } Attribute::Secondary(attr) => secondary.push(attr), } } - Attributes { function: primary, secondary } + Attributes { function, secondary } } } diff --git a/noir_stdlib/src/ec/tecurve.nr b/noir_stdlib/src/ec/tecurve.nr index 8512413c831..45a6b322ed1 100644 --- a/noir_stdlib/src/ec/tecurve.nr +++ b/noir_stdlib/src/ec/tecurve.nr @@ -27,7 +27,7 @@ pub mod affine { impl Point { // Point constructor - #[deprecated = "It's recommmended to use the external noir-edwards library (https://github.com/noir-lang/noir-edwards)"] + // #[deprecated("It's recommmended to use the external noir-edwards library (https://github.com/noir-lang/noir-edwards)")] pub fn new(x: Field, y: Field) -> Self { Self { x, y } } diff --git a/test_programs/compile_success_empty/attribute_args/src/main.nr b/test_programs/compile_success_empty/attribute_args/src/main.nr index 492afd9e2f1..29690ba36c7 100644 --- a/test_programs/compile_success_empty/attribute_args/src/main.nr +++ b/test_programs/compile_success_empty/attribute_args/src/main.nr @@ -1,6 +1,6 @@ #[attr_with_args(1, 2)] -#[varargs(1, 2)] -#[varargs(1, 2, 3, 4)] +#[attr_with_varargs(1, 2)] +#[attr_with_varargs(1, 2, 3, 4)] pub struct Foo {} comptime fn attr_with_args(s: StructDefinition, a: Field, b: Field) { @@ -12,7 +12,7 @@ comptime fn attr_with_args(s: StructDefinition, a: Field, b: Field) { } #[varargs] -comptime fn varargs(s: StructDefinition, t: [Field]) { +comptime fn attr_with_varargs(s: StructDefinition, t: [Field]) { let _ = s; for _ in t {} assert(t.len() < 5); diff --git a/tooling/lsp/src/attribute_reference_finder.rs b/tooling/lsp/src/attribute_reference_finder.rs index f831f83d492..e3f31b65b46 100644 --- a/tooling/lsp/src/attribute_reference_finder.rs +++ b/tooling/lsp/src/attribute_reference_finder.rs @@ -16,8 +16,8 @@ use noirc_frontend::{ resolution::import::resolve_import, }, node_interner::ReferenceId, - parser::{ParsedSubModule, Parser}, - token::CustomAttribute, + parser::ParsedSubModule, + token::MetaAttribute, usage_tracker::UsageTracker, ParsedModule, }; @@ -85,20 +85,16 @@ impl<'a> Visitor for AttributeReferenceFinder<'a> { false } - fn visit_custom_attribute(&mut self, attribute: &CustomAttribute, _target: AttributeTarget) { - if !self.includes_span(attribute.contents_span) { - return; + fn visit_meta_attribute( + &mut self, + attribute: &MetaAttribute, + _target: AttributeTarget, + ) -> bool { + if !self.includes_span(attribute.span) { + return false; } - let name = match attribute.contents.split_once('(') { - Some((left, _right)) => left.to_string(), - None => attribute.contents.to_string(), - }; - let mut parser = Parser::for_str(&name); - let Some(path) = parser.parse_path_no_turbofish() else { - return; - }; - + let path = attribute.name.clone(); // The path here must resolve to a function and it's a simple path (can't have turbofish) // so it can (and must) be solved as an import. let Ok(Some((module_def_id, _, _))) = resolve_import( @@ -109,9 +105,11 @@ impl<'a> Visitor for AttributeReferenceFinder<'a> { None, // references tracker ) .map(|result| result.namespace.values) else { - return; + return true; }; self.reference_id = Some(module_def_id_to_reference_id(module_def_id)); + + true } } diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 3bfb411ea4d..8d0d47fb862 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -33,7 +33,7 @@ use noirc_frontend::{ hir_def::traits::Trait, node_interner::{NodeInterner, ReferenceId, StructId}, parser::{Item, ItemKind, ParsedSubModule}, - token::{CustomAttribute, Token, Tokens}, + token::{MetaAttribute, Token, Tokens}, Kind, ParsedModule, StructType, Type, TypeBinding, }; use sort_text::underscore_sort_text; @@ -893,24 +893,6 @@ impl<'a> NodeFinder<'a> { None } - fn suggest_attributes(&mut self, prefix: &str, target: AttributeTarget) { - self.suggest_builtin_attributes(prefix, target); - - let function_completion_kind = FunctionCompletionKind::NameAndParameters; - let requested_items = RequestedItems::OnlyAttributeFunctions(target); - - self.complete_in_module( - self.module_id, - prefix, - PathKind::Plain, - true, - function_completion_kind, - requested_items, - ); - - self.complete_auto_imports(prefix, requested_items, function_completion_kind); - } - fn suggest_no_arguments_attributes(&mut self, prefix: &str, attributes: &[&str]) { for name in attributes { if name_matches(name, prefix) { @@ -1667,12 +1649,14 @@ impl<'a> Visitor for NodeFinder<'a> { false } - fn visit_custom_attribute(&mut self, attribute: &CustomAttribute, target: AttributeTarget) { - if self.byte_index != attribute.contents_span.end() as usize { - return; + fn visit_meta_attribute(&mut self, attribute: &MetaAttribute, target: AttributeTarget) -> bool { + if self.byte_index == attribute.name.span.end() as usize { + self.suggest_builtin_attributes(&attribute.name.to_string(), target); } - self.suggest_attributes(&attribute.contents, target); + self.find_in_path(&attribute.name, RequestedItems::OnlyAttributeFunctions(target)); + + true } fn visit_quote(&mut self, tokens: &Tokens) { diff --git a/tooling/lsp/src/requests/completion/builtins.rs b/tooling/lsp/src/requests/completion/builtins.rs index c2c561ced32..b4c7d8b6e01 100644 --- a/tooling/lsp/src/requests/completion/builtins.rs +++ b/tooling/lsp/src/requests/completion/builtins.rs @@ -107,6 +107,15 @@ impl<'a> NodeFinder<'a> { let one_argument_attributes = &["abi", "field", "foreign", "oracle"]; self.suggest_one_argument_attributes(prefix, one_argument_attributes); + if name_matches("deprecated", prefix) { + self.completion_items.push(snippet_completion_item( + "deprecated(\"...\")", + CompletionItemKind::METHOD, + "deprecated(\"${1:message}\")", + None, + )); + } + if name_matches("test", prefix) || name_matches("should_fail", prefix) { self.completion_items.push(snippet_completion_item( "test(should_fail)", diff --git a/tooling/lsp/src/requests/completion/tests.rs b/tooling/lsp/src/requests/completion/tests.rs index 745dacfc152..8cfb2a4b5ee 100644 --- a/tooling/lsp/src/requests/completion/tests.rs +++ b/tooling/lsp/src/requests/completion/tests.rs @@ -2350,13 +2350,13 @@ fn main() { #[test] async fn test_suggests_built_in_function_attribute() { let src = r#" - #[dep>|<] + #[no_pred>|<] fn foo() {} "#; assert_completion_excluding_auto_import( src, - vec![simple_completion_item("deprecated", CompletionItemKind::METHOD, None)], + vec![simple_completion_item("no_predicates", CompletionItemKind::METHOD, None)], ) .await; } diff --git a/tooling/nargo_fmt/src/formatter.rs b/tooling/nargo_fmt/src/formatter.rs index 4ae5443a2cc..9a9386e1911 100644 --- a/tooling/nargo_fmt/src/formatter.rs +++ b/tooling/nargo_fmt/src/formatter.rs @@ -188,8 +188,7 @@ impl<'a> Formatter<'a> { pub(crate) fn write_token(&mut self, token: Token) { self.skip_comments_and_whitespace(); if self.token == token { - self.write_current_token(); - self.bump(); + self.write_current_token_and_bump(); } else { panic!("Expected token {:?}, got: {:?}", token, self.token); } @@ -200,6 +199,12 @@ impl<'a> Formatter<'a> { self.write(&self.token.to_string()); } + /// Writes the current token and advances to the next one + pub(crate) fn write_current_token_and_bump(&mut self) { + self.write(&self.token.to_string()); + self.bump(); + } + /// Writes the current token trimming its end but doesn't advance to the next one. /// Mainly used when writing comment lines, because we never want trailing spaces /// inside comments. diff --git a/tooling/nargo_fmt/src/formatter/attribute.rs b/tooling/nargo_fmt/src/formatter/attribute.rs index c13ba2a8c4c..7eae129f590 100644 --- a/tooling/nargo_fmt/src/formatter/attribute.rs +++ b/tooling/nargo_fmt/src/formatter/attribute.rs @@ -1,30 +1,343 @@ -use noirc_frontend::token::Token; +use noirc_frontend::token::{ + Attribute, Attributes, FunctionAttribute, MetaAttribute, SecondaryAttribute, TestScope, Token, +}; + +use crate::chunks::ChunkGroup; use super::Formatter; impl<'a> Formatter<'a> { - pub(super) fn format_attributes(&mut self) { - loop { - self.skip_comments_and_whitespace(); + pub(super) fn format_attributes(&mut self, attributes: Attributes) { + let mut all_attributes = Vec::new(); + for attribute in attributes.secondary { + all_attributes.push(Attribute::Secondary(attribute)); + } + if let Some((function_attribute, index)) = attributes.function { + all_attributes.insert(index, Attribute::Function(function_attribute)); + } + for attribute in all_attributes { + self.format_attribute(attribute); + } + } - if let Token::Attribute(_) = self.token { - self.write_indentation(); - self.write_current_token(); - self.bump(); - self.write_line(); - } else { - break; + pub(super) fn format_secondary_attributes(&mut self, attributes: Vec) { + for attribute in attributes { + self.format_secondary_attribute(attribute); + } + } + + fn format_attribute(&mut self, attribute: Attribute) { + match attribute { + Attribute::Function(function_attribute) => { + self.format_function_attribute(function_attribute); + } + Attribute::Secondary(secondary_attribute) => { + self.format_secondary_attribute(secondary_attribute); } } } - pub(super) fn format_inner_attribute(&mut self) { + fn format_function_attribute(&mut self, attribute: FunctionAttribute) { self.skip_comments_and_whitespace(); - let Token::InnerAttribute(..) = self.token else { - panic!("Expected inner attribute, got {:?}", self.token); - }; self.write_indentation(); - self.write_current_token(); - self.bump(); + + if !matches!(self.token, Token::AttributeStart { .. }) { + panic!("Expected attribute start, got: {:?}", self.token); + } + + match attribute { + FunctionAttribute::Foreign(_) + | FunctionAttribute::Builtin(_) + | FunctionAttribute::Oracle(_) => self.format_one_arg_attribute(), + FunctionAttribute::Test(test_scope) => self.format_test_attribute(test_scope), + FunctionAttribute::Recursive + | FunctionAttribute::Fold + | FunctionAttribute::NoPredicates + | FunctionAttribute::InlineAlways => self.format_no_args_attribute(), + } + + self.write_line(); + } + + pub(super) fn format_secondary_attribute(&mut self, attribute: SecondaryAttribute) { + self.skip_comments_and_whitespace(); + self.write_indentation(); + + if !matches!(self.token, Token::AttributeStart { .. }) { + panic!("Expected attribute start, got: {:?}", self.token); + } + + match attribute { + SecondaryAttribute::Deprecated(message) => { + self.format_deprecated_attribute(message); + } + SecondaryAttribute::ContractLibraryMethod + | SecondaryAttribute::Export + | SecondaryAttribute::Varargs + | SecondaryAttribute::UseCallersScope => { + self.format_no_args_attribute(); + } + SecondaryAttribute::Field(_) + | SecondaryAttribute::Abi(_) + | SecondaryAttribute::Allow(_) => { + self.format_one_arg_attribute(); + } + SecondaryAttribute::Tag(custom_attribute) => { + self.write_and_skip_span_without_formatting(custom_attribute.span); + } + SecondaryAttribute::Meta(meta_attribute) => { + self.format_meta_attribute(meta_attribute); + } + } + + self.write_line(); + } + + fn format_deprecated_attribute(&mut self, message: Option) { + self.write_current_token_and_bump(); // #[ + self.skip_comments_and_whitespace(); + if message.is_some() { + self.write_current_token_and_bump(); // deprecated + self.write_left_paren(); // ( + self.skip_comments_and_whitespace(); // message + self.write_current_token_and_bump(); // ) + self.write_right_paren(); + } else { + self.write_current_token_and_bump(); + } + self.write_right_bracket(); // ] + } + + fn format_test_attribute(&mut self, test_scope: TestScope) { + self.write_current_token_and_bump(); // #[ + self.skip_comments_and_whitespace(); + self.write_current_token_and_bump(); // test + + match test_scope { + TestScope::None => (), + TestScope::ShouldFailWith { reason: None } => { + self.write_left_paren(); // ( + self.skip_comments_and_whitespace(); + self.write_current_token_and_bump(); // should_fail + self.write_right_paren(); // ) + } + TestScope::ShouldFailWith { reason: Some(..) } => { + self.write_left_paren(); // ( + self.skip_comments_and_whitespace(); + self.write_current_token_and_bump(); // should_fail_with + self.write_space(); + self.write_token(Token::Assign); + self.write_space(); + self.skip_comments_and_whitespace(); + self.write_current_token_and_bump(); // "reason" + self.write_right_paren(); // ) + } + } + + self.write_right_bracket(); // ] + } + + fn format_meta_attribute(&mut self, meta_attribute: MetaAttribute) { + self.write_current_token_and_bump(); // #[ + self.skip_comments_and_whitespace(); + self.format_path(meta_attribute.name); + self.skip_comments_and_whitespace(); + if self.is_at(Token::LeftParen) { + let comments_count_before_arguments = self.written_comments_count; + let has_arguments = !meta_attribute.arguments.is_empty(); + + let mut chunk_formatter = self.chunk_formatter(); + let mut group = ChunkGroup::new(); + group.text(chunk_formatter.chunk(|formatter| { + formatter.write_left_paren(); + })); + chunk_formatter.format_expressions_separated_by_comma( + meta_attribute.arguments, + false, // force trailing comma + &mut group, + ); + group.text(chunk_formatter.chunk(|formatter| { + formatter.write_right_paren(); + })); + if has_arguments || self.written_comments_count > comments_count_before_arguments { + self.format_chunk_group(group); + } + } + self.write_right_bracket(); + } + + fn format_no_args_attribute(&mut self) { + self.write_current_token_and_bump(); // #[ + self.skip_comments_and_whitespace(); + self.write_current_token_and_bump(); // name + self.write_right_bracket(); // ] + } + + fn format_one_arg_attribute(&mut self) { + self.write_current_token_and_bump(); // #[ + self.skip_comments_and_whitespace(); + self.write_current_token_and_bump(); // name + self.write_left_paren(); // ( + loop { + self.skip_comments_and_whitespace(); + if self.is_at(Token::RightParen) { + self.write_right_paren(); + break; + } else { + self.write_current_token_and_bump(); + } + } + self.write_right_bracket(); // ] + } +} + +#[cfg(test)] +mod tests { + use crate::assert_format; + + fn assert_format_attribute(src: &str, expected: &str) { + let src = format!(" {src} fn foo() {{}}"); + let expected = format!("{expected}\nfn foo() {{}}\n"); + assert_format(&src, &expected); + } + + #[test] + fn format_inner_tag_attribute() { + let src = " #!['foo] "; + let expected = "#!['foo]\n"; + assert_format(src, expected); + } + + #[test] + fn format_deprecated_attribute() { + let src = " #[ deprecated ] "; + let expected = "#[deprecated]"; + assert_format_attribute(src, expected); + } + + #[test] + fn format_deprecated_attribute_with_message() { + let src = " #[ deprecated ( \"use something else\" ) ] "; + let expected = "#[deprecated(\"use something else\")]"; + assert_format_attribute(src, expected); + } + + #[test] + fn format_contract_library_method() { + let src = " #[ contract_library_method ] "; + let expected = "#[contract_library_method]"; + assert_format_attribute(src, expected); + } + + #[test] + fn format_export() { + let src = " #[ export ] "; + let expected = "#[export]"; + assert_format_attribute(src, expected); + } + + #[test] + fn format_varargs() { + let src = " #[ varargs ] "; + let expected = "#[varargs]"; + assert_format_attribute(src, expected); + } + + #[test] + fn format_use_callers_scope() { + let src = " #[ use_callers_scope ] "; + let expected = "#[use_callers_scope]"; + assert_format_attribute(src, expected); + } + + #[test] + fn format_field_attribute() { + let src = " #[ field ( bn256 ) ] "; + let expected = "#[field(bn256)]"; + assert_format_attribute(src, expected); + } + + #[test] + fn format_abi_attribute() { + let src = " #[ abi ( foo ) ] "; + let expected = "#[abi(foo)]"; + assert_format_attribute(src, expected); + } + + #[test] + fn format_allow_attribute() { + let src = " #[ allow ( unused_vars ) ] "; + let expected = "#[allow(unused_vars)]"; + assert_format_attribute(src, expected); + } + + #[test] + fn format_meta_attribute_without_arguments() { + let src = " #[ custom ] "; + let expected = "#[custom]"; + assert_format_attribute(src, expected); + } + + #[test] + fn format_meta_attribute_without_arguments_removes_parentheses() { + let src = " #[ custom ( ) ] "; + let expected = "#[custom]"; + assert_format_attribute(src, expected); + } + + #[test] + fn format_meta_attribute_without_arguments_but_comment() { + let src = " #[ custom ( /* nothing */ ) ] "; + let expected = "#[custom( /* nothing */ )]"; + assert_format_attribute(src, expected); + } + + #[test] + fn format_meta_attribute_with_arguments() { + let src = " #[ custom ( 1 , 2, [ 3, 4 ], ) ] "; + let expected = "#[custom(1, 2, [3, 4])]"; + assert_format_attribute(src, expected); + } + + #[test] + fn format_foreign_attribute() { + let src = " #[ foreign ( foo ) ] "; + let expected = "#[foreign(foo)]"; + assert_format_attribute(src, expected); + } + + #[test] + fn format_recursive_attribute() { + let src = " #[ recursive ] "; + let expected = "#[recursive]"; + assert_format_attribute(src, expected); + } + + #[test] + fn format_test_attribute() { + let src = " #[ test ] "; + let expected = "#[test]"; + assert_format_attribute(src, expected); + } + + #[test] + fn format_test_should_fail_attribute() { + let src = " #[ test ( should_fail )] "; + let expected = "#[test(should_fail)]"; + assert_format_attribute(src, expected); + } + + #[test] + fn format_test_should_fail_with_reason_attribute() { + let src = " #[ test ( should_fail_with=\"reason\" )] "; + let expected = "#[test(should_fail_with = \"reason\")]"; + assert_format_attribute(src, expected); + } + + #[test] + fn format_multiple_function_attributes() { + let src = " #[foo] #[test] #[bar] "; + let expected = "#[foo]\n#[test]\n#[bar]"; + assert_format_attribute(src, expected); } } diff --git a/tooling/nargo_fmt/src/formatter/comments_and_whitespace.rs b/tooling/nargo_fmt/src/formatter/comments_and_whitespace.rs index 547d33348b8..e20eb4291d1 100644 --- a/tooling/nargo_fmt/src/formatter/comments_and_whitespace.rs +++ b/tooling/nargo_fmt/src/formatter/comments_and_whitespace.rs @@ -162,8 +162,7 @@ impl<'a> Formatter<'a> { // will never write two consecutive spaces. self.write_space_without_skipping_whitespace_and_comments(); } - self.write_current_token(); - self.bump(); + self.write_current_token_and_bump(); passed_whitespace = false; last_was_block_comment = true; self.written_comments_count += 1; diff --git a/tooling/nargo_fmt/src/formatter/function.rs b/tooling/nargo_fmt/src/formatter/function.rs index 1dc9ead42e0..154b81a2cb3 100644 --- a/tooling/nargo_fmt/src/formatter/function.rs +++ b/tooling/nargo_fmt/src/formatter/function.rs @@ -3,13 +3,14 @@ use noirc_frontend::{ BlockExpression, FunctionReturnType, Ident, ItemVisibility, NoirFunction, Param, UnresolvedGenerics, UnresolvedTraitConstraint, Visibility, }, - token::{Keyword, Token}, + token::{Attributes, Keyword, Token}, }; use super::Formatter; use crate::chunks::{ChunkGroup, TextChunk}; pub(super) struct FunctionToFormat { + pub(super) attributes: Attributes, pub(super) visibility: ItemVisibility, pub(super) name: Ident, pub(super) generics: UnresolvedGenerics, @@ -23,6 +24,7 @@ pub(super) struct FunctionToFormat { impl<'a> Formatter<'a> { pub(super) fn format_function(&mut self, func: NoirFunction) { self.format_function_impl(FunctionToFormat { + attributes: func.def.attributes, visibility: func.def.visibility, name: func.def.name, generics: func.def.generics, @@ -37,7 +39,7 @@ impl<'a> Formatter<'a> { pub(super) fn format_function_impl(&mut self, func: FunctionToFormat) { let has_where_clause = !func.where_clause.is_empty(); - self.format_attributes(); + self.format_attributes(func.attributes); self.write_indentation(); self.format_function_modifiers(func.visibility); self.write_keyword(Keyword::Fn); diff --git a/tooling/nargo_fmt/src/formatter/item.rs b/tooling/nargo_fmt/src/formatter/item.rs index 77f1cd10cbc..521e476fe71 100644 --- a/tooling/nargo_fmt/src/formatter/item.rs +++ b/tooling/nargo_fmt/src/formatter/item.rs @@ -73,7 +73,7 @@ impl<'a> Formatter<'a> { ItemKind::Submodules(parsed_sub_module) => { self.format_submodule(parsed_sub_module); } - ItemKind::InnerAttribute(..) => self.format_inner_attribute(), + ItemKind::InnerAttribute(attribute) => self.format_secondary_attribute(attribute), } } diff --git a/tooling/nargo_fmt/src/formatter/module.rs b/tooling/nargo_fmt/src/formatter/module.rs index 3df9b7e0c90..e07d22c7586 100644 --- a/tooling/nargo_fmt/src/formatter/module.rs +++ b/tooling/nargo_fmt/src/formatter/module.rs @@ -6,9 +6,7 @@ use super::Formatter; impl<'a> Formatter<'a> { pub(super) fn format_module_declaration(&mut self, module_declaration: ModuleDeclaration) { - if !module_declaration.outer_attributes.is_empty() { - self.format_attributes(); - } + self.format_secondary_attributes(module_declaration.outer_attributes); self.write_indentation(); self.format_item_visibility(module_declaration.visibility); self.write_keyword(Keyword::Mod); @@ -18,9 +16,7 @@ impl<'a> Formatter<'a> { } pub(super) fn format_submodule(&mut self, submodule: ParsedSubModule) { - if !submodule.outer_attributes.is_empty() { - self.format_attributes(); - } + self.format_secondary_attributes(submodule.outer_attributes); self.write_indentation(); self.format_item_visibility(submodule.visibility); if submodule.is_contract { diff --git a/tooling/nargo_fmt/src/formatter/statement.rs b/tooling/nargo_fmt/src/formatter/statement.rs index 28107294909..4d16c6819d0 100644 --- a/tooling/nargo_fmt/src/formatter/statement.rs +++ b/tooling/nargo_fmt/src/formatter/statement.rs @@ -102,9 +102,7 @@ impl<'a, 'b> ChunkFormatter<'a, 'b> { let mut group = ChunkGroup::new(); group.text(self.chunk(|formatter| { - if !attributes.is_empty() { - formatter.format_attributes(); - } + formatter.format_secondary_attributes(attributes); formatter.write_keyword(keyword); formatter.write_space(); formatter.format_pattern(pattern); diff --git a/tooling/nargo_fmt/src/formatter/structs.rs b/tooling/nargo_fmt/src/formatter/structs.rs index 54bfc88264d..c26ab552f30 100644 --- a/tooling/nargo_fmt/src/formatter/structs.rs +++ b/tooling/nargo_fmt/src/formatter/structs.rs @@ -8,9 +8,7 @@ use crate::chunks::ChunkGroup; impl<'a> Formatter<'a> { pub(super) fn format_struct(&mut self, noir_struct: NoirStruct) { - if !noir_struct.attributes.is_empty() { - self.format_attributes(); - } + self.format_secondary_attributes(noir_struct.attributes); self.write_indentation(); self.format_item_visibility(noir_struct.visibility); self.write_keyword(Keyword::Struct); diff --git a/tooling/nargo_fmt/src/formatter/traits.rs b/tooling/nargo_fmt/src/formatter/traits.rs index 1379596b483..9a6b84c6537 100644 --- a/tooling/nargo_fmt/src/formatter/traits.rs +++ b/tooling/nargo_fmt/src/formatter/traits.rs @@ -1,15 +1,13 @@ use noirc_frontend::{ ast::{NoirTrait, Param, Pattern, TraitItem, Visibility}, - token::{Keyword, Token}, + token::{Attributes, Keyword, Token}, }; use super::{function::FunctionToFormat, Formatter}; impl<'a> Formatter<'a> { pub(super) fn format_trait(&mut self, noir_trait: NoirTrait) { - if !noir_trait.attributes.is_empty() { - self.format_attributes(); - } + self.format_secondary_attributes(noir_trait.attributes); self.write_indentation(); self.format_item_visibility(noir_trait.visibility); self.write_keyword(Keyword::Trait); @@ -91,6 +89,7 @@ impl<'a> Formatter<'a> { .collect(); let func = FunctionToFormat { + attributes: Attributes::empty(), visibility, name, generics, diff --git a/tooling/nargo_fmt/src/formatter/type_expression.rs b/tooling/nargo_fmt/src/formatter/type_expression.rs index 87ba1430f10..95b0c045156 100644 --- a/tooling/nargo_fmt/src/formatter/type_expression.rs +++ b/tooling/nargo_fmt/src/formatter/type_expression.rs @@ -14,14 +14,12 @@ impl<'a> Formatter<'a> { match type_expr { UnresolvedTypeExpression::Variable(path) => self.format_path(path), UnresolvedTypeExpression::Constant(..) => { - self.write_current_token(); - self.bump(); + self.write_current_token_and_bump(); } UnresolvedTypeExpression::BinaryOperation(lhs, _operator, rhs, _span) => { self.format_type_expression(*lhs); self.write_space(); - self.write_current_token(); - self.bump(); + self.write_current_token_and_bump(); self.write_space(); self.format_type_expression(*rhs); } diff --git a/tooling/nargo_fmt/src/formatter/types.rs b/tooling/nargo_fmt/src/formatter/types.rs index d2b5c4ab793..e52704ddaa7 100644 --- a/tooling/nargo_fmt/src/formatter/types.rs +++ b/tooling/nargo_fmt/src/formatter/types.rs @@ -18,8 +18,7 @@ impl<'a> Formatter<'a> { self.write_keyword(Keyword::Bool); } UnresolvedTypeData::Integer(..) | UnresolvedTypeData::FieldElement => { - self.write_current_token(); - self.bump(); + self.write_current_token_and_bump(); } UnresolvedTypeData::Array(type_expr, typ) => { self.write_left_bracket(); @@ -145,8 +144,7 @@ impl<'a> Formatter<'a> { } } UnresolvedTypeData::Quoted(..) => { - self.write_current_token(); - self.bump(); + self.write_current_token_and_bump(); } UnresolvedTypeData::AsTraitPath(as_trait_path) => { self.format_as_trait_path(*as_trait_path); diff --git a/tooling/nargo_fmt/src/formatter/visibility.rs b/tooling/nargo_fmt/src/formatter/visibility.rs index d1068aa4d05..27441b977bb 100644 --- a/tooling/nargo_fmt/src/formatter/visibility.rs +++ b/tooling/nargo_fmt/src/formatter/visibility.rs @@ -37,8 +37,7 @@ impl<'a> Formatter<'a> { self.write_keyword(Keyword::CallData); self.write_left_paren(); self.skip_comments_and_whitespace(); - self.write_current_token(); - self.bump(); + self.write_current_token_and_bump(); self.skip_comments_and_whitespace(); self.write_right_paren(); self.write_space();