aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ide_completion/src/completions/attribute.rs173
-rw-r--r--crates/ide_completion/src/completions/attribute/derive.rs2
-rw-r--r--crates/ide_completion/src/completions/attribute/lint.rs2
3 files changed, 92 insertions, 85 deletions
diff --git a/crates/ide_completion/src/completions/attribute.rs b/crates/ide_completion/src/completions/attribute.rs
index 8b018c32f..610fec65a 100644
--- a/crates/ide_completion/src/completions/attribute.rs
+++ b/crates/ide_completion/src/completions/attribute.rs
@@ -3,9 +3,11 @@
3//! This module uses a bit of static metadata to provide completions 3//! This module uses a bit of static metadata to provide completions
4//! for built-in attributes. 4//! for built-in attributes.
5 5
6use std::mem;
7
6use once_cell::sync::Lazy; 8use once_cell::sync::Lazy;
7use rustc_hash::{FxHashMap, FxHashSet}; 9use rustc_hash::{FxHashMap, FxHashSet};
8use syntax::{ast, AstNode, SyntaxKind, T}; 10use syntax::{ast, AstNode, NodeOrToken, SyntaxKind, T};
9 11
10use crate::{ 12use crate::{
11 context::CompletionContext, 13 context::CompletionContext,
@@ -105,23 +107,32 @@ const fn attr(
105} 107}
106 108
107macro_rules! attrs { 109macro_rules! attrs {
110 // attributes applicable to all items
108 [@ { item $($tt:tt)* } {$($acc:tt)*}] => { 111 [@ { item $($tt:tt)* } {$($acc:tt)*}] => {
109 attrs!(@ { $($tt)* } { $($acc)*, "deprecated", "doc", "dochidden", "docalias", "must_use", "no_mangle" }) 112 attrs!(@ { $($tt)* } { $($acc)*, "deprecated", "doc", "dochidden", "docalias", "must_use", "no_mangle" })
110 }; 113 };
114 // attributes applicable to all adts
111 [@ { adt $($tt:tt)* } {$($acc:tt)*}] => { 115 [@ { adt $($tt:tt)* } {$($acc:tt)*}] => {
112 attrs!(@ { $($tt)* } { $($acc)*, "derive", "repr" }) 116 attrs!(@ { $($tt)* } { $($acc)*, "derive", "repr" })
113 }; 117 };
118 // attributes applicable to all linkable things aka functions/statics
114 [@ { linkable $($tt:tt)* } {$($acc:tt)*}] => { 119 [@ { linkable $($tt:tt)* } {$($acc:tt)*}] => {
115 attrs!(@ { $($tt)* } { $($acc)*, "export_name", "link_name", "link_section" }) }; 120 attrs!(@ { $($tt)* } { $($acc)*, "export_name", "link_name", "link_section" })
116 [@ { $ty:ident $($tt:tt)* } {$($acc:tt)*}] => { compile_error!(concat!("unknown attr subtype ", stringify!($ty))) 121 };
122 // error fallback for nicer error message
123 [@ { $ty:ident $($tt:tt)* } {$($acc:tt)*}] => {
124 compile_error!(concat!("unknown attr subtype ", stringify!($ty)))
117 }; 125 };
126 // general push down accumulation
118 [@ { $lit:literal $($tt:tt)*} {$($acc:tt)*}] => { 127 [@ { $lit:literal $($tt:tt)*} {$($acc:tt)*}] => {
119 attrs!(@ { $($tt)* } { $($acc)*, $lit }) 128 attrs!(@ { $($tt)* } { $($acc)*, $lit })
120 }; 129 };
121 [@ {$($tt:tt)+} {$($tt2:tt)*}] => { 130 [@ {$($tt:tt)+} {$($tt2:tt)*}] => {
122 compile_error!(concat!("Unexpected input ", stringify!($($tt)+))) 131 compile_error!(concat!("Unexpected input ", stringify!($($tt)+)))
123 }; 132 };
133 // final output construction
124 [@ {} {$($tt:tt)*}] => { &[$($tt)*] as _ }; 134 [@ {} {$($tt:tt)*}] => { &[$($tt)*] as _ };
135 // starting matcher
125 [$($tt:tt),*] => { 136 [$($tt:tt),*] => {
126 attrs!(@ { $($tt)* } { "allow", "cfg", "cfg_attr", "deny", "forbid", "warn" }) 137 attrs!(@ { $($tt)* } { "allow", "cfg", "cfg_attr", "deny", "forbid", "warn" })
127 }; 138 };
@@ -129,28 +140,29 @@ macro_rules! attrs {
129 140
130#[rustfmt::skip] 141#[rustfmt::skip]
131static KIND_TO_ATTRIBUTES: Lazy<FxHashMap<SyntaxKind, &[&str]>> = Lazy::new(|| { 142static KIND_TO_ATTRIBUTES: Lazy<FxHashMap<SyntaxKind, &[&str]>> = Lazy::new(|| {
143 use SyntaxKind::*;
132 std::array::IntoIter::new([ 144 std::array::IntoIter::new([
133 ( 145 (
134 SyntaxKind::SOURCE_FILE, 146 SOURCE_FILE,
135 attrs!( 147 attrs!(
136 item, 148 item,
137 "crate_name", "feature", "no_implicit_prelude", "no_main", "no_std", 149 "crate_name", "feature", "no_implicit_prelude", "no_main", "no_std",
138 "recursion_limit", "type_length_limit", "windows_subsystem" 150 "recursion_limit", "type_length_limit", "windows_subsystem"
139 ), 151 ),
140 ), 152 ),
141 (SyntaxKind::MODULE, attrs!(item, "no_implicit_prelude", "path")), 153 (MODULE, attrs!(item, "no_implicit_prelude", "path")),
142 (SyntaxKind::ITEM_LIST, attrs!(item, "no_implicit_prelude")), 154 (ITEM_LIST, attrs!(item, "no_implicit_prelude")),
143 (SyntaxKind::MACRO_RULES, attrs!(item, "macro_export", "macro_use")), 155 (MACRO_RULES, attrs!(item, "macro_export", "macro_use")),
144 (SyntaxKind::MACRO_DEF, attrs!(item)), 156 (MACRO_DEF, attrs!(item)),
145 (SyntaxKind::EXTERN_CRATE, attrs!(item, "macro_use", "no_link")), 157 (EXTERN_CRATE, attrs!(item, "macro_use", "no_link")),
146 (SyntaxKind::USE, attrs!(item)), 158 (USE, attrs!(item)),
147 (SyntaxKind::TYPE_ALIAS, attrs!(item)), 159 (TYPE_ALIAS, attrs!(item)),
148 (SyntaxKind::STRUCT, attrs!(item, adt, "non_exhaustive")), 160 (STRUCT, attrs!(item, adt, "non_exhaustive")),
149 (SyntaxKind::ENUM, attrs!(item, adt, "non_exhaustive")), 161 (ENUM, attrs!(item, adt, "non_exhaustive")),
150 (SyntaxKind::UNION, attrs!(item, adt)), 162 (UNION, attrs!(item, adt)),
151 (SyntaxKind::CONST, attrs!(item)), 163 (CONST, attrs!(item)),
152 ( 164 (
153 SyntaxKind::FN, 165 FN,
154 attrs!( 166 attrs!(
155 item, linkable, 167 item, linkable,
156 "cold", "ignore", "inline", "must_use", "panic_handler", "proc_macro", 168 "cold", "ignore", "inline", "must_use", "panic_handler", "proc_macro",
@@ -158,29 +170,29 @@ static KIND_TO_ATTRIBUTES: Lazy<FxHashMap<SyntaxKind, &[&str]>> = Lazy::new(|| {
158 "test", "track_caller" 170 "test", "track_caller"
159 ), 171 ),
160 ), 172 ),
161 (SyntaxKind::STATIC, attrs!(item, linkable, "global_allocator", "used")), 173 (STATIC, attrs!(item, linkable, "global_allocator", "used")),
162 (SyntaxKind::TRAIT, attrs!(item, "must_use")), 174 (TRAIT, attrs!(item, "must_use")),
163 (SyntaxKind::IMPL, attrs!(item, "automatically_derived")), 175 (IMPL, attrs!(item, "automatically_derived")),
164 (SyntaxKind::ASSOC_ITEM_LIST, attrs!(item)), 176 (ASSOC_ITEM_LIST, attrs!(item)),
165 (SyntaxKind::EXTERN_BLOCK, attrs!(item, "link")), 177 (EXTERN_BLOCK, attrs!(item, "link")),
166 (SyntaxKind::EXTERN_ITEM_LIST, attrs!(item, "link")), 178 (EXTERN_ITEM_LIST, attrs!(item, "link")),
167 (SyntaxKind::MACRO_CALL, attrs!()), 179 (MACRO_CALL, attrs!()),
168 (SyntaxKind::SELF_PARAM, attrs!()), 180 (SELF_PARAM, attrs!()),
169 (SyntaxKind::PARAM, attrs!()), 181 (PARAM, attrs!()),
170 (SyntaxKind::RECORD_FIELD, attrs!()), 182 (RECORD_FIELD, attrs!()),
171 (SyntaxKind::VARIANT, attrs!("non_exhaustive")), 183 (VARIANT, attrs!("non_exhaustive")),
172 (SyntaxKind::TYPE_PARAM, attrs!()), 184 (TYPE_PARAM, attrs!()),
173 (SyntaxKind::CONST_PARAM, attrs!()), 185 (CONST_PARAM, attrs!()),
174 (SyntaxKind::LIFETIME_PARAM, attrs!()), 186 (LIFETIME_PARAM, attrs!()),
175 (SyntaxKind::LET_STMT, attrs!()), 187 (LET_STMT, attrs!()),
176 (SyntaxKind::EXPR_STMT, attrs!()), 188 (EXPR_STMT, attrs!()),
177 (SyntaxKind::LITERAL, attrs!()), 189 (LITERAL, attrs!()),
178 (SyntaxKind::RECORD_EXPR_FIELD_LIST, attrs!()), 190 (RECORD_EXPR_FIELD_LIST, attrs!()),
179 (SyntaxKind::RECORD_EXPR_FIELD, attrs!()), 191 (RECORD_EXPR_FIELD, attrs!()),
180 (SyntaxKind::MATCH_ARM_LIST, attrs!()), 192 (MATCH_ARM_LIST, attrs!()),
181 (SyntaxKind::MATCH_ARM, attrs!()), 193 (MATCH_ARM, attrs!()),
182 (SyntaxKind::IDENT_PAT, attrs!()), 194 (IDENT_PAT, attrs!()),
183 (SyntaxKind::RECORD_PAT_FIELD, attrs!()), 195 (RECORD_PAT_FIELD, attrs!()),
184 ]) 196 ])
185 .collect() 197 .collect()
186}); 198});
@@ -257,62 +269,57 @@ const ATTRIBUTES: &[AttrCompletion] = &[
257 .prefer_inner(), 269 .prefer_inner(),
258]; 270];
259 271
260#[test] 272fn parse_comma_sep_input(derive_input: ast::TokenTree) -> Option<FxHashSet<String>> {
261fn attributes_are_sorted() { 273 let (l_paren, r_paren) = derive_input.l_paren_token().zip(derive_input.r_paren_token())?;
262 let mut attrs = ATTRIBUTES.iter().map(|attr| attr.key()); 274 let mut input_derives = FxHashSet::default();
263 let mut prev = attrs.next().unwrap(); 275 let mut current_derive = String::new();
264 276 for token in derive_input
265 attrs.for_each(|next| { 277 .syntax()
266 assert!( 278 .children_with_tokens()
267 prev < next, 279 .filter_map(NodeOrToken::into_token)
268 r#"Attributes are not sorted, "{}" should come after "{}""#, 280 .skip_while(|token| token != &l_paren)
269 prev, 281 .skip(1)
270 next 282 .take_while(|token| token != &r_paren)
271 ); 283 {
272 prev = next; 284 if token.kind() == T![,] {
273 });
274}
275
276fn parse_comma_sep_input(derive_input: ast::TokenTree) -> Result<FxHashSet<String>, ()> {
277 match (derive_input.left_delimiter_token(), derive_input.right_delimiter_token()) {
278 (Some(left_paren), Some(right_paren))
279 if left_paren.kind() == T!['('] && right_paren.kind() == T![')'] =>
280 {
281 let mut input_derives = FxHashSet::default();
282 let mut current_derive = String::new();
283 for token in derive_input
284 .syntax()
285 .children_with_tokens()
286 .filter_map(|token| token.into_token())
287 .skip_while(|token| token != &left_paren)
288 .skip(1)
289 .take_while(|token| token != &right_paren)
290 {
291 if T![,] == token.kind() {
292 if !current_derive.is_empty() {
293 input_derives.insert(current_derive);
294 current_derive = String::new();
295 }
296 } else {
297 current_derive.push_str(token.text().trim());
298 }
299 }
300
301 if !current_derive.is_empty() { 285 if !current_derive.is_empty() {
302 input_derives.insert(current_derive); 286 input_derives.insert(mem::take(&mut current_derive));
303 } 287 }
304 Ok(input_derives) 288 } else {
289 current_derive.push_str(token.text().trim());
305 } 290 }
306 _ => Err(()),
307 } 291 }
292
293 if !current_derive.is_empty() {
294 input_derives.insert(current_derive);
295 }
296 Some(input_derives)
308} 297}
309 298
310#[cfg(test)] 299#[cfg(test)]
311mod tests { 300mod tests {
301 use super::*;
302
312 use expect_test::{expect, Expect}; 303 use expect_test::{expect, Expect};
313 304
314 use crate::{test_utils::completion_list, CompletionKind}; 305 use crate::{test_utils::completion_list, CompletionKind};
315 306
307 #[test]
308 fn attributes_are_sorted() {
309 let mut attrs = ATTRIBUTES.iter().map(|attr| attr.key());
310 let mut prev = attrs.next().unwrap();
311
312 attrs.for_each(|next| {
313 assert!(
314 prev < next,
315 r#"ATTRIBUTES array is not sorted, "{}" should come after "{}""#,
316 prev,
317 next
318 );
319 prev = next;
320 });
321 }
322
316 fn check(ra_fixture: &str, expect: Expect) { 323 fn check(ra_fixture: &str, expect: Expect) {
317 let actual = completion_list(ra_fixture, CompletionKind::Attribute); 324 let actual = completion_list(ra_fixture, CompletionKind::Attribute);
318 expect.assert_eq(&actual); 325 expect.assert_eq(&actual);
diff --git a/crates/ide_completion/src/completions/attribute/derive.rs b/crates/ide_completion/src/completions/attribute/derive.rs
index c213e4792..7b0a778a2 100644
--- a/crates/ide_completion/src/completions/attribute/derive.rs
+++ b/crates/ide_completion/src/completions/attribute/derive.rs
@@ -14,7 +14,7 @@ pub(super) fn complete_derive(
14 ctx: &CompletionContext, 14 ctx: &CompletionContext,
15 derive_input: ast::TokenTree, 15 derive_input: ast::TokenTree,
16) { 16) {
17 if let Ok(existing_derives) = super::parse_comma_sep_input(derive_input) { 17 if let Some(existing_derives) = super::parse_comma_sep_input(derive_input) {
18 for derive_completion in DEFAULT_DERIVE_COMPLETIONS 18 for derive_completion in DEFAULT_DERIVE_COMPLETIONS
19 .iter() 19 .iter()
20 .filter(|completion| !existing_derives.contains(completion.label)) 20 .filter(|completion| !existing_derives.contains(completion.label))
diff --git a/crates/ide_completion/src/completions/attribute/lint.rs b/crates/ide_completion/src/completions/attribute/lint.rs
index 8815e5867..115c6cfe0 100644
--- a/crates/ide_completion/src/completions/attribute/lint.rs
+++ b/crates/ide_completion/src/completions/attribute/lint.rs
@@ -13,7 +13,7 @@ pub(super) fn complete_lint(
13 derive_input: ast::TokenTree, 13 derive_input: ast::TokenTree,
14 lints_completions: &[LintCompletion], 14 lints_completions: &[LintCompletion],
15) { 15) {
16 if let Ok(existing_lints) = super::parse_comma_sep_input(derive_input) { 16 if let Some(existing_lints) = super::parse_comma_sep_input(derive_input) {
17 for lint_completion in lints_completions 17 for lint_completion in lints_completions
18 .into_iter() 18 .into_iter()
19 .filter(|completion| !existing_lints.contains(completion.label)) 19 .filter(|completion| !existing_lints.contains(completion.label))