]> git.lizzy.rs Git - rust.git/blob - crates/ide_completion/src/completions/attribute.rs
tt muncher time
[rust.git] / crates / ide_completion / src / completions / attribute.rs
1 //! Completion for attributes
2 //!
3 //! This module uses a bit of static metadata to provide completions
4 //! for built-in attributes.
5
6 use once_cell::sync::Lazy;
7 use rustc_hash::{FxHashMap, FxHashSet};
8 use syntax::{ast, AstNode, SyntaxKind, T};
9
10 use crate::{
11     context::CompletionContext,
12     generated_lint_completions::{CLIPPY_LINTS, FEATURES},
13     item::{CompletionItem, CompletionItemKind, CompletionKind},
14     Completions,
15 };
16
17 mod derive;
18 mod lint;
19 pub(crate) use self::lint::LintCompletion;
20
21 pub(crate) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
22     if ctx.mod_declaration_under_caret.is_some() {
23         return None;
24     }
25
26     let attribute = ctx.attribute_under_caret.as_ref()?;
27     match (attribute.path().and_then(|p| p.as_single_name_ref()), attribute.token_tree()) {
28         (Some(path), Some(token_tree)) => match path.text().as_str() {
29             "derive" => derive::complete_derive(acc, ctx, token_tree),
30             "feature" => lint::complete_lint(acc, ctx, token_tree, FEATURES),
31             "allow" | "warn" | "deny" | "forbid" => {
32                 lint::complete_lint(acc, ctx, token_tree.clone(), lint::DEFAULT_LINT_COMPLETIONS);
33                 lint::complete_lint(acc, ctx, token_tree, CLIPPY_LINTS);
34             }
35             _ => (),
36         },
37         (None, Some(_)) => (),
38         _ => complete_new_attribute(acc, ctx, attribute),
39     }
40     Some(())
41 }
42
43 fn complete_new_attribute(acc: &mut Completions, ctx: &CompletionContext, attribute: &ast::Attr) {
44     let attribute_annotated_item_kind = attribute.syntax().parent().map(|it| it.kind());
45     let attributes = attribute_annotated_item_kind.and_then(|kind| {
46         if ast::Expr::can_cast(kind) {
47             Some(EXPR_ATTRIBUTES)
48         } else {
49             KIND_TO_ATTRIBUTES.get(&kind).copied()
50         }
51     });
52     let is_inner = attribute.kind() == ast::AttrKind::Inner;
53
54     let add_completion = |attr_completion: &AttrCompletion| {
55         let mut item = CompletionItem::new(
56             CompletionKind::Attribute,
57             ctx.source_range(),
58             attr_completion.label,
59         );
60         item.kind(CompletionItemKind::Attribute);
61
62         if let Some(lookup) = attr_completion.lookup {
63             item.lookup_by(lookup);
64         }
65
66         if let Some((snippet, cap)) = attr_completion.snippet.zip(ctx.config.snippet_cap) {
67             item.insert_snippet(cap, snippet);
68         }
69
70         if is_inner || !attr_completion.prefer_inner {
71             acc.add(item.build());
72         }
73     };
74
75     match attributes {
76         Some(applicable) => applicable
77             .iter()
78             .flat_map(|name| ATTRIBUTES.binary_search_by(|attr| attr.key().cmp(name)).ok())
79             .flat_map(|idx| ATTRIBUTES.get(idx))
80             .for_each(add_completion),
81         None if is_inner => ATTRIBUTES.iter().for_each(add_completion),
82         None => ATTRIBUTES.iter().filter(|compl| !compl.prefer_inner).for_each(add_completion),
83     }
84 }
85
86 struct AttrCompletion {
87     label: &'static str,
88     lookup: Option<&'static str>,
89     snippet: Option<&'static str>,
90     prefer_inner: bool,
91 }
92
93 impl AttrCompletion {
94     fn key(&self) -> &'static str {
95         self.lookup.unwrap_or(self.label)
96     }
97
98     const fn prefer_inner(self) -> AttrCompletion {
99         AttrCompletion { prefer_inner: true, ..self }
100     }
101 }
102
103 const fn attr(
104     label: &'static str,
105     lookup: Option<&'static str>,
106     snippet: Option<&'static str>,
107 ) -> AttrCompletion {
108     AttrCompletion { label, lookup, snippet, prefer_inner: false }
109 }
110
111 macro_rules! attrs {
112     [@ { item $($tt:tt)* } {$($acc:tt)*}] => {
113         attrs!(@ { $($tt)* } { $($acc)*, "deprecated", "doc", "dochidden", "docalias", "must_use", "no_mangle" })
114     };
115     [@ { adt $($tt:tt)* } {$($acc:tt)*}] => {
116         attrs!(@ { $($tt)* } { $($acc)*, "derive", "repr" })
117     };
118     [@ { linkable $($tt:tt)* } {$($acc:tt)*}] => {
119         attrs!(@ { $($tt)* } { $($acc)*, "export_name", "link_name", "link_section" }) };
120     [@ { $ty:ident $($tt:tt)* } {$($acc:tt)*}] => { compile_error!(concat!("unknown attr subtype ", stringify!($ty)))
121     };
122     [@ { $lit:literal $($tt:tt)*} {$($acc:tt)*}] => {
123         attrs!(@ { $($tt)* } { $($acc)*, $lit })
124     };
125     [@ {$($tt:tt)+} {$($tt2:tt)*}] => {
126         compile_error!(concat!("Unexpected input ", stringify!($($tt)+)))
127     };
128     [@ {} {$($tt:tt)*}] => { &[$($tt)*] as _ };
129     [$($tt:tt),*] => {
130         attrs!(@ { $($tt)* } { "allow", "cfg", "cfg_attr", "deny", "forbid", "warn" })
131     };
132 }
133
134 #[rustfmt::skip]
135 static KIND_TO_ATTRIBUTES: Lazy<FxHashMap<SyntaxKind, &[&str]>> = Lazy::new(|| {
136     std::array::IntoIter::new([
137         (
138             SyntaxKind::SOURCE_FILE,
139             attrs!(
140                 item,
141                 "crate_name", "feature", "no_implicit_prelude", "no_main", "no_std",
142                 "recursion_limit", "type_length_limit", "windows_subsystem"
143             ),
144         ),
145         (SyntaxKind::MODULE, attrs!(item, "no_implicit_prelude", "path")),
146         (SyntaxKind::ITEM_LIST, attrs!(item, "no_implicit_prelude")),
147         (SyntaxKind::MACRO_RULES, attrs!(item, "macro_export", "macro_use")),
148         (SyntaxKind::MACRO_DEF, attrs!(item)),
149         (SyntaxKind::EXTERN_CRATE, attrs!(item, "macro_use", "no_link")),
150         (SyntaxKind::USE, attrs!(item)),
151         (SyntaxKind::TYPE_ALIAS, attrs!(item)),
152         (SyntaxKind::STRUCT, attrs!(item, adt, "non_exhaustive")),
153         (SyntaxKind::ENUM, attrs!(item, adt, "non_exhaustive")),
154         (SyntaxKind::UNION, attrs!(item, adt)),
155         (SyntaxKind::CONST, attrs!(item)),
156         (
157             SyntaxKind::FN,
158             attrs!(
159                 item, linkable,
160                 "cold", "ignore", "inline", "must_use", "panic_handler", "proc_macro",
161                 "proc_macro_derive", "proc_macro_attribute", "should_panic", "target_feature",
162                 "test", "track_caller"
163             ),
164         ),
165         (SyntaxKind::STATIC, attrs!(item, linkable, "global_allocator", "used")),
166         (SyntaxKind::TRAIT, attrs!(item, "must_use")),
167         (SyntaxKind::IMPL, attrs!(item, "automatically_derived")),
168         (SyntaxKind::ASSOC_ITEM_LIST, attrs!(item)),
169         (SyntaxKind::EXTERN_BLOCK, attrs!(item, "link")),
170         (SyntaxKind::EXTERN_ITEM_LIST, attrs!(item, "link")),
171         (SyntaxKind::MACRO_CALL, attrs!()),
172         (SyntaxKind::SELF_PARAM, attrs!()),
173         (SyntaxKind::PARAM, attrs!()),
174         (SyntaxKind::RECORD_FIELD, attrs!()),
175         (SyntaxKind::VARIANT, attrs!("non_exhaustive")),
176         (SyntaxKind::TYPE_PARAM, attrs!()),
177         (SyntaxKind::CONST_PARAM, attrs!()),
178         (SyntaxKind::LIFETIME_PARAM, attrs!()),
179         (SyntaxKind::LET_STMT, attrs!()),
180         (SyntaxKind::EXPR_STMT, attrs!()),
181         (SyntaxKind::LITERAL, attrs!()),
182         (SyntaxKind::RECORD_EXPR_FIELD_LIST, attrs!()),
183         (SyntaxKind::RECORD_EXPR_FIELD, attrs!()),
184         (SyntaxKind::MATCH_ARM_LIST, attrs!()),
185         (SyntaxKind::MATCH_ARM, attrs!()),
186         (SyntaxKind::IDENT_PAT, attrs!()),
187         (SyntaxKind::RECORD_PAT_FIELD, attrs!()),
188     ])
189     .collect()
190 });
191 const EXPR_ATTRIBUTES: &[&str] = attrs!();
192
193 /// https://doc.rust-lang.org/reference/attributes.html#built-in-attributes-index
194 // Keep these sorted for the binary search!
195 const ATTRIBUTES: &[AttrCompletion] = &[
196     attr("allow(…)", Some("allow"), Some("allow(${0:lint})")),
197     attr("automatically_derived", None, None),
198     attr("cfg(…)", Some("cfg"), Some("cfg(${0:predicate})")),
199     attr("cfg_attr(…)", Some("cfg_attr"), Some("cfg_attr(${1:predicate}, ${0:attr})")),
200     attr("cold", None, None),
201     attr(r#"crate_name = """#, Some("crate_name"), Some(r#"crate_name = "${0:crate_name}""#))
202         .prefer_inner(),
203     attr("deny(…)", Some("deny"), Some("deny(${0:lint})")),
204     attr(r#"deprecated"#, Some("deprecated"), Some(r#"deprecated"#)),
205     attr("derive(…)", Some("derive"), Some(r#"derive(${0:Debug})"#)),
206     attr(r#"doc = "…""#, Some("doc"), Some(r#"doc = "${0:docs}""#)),
207     attr(r#"doc(alias = "…")"#, Some("docalias"), Some(r#"doc(alias = "${0:docs}")"#)),
208     attr(r#"doc(hidden)"#, Some("dochidden"), Some(r#"doc(hidden)"#)),
209     attr(
210         r#"export_name = "…""#,
211         Some("export_name"),
212         Some(r#"export_name = "${0:exported_symbol_name}""#),
213     ),
214     attr("feature(…)", Some("feature"), Some("feature(${0:flag})")).prefer_inner(),
215     attr("forbid(…)", Some("forbid"), Some("forbid(${0:lint})")),
216     // FIXME: resolve through macro resolution?
217     attr("global_allocator", None, None).prefer_inner(),
218     attr(r#"ignore = "…""#, Some("ignore"), Some(r#"ignore = "${0:reason}""#)),
219     attr("inline", Some("inline"), Some("inline")),
220     attr("link", None, None),
221     attr(r#"link_name = "…""#, Some("link_name"), Some(r#"link_name = "${0:symbol_name}""#)),
222     attr(
223         r#"link_section = "…""#,
224         Some("link_section"),
225         Some(r#"link_section = "${0:section_name}""#),
226     ),
227     attr("macro_export", None, None),
228     attr("macro_use", None, None),
229     attr(r#"must_use"#, Some("must_use"), Some(r#"must_use"#)),
230     attr("no_implicit_prelude", None, None).prefer_inner(),
231     attr("no_link", None, None).prefer_inner(),
232     attr("no_main", None, None).prefer_inner(),
233     attr("no_mangle", None, None),
234     attr("no_std", None, None).prefer_inner(),
235     attr("non_exhaustive", None, None),
236     attr("panic_handler", None, None).prefer_inner(),
237     attr(r#"path = "…""#, Some("path"), Some(r#"path ="${0:path}""#)),
238     attr("proc_macro", None, None),
239     attr("proc_macro_attribute", None, None),
240     attr("proc_macro_derive(…)", Some("proc_macro_derive"), Some("proc_macro_derive(${0:Trait})")),
241     attr("recursion_limit = …", Some("recursion_limit"), Some("recursion_limit = ${0:128}"))
242         .prefer_inner(),
243     attr("repr(…)", Some("repr"), Some("repr(${0:C})")),
244     attr("should_panic", Some("should_panic"), Some(r#"should_panic"#)),
245     attr(
246         r#"target_feature = "…""#,
247         Some("target_feature"),
248         Some(r#"target_feature = "${0:feature}""#),
249     ),
250     attr("test", None, None),
251     attr("track_caller", None, None),
252     attr("type_length_limit = …", Some("type_length_limit"), Some("type_length_limit = ${0:128}"))
253         .prefer_inner(),
254     attr("used", None, None),
255     attr("warn(…)", Some("warn"), Some("warn(${0:lint})")),
256     attr(
257         r#"windows_subsystem = "…""#,
258         Some("windows_subsystem"),
259         Some(r#"windows_subsystem = "${0:subsystem}""#),
260     )
261     .prefer_inner(),
262 ];
263
264 #[test]
265 fn attributes_are_sorted() {
266     let mut attrs = ATTRIBUTES.iter().map(|attr| attr.key());
267     let mut prev = attrs.next().unwrap();
268
269     attrs.for_each(|next| {
270         assert!(
271             prev < next,
272             r#"Attributes are not sorted, "{}" should come after "{}""#,
273             prev,
274             next
275         );
276         prev = next;
277     });
278 }
279
280 fn parse_comma_sep_input(derive_input: ast::TokenTree) -> Result<FxHashSet<String>, ()> {
281     match (derive_input.left_delimiter_token(), derive_input.right_delimiter_token()) {
282         (Some(left_paren), Some(right_paren))
283             if left_paren.kind() == T!['('] && right_paren.kind() == T![')'] =>
284         {
285             let mut input_derives = FxHashSet::default();
286             let mut current_derive = String::new();
287             for token in derive_input
288                 .syntax()
289                 .children_with_tokens()
290                 .filter_map(|token| token.into_token())
291                 .skip_while(|token| token != &left_paren)
292                 .skip(1)
293                 .take_while(|token| token != &right_paren)
294             {
295                 if T![,] == token.kind() {
296                     if !current_derive.is_empty() {
297                         input_derives.insert(current_derive);
298                         current_derive = String::new();
299                     }
300                 } else {
301                     current_derive.push_str(token.text().trim());
302                 }
303             }
304
305             if !current_derive.is_empty() {
306                 input_derives.insert(current_derive);
307             }
308             Ok(input_derives)
309         }
310         _ => Err(()),
311     }
312 }
313
314 #[cfg(test)]
315 mod tests {
316     use expect_test::{expect, Expect};
317
318     use crate::{test_utils::completion_list, CompletionKind};
319
320     fn check(ra_fixture: &str, expect: Expect) {
321         let actual = completion_list(ra_fixture, CompletionKind::Attribute);
322         expect.assert_eq(&actual);
323     }
324
325     #[test]
326     fn complete_attribute_on_struct() {
327         check(
328             r#"
329 #[$0]
330 struct Test {}
331         "#,
332             expect![[r#"
333                 at allow(…)
334                 at cfg(…)
335                 at cfg_attr(…)
336                 at deny(…)
337                 at forbid(…)
338                 at warn(…)
339                 at deprecated
340                 at must_use
341             "#]],
342         );
343     }
344     #[test]
345     fn test_attribute_completion_inside_nested_attr() {
346         check(r#"#[cfg($0)]"#, expect![[]])
347     }
348 }