diff options
-rw-r--r-- | crates/ide_completion/src/completions/attribute.rs | 173 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/attribute/derive.rs | 2 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/attribute/lint.rs | 2 |
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 | ||
6 | use std::mem; | ||
7 | |||
6 | use once_cell::sync::Lazy; | 8 | use once_cell::sync::Lazy; |
7 | use rustc_hash::{FxHashMap, FxHashSet}; | 9 | use rustc_hash::{FxHashMap, FxHashSet}; |
8 | use syntax::{ast, AstNode, SyntaxKind, T}; | 10 | use syntax::{ast, AstNode, NodeOrToken, SyntaxKind, T}; |
9 | 11 | ||
10 | use crate::{ | 12 | use crate::{ |
11 | context::CompletionContext, | 13 | context::CompletionContext, |
@@ -105,23 +107,32 @@ const fn attr( | |||
105 | } | 107 | } |
106 | 108 | ||
107 | macro_rules! attrs { | 109 | macro_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] |
131 | static KIND_TO_ATTRIBUTES: Lazy<FxHashMap<SyntaxKind, &[&str]>> = Lazy::new(|| { | 142 | static 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] | 272 | fn parse_comma_sep_input(derive_input: ast::TokenTree) -> Option<FxHashSet<String>> { |
261 | fn 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 | |||
276 | fn 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)] |
311 | mod tests { | 300 | mod 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)) |