|
3 | 3 | //! This module uses a bit of static metadata to provide completions
|
4 | 4 | //! for built-in attributes.
|
5 | 5 |
|
| 6 | +use std::mem; |
| 7 | + |
6 | 8 | use once_cell::sync::Lazy;
|
7 | 9 | use rustc_hash::{FxHashMap, FxHashSet};
|
8 |
| -use syntax::{ast, AstNode, SyntaxKind, T}; |
| 10 | +use syntax::{ast, AstNode, NodeOrToken, SyntaxKind, T}; |
9 | 11 |
|
10 | 12 | use crate::{
|
11 | 13 | context::CompletionContext,
|
@@ -105,82 +107,92 @@ const fn attr(
|
105 | 107 | }
|
106 | 108 |
|
107 | 109 | macro_rules! attrs {
|
| 110 | + // attributes applicable to all items |
108 | 111 | [@ { item $($tt:tt)* } {$($acc:tt)*}] => {
|
109 | 112 | attrs!(@ { $($tt)* } { $($acc)*, "deprecated", "doc", "dochidden", "docalias", "must_use", "no_mangle" })
|
110 | 113 | };
|
| 114 | + // attributes applicable to all adts |
111 | 115 | [@ { adt $($tt:tt)* } {$($acc:tt)*}] => {
|
112 | 116 | attrs!(@ { $($tt)* } { $($acc)*, "derive", "repr" })
|
113 | 117 | };
|
| 118 | + // attributes applicable to all linkable things aka functions/statics |
114 | 119 | [@ { linkable $($tt:tt)* } {$($acc:tt)*}] => {
|
115 |
| - attrs!(@ { $($tt)* } { $($acc)*, "export_name", "link_name", "link_section" }) }; |
116 |
| - [@ { $ty:ident $($tt:tt)* } {$($acc:tt)*}] => { compile_error!(concat!("unknown attr subtype ", stringify!($ty))) |
| 120 | + attrs!(@ { $($tt)* } { $($acc)*, "export_name", "link_name", "link_section" }) |
| 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 | 127 | [@ { $lit:literal $($tt:tt)*} {$($acc:tt)*}] => {
|
119 | 128 | attrs!(@ { $($tt)* } { $($acc)*, $lit })
|
120 | 129 | };
|
121 | 130 | [@ {$($tt:tt)+} {$($tt2:tt)*}] => {
|
122 | 131 | compile_error!(concat!("Unexpected input ", stringify!($($tt)+)))
|
123 | 132 | };
|
| 133 | + // final output construction |
124 | 134 | [@ {} {$($tt:tt)*}] => { &[$($tt)*] as _ };
|
| 135 | + // starting matcher |
125 | 136 | [$($tt:tt),*] => {
|
126 | 137 | attrs!(@ { $($tt)* } { "allow", "cfg", "cfg_attr", "deny", "forbid", "warn" })
|
127 | 138 | };
|
128 | 139 | }
|
129 | 140 |
|
130 | 141 | #[rustfmt::skip]
|
131 | 142 | static KIND_TO_ATTRIBUTES: Lazy<FxHashMap<SyntaxKind, &[&str]>> = Lazy::new(|| {
|
| 143 | + use SyntaxKind::*; |
132 | 144 | std::array::IntoIter::new([
|
133 | 145 | (
|
134 |
| - SyntaxKind::SOURCE_FILE, |
| 146 | + SOURCE_FILE, |
135 | 147 | attrs!(
|
136 | 148 | item,
|
137 | 149 | "crate_name", "feature", "no_implicit_prelude", "no_main", "no_std",
|
138 | 150 | "recursion_limit", "type_length_limit", "windows_subsystem"
|
139 | 151 | ),
|
140 | 152 | ),
|
141 |
| - (SyntaxKind::MODULE, attrs!(item, "no_implicit_prelude", "path")), |
142 |
| - (SyntaxKind::ITEM_LIST, attrs!(item, "no_implicit_prelude")), |
143 |
| - (SyntaxKind::MACRO_RULES, attrs!(item, "macro_export", "macro_use")), |
144 |
| - (SyntaxKind::MACRO_DEF, attrs!(item)), |
145 |
| - (SyntaxKind::EXTERN_CRATE, attrs!(item, "macro_use", "no_link")), |
146 |
| - (SyntaxKind::USE, attrs!(item)), |
147 |
| - (SyntaxKind::TYPE_ALIAS, attrs!(item)), |
148 |
| - (SyntaxKind::STRUCT, attrs!(item, adt, "non_exhaustive")), |
149 |
| - (SyntaxKind::ENUM, attrs!(item, adt, "non_exhaustive")), |
150 |
| - (SyntaxKind::UNION, attrs!(item, adt)), |
151 |
| - (SyntaxKind::CONST, attrs!(item)), |
| 153 | + (MODULE, attrs!(item, "no_implicit_prelude", "path")), |
| 154 | + (ITEM_LIST, attrs!(item, "no_implicit_prelude")), |
| 155 | + (MACRO_RULES, attrs!(item, "macro_export", "macro_use")), |
| 156 | + (MACRO_DEF, attrs!(item)), |
| 157 | + (EXTERN_CRATE, attrs!(item, "macro_use", "no_link")), |
| 158 | + (USE, attrs!(item)), |
| 159 | + (TYPE_ALIAS, attrs!(item)), |
| 160 | + (STRUCT, attrs!(item, adt, "non_exhaustive")), |
| 161 | + (ENUM, attrs!(item, adt, "non_exhaustive")), |
| 162 | + (UNION, attrs!(item, adt)), |
| 163 | + (CONST, attrs!(item)), |
152 | 164 | (
|
153 |
| - SyntaxKind::FN, |
| 165 | + FN, |
154 | 166 | attrs!(
|
155 | 167 | item, linkable,
|
156 | 168 | "cold", "ignore", "inline", "must_use", "panic_handler", "proc_macro",
|
157 | 169 | "proc_macro_derive", "proc_macro_attribute", "should_panic", "target_feature",
|
158 | 170 | "test", "track_caller"
|
159 | 171 | ),
|
160 | 172 | ),
|
161 |
| - (SyntaxKind::STATIC, attrs!(item, linkable, "global_allocator", "used")), |
162 |
| - (SyntaxKind::TRAIT, attrs!(item, "must_use")), |
163 |
| - (SyntaxKind::IMPL, attrs!(item, "automatically_derived")), |
164 |
| - (SyntaxKind::ASSOC_ITEM_LIST, attrs!(item)), |
165 |
| - (SyntaxKind::EXTERN_BLOCK, attrs!(item, "link")), |
166 |
| - (SyntaxKind::EXTERN_ITEM_LIST, attrs!(item, "link")), |
167 |
| - (SyntaxKind::MACRO_CALL, attrs!()), |
168 |
| - (SyntaxKind::SELF_PARAM, attrs!()), |
169 |
| - (SyntaxKind::PARAM, attrs!()), |
170 |
| - (SyntaxKind::RECORD_FIELD, attrs!()), |
171 |
| - (SyntaxKind::VARIANT, attrs!("non_exhaustive")), |
172 |
| - (SyntaxKind::TYPE_PARAM, attrs!()), |
173 |
| - (SyntaxKind::CONST_PARAM, attrs!()), |
174 |
| - (SyntaxKind::LIFETIME_PARAM, attrs!()), |
175 |
| - (SyntaxKind::LET_STMT, attrs!()), |
176 |
| - (SyntaxKind::EXPR_STMT, attrs!()), |
177 |
| - (SyntaxKind::LITERAL, attrs!()), |
178 |
| - (SyntaxKind::RECORD_EXPR_FIELD_LIST, attrs!()), |
179 |
| - (SyntaxKind::RECORD_EXPR_FIELD, attrs!()), |
180 |
| - (SyntaxKind::MATCH_ARM_LIST, attrs!()), |
181 |
| - (SyntaxKind::MATCH_ARM, attrs!()), |
182 |
| - (SyntaxKind::IDENT_PAT, attrs!()), |
183 |
| - (SyntaxKind::RECORD_PAT_FIELD, attrs!()), |
| 173 | + (STATIC, attrs!(item, linkable, "global_allocator", "used")), |
| 174 | + (TRAIT, attrs!(item, "must_use")), |
| 175 | + (IMPL, attrs!(item, "automatically_derived")), |
| 176 | + (ASSOC_ITEM_LIST, attrs!(item)), |
| 177 | + (EXTERN_BLOCK, attrs!(item, "link")), |
| 178 | + (EXTERN_ITEM_LIST, attrs!(item, "link")), |
| 179 | + (MACRO_CALL, attrs!()), |
| 180 | + (SELF_PARAM, attrs!()), |
| 181 | + (PARAM, attrs!()), |
| 182 | + (RECORD_FIELD, attrs!()), |
| 183 | + (VARIANT, attrs!("non_exhaustive")), |
| 184 | + (TYPE_PARAM, attrs!()), |
| 185 | + (CONST_PARAM, attrs!()), |
| 186 | + (LIFETIME_PARAM, attrs!()), |
| 187 | + (LET_STMT, attrs!()), |
| 188 | + (EXPR_STMT, attrs!()), |
| 189 | + (LITERAL, attrs!()), |
| 190 | + (RECORD_EXPR_FIELD_LIST, attrs!()), |
| 191 | + (RECORD_EXPR_FIELD, attrs!()), |
| 192 | + (MATCH_ARM_LIST, attrs!()), |
| 193 | + (MATCH_ARM, attrs!()), |
| 194 | + (IDENT_PAT, attrs!()), |
| 195 | + (RECORD_PAT_FIELD, attrs!()), |
184 | 196 | ])
|
185 | 197 | .collect()
|
186 | 198 | });
|
@@ -257,62 +269,57 @@ const ATTRIBUTES: &[AttrCompletion] = &[
|
257 | 269 | .prefer_inner(),
|
258 | 270 | ];
|
259 | 271 |
|
260 |
| -#[test] |
261 |
| -fn attributes_are_sorted() { |
262 |
| - let mut attrs = ATTRIBUTES.iter().map(|attr| attr.key()); |
263 |
| - let mut prev = attrs.next().unwrap(); |
264 |
| - |
265 |
| - attrs.for_each(|next| { |
266 |
| - assert!( |
267 |
| - prev < next, |
268 |
| - r#"Attributes are not sorted, "{}" should come after "{}""#, |
269 |
| - prev, |
270 |
| - next |
271 |
| - ); |
272 |
| - prev = next; |
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 |
| - |
| 272 | +fn parse_comma_sep_input(derive_input: ast::TokenTree) -> Option<FxHashSet<String>> { |
| 273 | + let (l_paren, r_paren) = derive_input.l_paren_token().zip(derive_input.r_paren_token())?; |
| 274 | + let mut input_derives = FxHashSet::default(); |
| 275 | + let mut current_derive = String::new(); |
| 276 | + for token in derive_input |
| 277 | + .syntax() |
| 278 | + .children_with_tokens() |
| 279 | + .filter_map(NodeOrToken::into_token) |
| 280 | + .skip_while(|token| token != &l_paren) |
| 281 | + .skip(1) |
| 282 | + .take_while(|token| token != &r_paren) |
| 283 | + { |
| 284 | + if token.kind() == T![,] { |
301 | 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 | 299 | #[cfg(test)]
|
311 | 300 | mod tests {
|
| 301 | + use super::*; |
| 302 | + |
312 | 303 | use expect_test::{expect, Expect};
|
313 | 304 |
|
314 | 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 | 323 | fn check(ra_fixture: &str, expect: Expect) {
|
317 | 324 | let actual = completion_list(ra_fixture, CompletionKind::Attribute);
|
318 | 325 | expect.assert_eq(&actual);
|
|
0 commit comments