From 49a09cbd22f086a1131f63b3704d95b109a0b394 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Fri, 6 Sep 2024 11:10:52 -0300 Subject: [PATCH 1/4] feat: LSP now suggests attributes --- compiler/noirc_frontend/src/ast/mod.rs | 1 + compiler/noirc_frontend/src/ast/visitor.rs | 61 ++++++++++++-- tooling/lsp/src/requests/completion.rs | 88 ++++++++++++++++++-- tooling/lsp/src/requests/completion/tests.rs | 14 ++++ 4 files changed, 151 insertions(+), 13 deletions(-) diff --git a/compiler/noirc_frontend/src/ast/mod.rs b/compiler/noirc_frontend/src/ast/mod.rs index 3fd63249201..1ed88115fa0 100644 --- a/compiler/noirc_frontend/src/ast/mod.rs +++ b/compiler/noirc_frontend/src/ast/mod.rs @@ -12,6 +12,7 @@ mod traits; mod type_alias; mod visitor; +pub use visitor::AttributeTarget; pub use visitor::Visitor; pub use expression::*; diff --git a/compiler/noirc_frontend/src/ast/visitor.rs b/compiler/noirc_frontend/src/ast/visitor.rs index 0aeeed39dd0..b8bb2e339c5 100644 --- a/compiler/noirc_frontend/src/ast/visitor.rs +++ b/compiler/noirc_frontend/src/ast/visitor.rs @@ -16,7 +16,7 @@ use crate::{ QuotedTypeId, }, parser::{Item, ItemKind, ParsedSubModule}, - token::{SecondaryAttribute, Tokens}, + token::{CustomAtrribute, SecondaryAttribute, Tokens}, ParsedModule, QuotedType, }; @@ -26,6 +26,13 @@ use super::{ UnresolvedTypeExpression, }; +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum AttributeTarget { + Module, + Struct, + Function, +} + /// Implements the [Visitor pattern](https://en.wikipedia.org/wiki/Visitor_pattern) for Noir's AST. /// /// In this implementation, methods must return a bool: @@ -127,7 +134,9 @@ pub trait Visitor { true } - fn visit_module_declaration(&mut self, _: &ModuleDeclaration, _: Span) {} + fn visit_module_declaration(&mut self, _: &ModuleDeclaration, _: Span) -> bool { + true + } fn visit_expression(&mut self, _: &Expression) -> bool { true @@ -433,7 +442,15 @@ pub trait Visitor { true } - fn visit_secondary_attribute(&mut self, _: &SecondaryAttribute, _: Span) {} + fn visit_secondary_attribute( + &mut self, + _: &SecondaryAttribute, + _target: AttributeTarget, + ) -> bool { + true + } + + fn visit_custom_attribute(&mut self, _: &CustomAtrribute, _target: AttributeTarget) {} } impl ParsedModule { @@ -484,7 +501,7 @@ impl Item { module_declaration.accept(self.span, visitor); } ItemKind::InnerAttribute(attribute) => { - attribute.accept(self.span, visitor); + attribute.accept(AttributeTarget::Module, visitor); } } } @@ -498,6 +515,10 @@ impl ParsedSubModule { } pub fn accept_children(&self, visitor: &mut impl Visitor) { + for attribute in &self.outer_attributes { + attribute.accept(AttributeTarget::Module, visitor); + } + self.contents.accept(visitor); } } @@ -510,6 +531,10 @@ impl NoirFunction { } pub fn accept_children(&self, visitor: &mut impl Visitor) { + for attribute in self.secondary_attributes() { + attribute.accept(AttributeTarget::Function, visitor); + } + for param in &self.def.parameters { param.typ.accept(visitor); } @@ -674,6 +699,10 @@ impl NoirStruct { } pub fn accept_children(&self, visitor: &mut impl Visitor) { + for attribute in &self.attributes { + attribute.accept(AttributeTarget::Struct, visitor); + } + for (_name, unresolved_type) in &self.fields { unresolved_type.accept(visitor); } @@ -694,7 +723,11 @@ impl NoirTypeAlias { impl ModuleDeclaration { pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { - visitor.visit_module_declaration(self, span); + if visitor.visit_module_declaration(self, span) { + for attribute in &self.outer_attributes { + attribute.accept(AttributeTarget::Module, visitor); + } + } } } @@ -1295,8 +1328,22 @@ impl Pattern { } impl SecondaryAttribute { - pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { - visitor.visit_secondary_attribute(self, span); + pub fn accept(&self, target: AttributeTarget, visitor: &mut impl Visitor) { + if visitor.visit_secondary_attribute(self, target) { + self.accept_children(target, visitor); + } + } + + pub fn accept_children(&self, target: AttributeTarget, visitor: &mut impl Visitor) { + if let SecondaryAttribute::Custom(custom) = self { + custom.accept(target, visitor); + } + } +} + +impl CustomAtrribute { + pub fn accept(&self, target: AttributeTarget, visitor: &mut impl Visitor) { + visitor.visit_custom_attribute(self, target); } } diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 51790ec107b..6809fedc60a 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -5,7 +5,7 @@ use std::{ use async_lsp::ResponseError; use completion_items::{ - crate_completion_item, field_completion_item, simple_completion_item, + crate_completion_item, field_completion_item, simple_completion_item, snippet_completion_item, struct_field_completion_item, }; use convert_case::{Case, Casing}; @@ -15,11 +15,11 @@ use lsp_types::{CompletionItem, CompletionItemKind, CompletionParams, Completion use noirc_errors::{Location, Span}; use noirc_frontend::{ ast::{ - AsTraitPath, BlockExpression, CallExpression, ConstructorExpression, Expression, - ExpressionKind, ForLoopStatement, GenericTypeArgs, Ident, IfExpression, ItemVisibility, - Lambda, LetStatement, MemberAccessExpression, MethodCallExpression, NoirFunction, - NoirStruct, NoirTraitImpl, Path, PathKind, Pattern, Statement, TypeImpl, UnresolvedGeneric, - UnresolvedGenerics, UnresolvedType, UseTree, UseTreeKind, Visitor, + AsTraitPath, AttributeTarget, BlockExpression, CallExpression, ConstructorExpression, + Expression, ExpressionKind, ForLoopStatement, GenericTypeArgs, Ident, IfExpression, + ItemVisibility, Lambda, LetStatement, MemberAccessExpression, MethodCallExpression, + NoirFunction, NoirStruct, NoirTraitImpl, Path, PathKind, Pattern, Statement, TypeImpl, + UnresolvedGeneric, UnresolvedGenerics, UnresolvedType, UseTree, UseTreeKind, Visitor, }, graph::{CrateId, Dependency}, hir::def_map::{CrateDefMap, LocalModuleId, ModuleId}, @@ -27,6 +27,7 @@ use noirc_frontend::{ macros_api::{ModuleDefId, NodeInterner}, node_interner::ReferenceId, parser::{Item, ItemKind, ParsedSubModule}, + token::CustomAtrribute, ParsedModule, StructType, Type, }; use sort_text::underscore_sort_text; @@ -786,6 +787,65 @@ impl<'a> NodeFinder<'a> { None } + fn suggest_attributes(&mut self, prefix: &str, target: AttributeTarget) { + match target { + AttributeTarget::Module => (), + AttributeTarget::Struct => { + self.suggest_one_argument_attributes(prefix, &["abi"]); + } + AttributeTarget::Function => { + let no_arguments_attributes = &[ + "contract_library_method", + "deprecated", + "export", + "fold", + "no_predicated", + "recursive", + "test", + "varargs", + ]; + self.suggest_no_arguments_attributes(prefix, no_arguments_attributes); + + let one_argument_attributes = &["abi", "field", "foreign", "oracle"]; + self.suggest_one_argument_attributes(prefix, one_argument_attributes); + + if "test".starts_with(prefix) { + self.completion_items.push(snippet_completion_item( + "test(should_fail_with=\"...\")", + CompletionItemKind::METHOD, + "test(should_fail_with=\"${1:message}\")", + None, + )); + } + } + } + } + + fn suggest_no_arguments_attributes(&mut self, prefix: &str, attributes: &[&str]) { + for name in attributes { + if name.starts_with(prefix) { + self.completion_items.push(simple_completion_item( + *name, + CompletionItemKind::METHOD, + None, + )); + } + } + } + + fn suggest_one_argument_attributes(&mut self, prefix: &str, attributes: &[&str]) { + for name in attributes { + if name.starts_with(prefix) { + self.completion_items.push(snippet_completion_item( + format!("{}(…)", name), + CompletionItemKind::METHOD, + format!("{}(${{1:name}})", name), + None, + )); + } + } + } + fn try_set_self_type(&mut self, pattern: &Pattern) { match pattern { Pattern::Identifier(ident) => { @@ -855,6 +915,10 @@ impl<'a> Visitor for NodeFinder<'a> { } fn visit_noir_function(&mut self, noir_function: &NoirFunction, span: Span) -> bool { + for attribute in noir_function.secondary_attributes() { + attribute.accept(AttributeTarget::Function, self); + } + let old_type_parameters = self.type_parameters.clone(); self.collect_type_parameters_in_generics(&noir_function.def.generics); @@ -915,6 +979,10 @@ impl<'a> Visitor for NodeFinder<'a> { } fn visit_noir_struct(&mut self, noir_struct: &NoirStruct, _: Span) -> bool { + for attribute in &noir_struct.attributes { + attribute.accept(AttributeTarget::Struct, self); + } + self.type_parameters.clear(); self.collect_type_parameters_in_generics(&noir_struct.generics); @@ -1263,6 +1331,14 @@ impl<'a> Visitor for NodeFinder<'a> { unresolved_types.accept(self); false } + + fn visit_custom_attribute(&mut self, attribute: &CustomAtrribute, target: AttributeTarget) { + if self.byte_index != attribute.contents_span.end() as usize { + return; + } + + self.suggest_attributes(&attribute.contents, target); + } } /// Returns true if name matches a prefix written in code. diff --git a/tooling/lsp/src/requests/completion/tests.rs b/tooling/lsp/src/requests/completion/tests.rs index caf5a60e4b8..f81b8dd4d5f 100644 --- a/tooling/lsp/src/requests/completion/tests.rs +++ b/tooling/lsp/src/requests/completion/tests.rs @@ -1917,4 +1917,18 @@ mod completion_tests { ) .await; } + + #[test] + async fn test_suggests_function_attribute() { + let src = r#" + #[dep>|<] + fn foo() {} + "#; + + assert_completion_excluding_auto_import( + src, + vec![simple_completion_item("deprecated", CompletionItemKind::METHOD, None)], + ) + .await; + } } From a81a90fa55d2f806874b9cc9b8918b8f62b5db59 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Fri, 6 Sep 2024 12:10:33 -0300 Subject: [PATCH 2/4] Also suggest attribute functions --- compiler/noirc_frontend/src/ast/visitor.rs | 20 ++++---- tooling/lsp/src/requests/completion.rs | 17 +++++++ .../requests/completion/completion_items.rs | 48 +++++++++++++++++-- tooling/lsp/src/requests/completion/kinds.rs | 4 +- tooling/lsp/src/requests/completion/tests.rs | 23 ++++++++- 5 files changed, 96 insertions(+), 16 deletions(-) diff --git a/compiler/noirc_frontend/src/ast/visitor.rs b/compiler/noirc_frontend/src/ast/visitor.rs index b8bb2e339c5..64b479b5fd6 100644 --- a/compiler/noirc_frontend/src/ast/visitor.rs +++ b/compiler/noirc_frontend/src/ast/visitor.rs @@ -134,9 +134,7 @@ pub trait Visitor { true } - fn visit_module_declaration(&mut self, _: &ModuleDeclaration, _: Span) -> bool { - true - } + fn visit_module_declaration(&mut self, _: &ModuleDeclaration, _: Span) {} fn visit_expression(&mut self, _: &Expression) -> bool { true @@ -509,16 +507,16 @@ impl Item { impl ParsedSubModule { pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { + for attribute in &self.outer_attributes { + attribute.accept(AttributeTarget::Module, visitor); + } + if visitor.visit_parsed_submodule(self, span) { self.accept_children(visitor); } } pub fn accept_children(&self, visitor: &mut impl Visitor) { - for attribute in &self.outer_attributes { - attribute.accept(AttributeTarget::Module, visitor); - } - self.contents.accept(visitor); } } @@ -723,11 +721,11 @@ impl NoirTypeAlias { impl ModuleDeclaration { pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { - if visitor.visit_module_declaration(self, span) { - for attribute in &self.outer_attributes { - attribute.accept(AttributeTarget::Module, visitor); - } + for attribute in &self.outer_attributes { + attribute.accept(AttributeTarget::Module, visitor); } + + visitor.visit_module_declaration(self, span); } } diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 6809fedc60a..19b853605e3 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -360,6 +360,7 @@ impl<'a> NodeFinder<'a> { self.builtin_types_completion(&prefix); self.type_parameters_completion(&prefix); } + RequestedItems::OnlyAttributeFunctions(..) => (), } self.complete_auto_imports(&prefix, requested_items, function_completion_kind); } @@ -607,6 +608,7 @@ impl<'a> NodeFinder<'a> { func_id, function_completion_kind, function_kind, + None, // attribute first type self_prefix, ) { self.completion_items.push(completion_item); @@ -633,6 +635,7 @@ impl<'a> NodeFinder<'a> { *func_id, function_completion_kind, function_kind, + None, // attribute first type self_prefix, ) { self.completion_items.push(completion_item); @@ -819,6 +822,20 @@ impl<'a> NodeFinder<'a> { } } } + + 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]) { diff --git a/tooling/lsp/src/requests/completion/completion_items.rs b/tooling/lsp/src/requests/completion/completion_items.rs index 3ecb5f2da3f..c3afc225f52 100644 --- a/tooling/lsp/src/requests/completion/completion_items.rs +++ b/tooling/lsp/src/requests/completion/completion_items.rs @@ -2,10 +2,11 @@ use lsp_types::{ Command, CompletionItem, CompletionItemKind, CompletionItemLabelDetails, InsertTextFormat, }; use noirc_frontend::{ + ast::AttributeTarget, hir_def::{function::FuncMeta, stmt::HirPattern}, macros_api::ModuleDefId, node_interner::{FuncId, GlobalId}, - Type, + QuotedType, Type, }; use super::{ @@ -33,9 +34,25 @@ impl<'a> NodeFinder<'a> { | ModuleDefId::TypeAliasId(_) | ModuleDefId::TraitId(_) => (), }, + RequestedItems::OnlyAttributeFunctions(..) => { + if !matches!(module_def_id, ModuleDefId::FunctionId(..)) { + return None; + } + } RequestedItems::AnyItems => (), } + let attribute_first_type = + if let RequestedItems::OnlyAttributeFunctions(target) = requested_items { + match target { + AttributeTarget::Module => Some(Type::Quoted(QuotedType::Module)), + AttributeTarget::Struct => Some(Type::Quoted(QuotedType::StructDefinition)), + AttributeTarget::Function => Some(Type::Quoted(QuotedType::FunctionDefinition)), + } + } else { + None + }; + match module_def_id { ModuleDefId::ModuleId(_) => Some(module_completion_item(name)), ModuleDefId::FunctionId(func_id) => self.function_completion_item( @@ -43,6 +60,7 @@ impl<'a> NodeFinder<'a> { func_id, function_completion_kind, function_kind, + attribute_first_type.as_ref(), false, // self_prefix ), ModuleDefId::TypeId(..) => Some(self.struct_completion_item(name)), @@ -78,6 +96,7 @@ impl<'a> NodeFinder<'a> { func_id: FuncId, function_completion_kind: FunctionCompletionKind, function_kind: FunctionKind, + attribute_first_type: Option<&Type>, self_prefix: bool, ) -> Option { let func_meta = self.interner.function_meta(&func_id); @@ -97,6 +116,17 @@ impl<'a> NodeFinder<'a> { None }; + if let Some(attribute_first_type) = attribute_first_type { + if func_meta.parameters.is_empty() { + return None; + } + + let (_, typ, _) = &func_meta.parameters.0[0]; + if typ != attribute_first_type { + return None; + } + } + match function_kind { FunctionKind::Any => (), FunctionKind::SelfType(mut self_type) => { @@ -147,7 +177,13 @@ impl<'a> NodeFinder<'a> { } FunctionCompletionKind::NameAndParameters => { let kind = CompletionItemKind::FUNCTION; - let insert_text = self.compute_function_insert_text(func_meta, name, function_kind); + let skip_first_argument = attribute_first_type.is_some(); + let insert_text = self.compute_function_insert_text( + func_meta, + name, + function_kind, + skip_first_argument, + ); let label = if insert_text.ends_with("()") { format!("{}()", name) } else { @@ -183,13 +219,19 @@ impl<'a> NodeFinder<'a> { func_meta: &FuncMeta, name: &str, function_kind: FunctionKind, + skip_first_argument: bool, ) -> String { let mut text = String::new(); text.push_str(name); text.push('('); + let mut parameters = func_meta.parameters.0.iter(); + if skip_first_argument { + parameters.next(); + } + let mut index = 1; - for (pattern, _, _) in &func_meta.parameters.0 { + for (pattern, _, _) in parameters { if index == 1 { match function_kind { FunctionKind::SelfType(_) => { diff --git a/tooling/lsp/src/requests/completion/kinds.rs b/tooling/lsp/src/requests/completion/kinds.rs index 2fe039ba331..6fa74ffdb1a 100644 --- a/tooling/lsp/src/requests/completion/kinds.rs +++ b/tooling/lsp/src/requests/completion/kinds.rs @@ -1,4 +1,4 @@ -use noirc_frontend::Type; +use noirc_frontend::{ast::AttributeTarget, Type}; /// When suggest a function as a result of completion, whether to autocomplete its name or its name and parameters. #[derive(Clone, Copy, PartialEq, Eq, Debug)] @@ -27,4 +27,6 @@ pub(super) enum RequestedItems { AnyItems, // Only suggest types. OnlyTypes, + // Only attribute functions + OnlyAttributeFunctions(AttributeTarget), } diff --git a/tooling/lsp/src/requests/completion/tests.rs b/tooling/lsp/src/requests/completion/tests.rs index f81b8dd4d5f..e6a732e9142 100644 --- a/tooling/lsp/src/requests/completion/tests.rs +++ b/tooling/lsp/src/requests/completion/tests.rs @@ -1919,7 +1919,7 @@ mod completion_tests { } #[test] - async fn test_suggests_function_attribute() { + async fn test_suggests_built_in_function_attribute() { let src = r#" #[dep>|<] fn foo() {} @@ -1931,4 +1931,25 @@ mod completion_tests { ) .await; } + + #[test] + async fn test_suggests_function_attribute() { + let src = r#" + #[some>|<] + fn foo() {} + + fn some_attr(f: FunctionDefinition, x: Field) {} + fn some_other_function(x: Field) {} + "#; + + assert_completion_excluding_auto_import( + src, + vec![function_completion_item( + "some_attr(…)", + "some_attr(${1:x})", + "fn(FunctionDefinition, Field)", + )], + ) + .await; + } } From 94e964f76ba8fe6cb28d56e2d2e4b3934bc2a68b Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Fri, 6 Sep 2024 13:45:21 -0300 Subject: [PATCH 3/4] Use `name_matches` --- tooling/lsp/src/requests/completion.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 19b853605e3..4256befae08 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -802,7 +802,7 @@ impl<'a> NodeFinder<'a> { "deprecated", "export", "fold", - "no_predicated", + "no_predicates", "recursive", "test", "varargs", @@ -812,7 +812,7 @@ impl<'a> NodeFinder<'a> { let one_argument_attributes = &["abi", "field", "foreign", "oracle"]; self.suggest_one_argument_attributes(prefix, one_argument_attributes); - if "test".starts_with(prefix) { + if name_matches("test", prefix) || name_matches("should_fail_with", prefix) { self.completion_items.push(snippet_completion_item( "test(should_fail_with=\"...\")", CompletionItemKind::METHOD, @@ -840,7 +840,7 @@ impl<'a> NodeFinder<'a> { fn suggest_no_arguments_attributes(&mut self, prefix: &str, attributes: &[&str]) { for name in attributes { - if name.starts_with(prefix) { + if name_matches(name, prefix) { self.completion_items.push(simple_completion_item( *name, CompletionItemKind::METHOD, @@ -852,7 +852,7 @@ impl<'a> NodeFinder<'a> { fn suggest_one_argument_attributes(&mut self, prefix: &str, attributes: &[&str]) { for name in attributes { - if name.starts_with(prefix) { + if name_matches(name, prefix) { self.completion_items.push(snippet_completion_item( format!("{}(…)", name), CompletionItemKind::METHOD, From 157828094c81de650c5d0e204fc2b24e995d19c6 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Fri, 6 Sep 2024 13:47:10 -0300 Subject: [PATCH 4/4] Move some code to builtins --- tooling/lsp/src/requests/completion.rs | 32 +---------------- .../lsp/src/requests/completion/builtins.rs | 36 ++++++++++++++++++- 2 files changed, 36 insertions(+), 32 deletions(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 4256befae08..dad0d37aba7 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -791,37 +791,7 @@ impl<'a> NodeFinder<'a> { } fn suggest_attributes(&mut self, prefix: &str, target: AttributeTarget) { - match target { - AttributeTarget::Module => (), - AttributeTarget::Struct => { - self.suggest_one_argument_attributes(prefix, &["abi"]); - } - AttributeTarget::Function => { - let no_arguments_attributes = &[ - "contract_library_method", - "deprecated", - "export", - "fold", - "no_predicates", - "recursive", - "test", - "varargs", - ]; - self.suggest_no_arguments_attributes(prefix, no_arguments_attributes); - - let one_argument_attributes = &["abi", "field", "foreign", "oracle"]; - self.suggest_one_argument_attributes(prefix, one_argument_attributes); - - if name_matches("test", prefix) || name_matches("should_fail_with", prefix) { - self.completion_items.push(snippet_completion_item( - "test(should_fail_with=\"...\")", - CompletionItemKind::METHOD, - "test(should_fail_with=\"${1:message}\")", - None, - )); - } - } - } + self.suggest_builtin_attributes(prefix, target); let function_completion_kind = FunctionCompletionKind::NameAndParameters; let requested_items = RequestedItems::OnlyAttributeFunctions(target); diff --git a/tooling/lsp/src/requests/completion/builtins.rs b/tooling/lsp/src/requests/completion/builtins.rs index f449177a027..bca1061ff47 100644 --- a/tooling/lsp/src/requests/completion/builtins.rs +++ b/tooling/lsp/src/requests/completion/builtins.rs @@ -1,5 +1,5 @@ use lsp_types::CompletionItemKind; -use noirc_frontend::token::Keyword; +use noirc_frontend::{ast::AttributeTarget, token::Keyword}; use strum::IntoEnumIterator; use super::{ @@ -84,6 +84,40 @@ impl<'a> NodeFinder<'a> { } } } + + pub(super) fn suggest_builtin_attributes(&mut self, prefix: &str, target: AttributeTarget) { + match target { + AttributeTarget::Module => (), + AttributeTarget::Struct => { + self.suggest_one_argument_attributes(prefix, &["abi"]); + } + AttributeTarget::Function => { + let no_arguments_attributes = &[ + "contract_library_method", + "deprecated", + "export", + "fold", + "no_predicates", + "recursive", + "test", + "varargs", + ]; + self.suggest_no_arguments_attributes(prefix, no_arguments_attributes); + + let one_argument_attributes = &["abi", "field", "foreign", "oracle"]; + self.suggest_one_argument_attributes(prefix, one_argument_attributes); + + if name_matches("test", prefix) || name_matches("should_fail_with", prefix) { + self.completion_items.push(snippet_completion_item( + "test(should_fail_with=\"...\")", + CompletionItemKind::METHOD, + "test(should_fail_with=\"${1:message}\")", + None, + )); + } + } + } + } } pub(super) fn builtin_integer_types() -> [&'static str; 8] {