]> git.lizzy.rs Git - rust.git/blob - crates/ide_completion/src/completions/attribute.rs
Add another attribute completion test
[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     let attribute = ctx.attribute_under_caret.as_ref()?;
23     match (attribute.path().and_then(|p| p.as_single_name_ref()), attribute.token_tree()) {
24         (Some(path), Some(token_tree)) => match path.text().as_str() {
25             "derive" => derive::complete_derive(acc, ctx, token_tree),
26             "feature" => lint::complete_lint(acc, ctx, token_tree, FEATURES),
27             "allow" | "warn" | "deny" | "forbid" => {
28                 lint::complete_lint(acc, ctx, token_tree.clone(), lint::DEFAULT_LINT_COMPLETIONS);
29                 lint::complete_lint(acc, ctx, token_tree, CLIPPY_LINTS);
30             }
31             _ => (),
32         },
33         (None, Some(_)) => (),
34         _ => complete_new_attribute(acc, ctx, attribute),
35     }
36     Some(())
37 }
38
39 fn complete_new_attribute(acc: &mut Completions, ctx: &CompletionContext, attribute: &ast::Attr) {
40     let attribute_annotated_item_kind = attribute.syntax().parent().map(|it| it.kind());
41     let attributes = attribute_annotated_item_kind.and_then(|kind| {
42         if ast::Expr::can_cast(kind) {
43             Some(EXPR_ATTRIBUTES)
44         } else {
45             KIND_TO_ATTRIBUTES.get(&kind).copied()
46         }
47     });
48     let is_inner = attribute.kind() == ast::AttrKind::Inner;
49
50     let add_completion = |attr_completion: &AttrCompletion| {
51         let mut item = CompletionItem::new(
52             CompletionKind::Attribute,
53             ctx.source_range(),
54             attr_completion.label,
55         );
56         item.kind(CompletionItemKind::Attribute);
57
58         if let Some(lookup) = attr_completion.lookup {
59             item.lookup_by(lookup);
60         }
61
62         if let Some((snippet, cap)) = attr_completion.snippet.zip(ctx.config.snippet_cap) {
63             item.insert_snippet(cap, snippet);
64         }
65
66         if is_inner || !attr_completion.prefer_inner {
67             acc.add(item.build());
68         }
69     };
70
71     match attributes {
72         Some(applicable) => applicable
73             .iter()
74             .flat_map(|name| ATTRIBUTES.binary_search_by(|attr| attr.key().cmp(name)).ok())
75             .flat_map(|idx| ATTRIBUTES.get(idx))
76             .for_each(add_completion),
77         None if is_inner => ATTRIBUTES.iter().for_each(add_completion),
78         None => ATTRIBUTES.iter().filter(|compl| !compl.prefer_inner).for_each(add_completion),
79     }
80 }
81
82 struct AttrCompletion {
83     label: &'static str,
84     lookup: Option<&'static str>,
85     snippet: Option<&'static str>,
86     prefer_inner: bool,
87 }
88
89 impl AttrCompletion {
90     fn key(&self) -> &'static str {
91         self.lookup.unwrap_or(self.label)
92     }
93
94     const fn prefer_inner(self) -> AttrCompletion {
95         AttrCompletion { prefer_inner: true, ..self }
96     }
97 }
98
99 const fn attr(
100     label: &'static str,
101     lookup: Option<&'static str>,
102     snippet: Option<&'static str>,
103 ) -> AttrCompletion {
104     AttrCompletion { label, lookup, snippet, prefer_inner: false }
105 }
106
107 macro_rules! attrs {
108     [@ { item $($tt:tt)* } {$($acc:tt)*}] => {
109         attrs!(@ { $($tt)* } { $($acc)*, "deprecated", "doc", "dochidden", "docalias", "must_use", "no_mangle" })
110     };
111     [@ { adt $($tt:tt)* } {$($acc:tt)*}] => {
112         attrs!(@ { $($tt)* } { $($acc)*, "derive", "repr" })
113     };
114     [@ { 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)))
117     };
118     [@ { $lit:literal $($tt:tt)*} {$($acc:tt)*}] => {
119         attrs!(@ { $($tt)* } { $($acc)*, $lit })
120     };
121     [@ {$($tt:tt)+} {$($tt2:tt)*}] => {
122         compile_error!(concat!("Unexpected input ", stringify!($($tt)+)))
123     };
124     [@ {} {$($tt:tt)*}] => { &[$($tt)*] as _ };
125     [$($tt:tt),*] => {
126         attrs!(@ { $($tt)* } { "allow", "cfg", "cfg_attr", "deny", "forbid", "warn" })
127     };
128 }
129
130 #[rustfmt::skip]
131 static KIND_TO_ATTRIBUTES: Lazy<FxHashMap<SyntaxKind, &[&str]>> = Lazy::new(|| {
132     std::array::IntoIter::new([
133         (
134             SyntaxKind::SOURCE_FILE,
135             attrs!(
136                 item,
137                 "crate_name", "feature", "no_implicit_prelude", "no_main", "no_std",
138                 "recursion_limit", "type_length_limit", "windows_subsystem"
139             ),
140         ),
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)),
152         (
153             SyntaxKind::FN,
154             attrs!(
155                 item, linkable,
156                 "cold", "ignore", "inline", "must_use", "panic_handler", "proc_macro",
157                 "proc_macro_derive", "proc_macro_attribute", "should_panic", "target_feature",
158                 "test", "track_caller"
159             ),
160         ),
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!()),
184     ])
185     .collect()
186 });
187 const EXPR_ATTRIBUTES: &[&str] = attrs!();
188
189 /// https://doc.rust-lang.org/reference/attributes.html#built-in-attributes-index
190 // Keep these sorted for the binary search!
191 const ATTRIBUTES: &[AttrCompletion] = &[
192     attr("allow(…)", Some("allow"), Some("allow(${0:lint})")),
193     attr("automatically_derived", None, None),
194     attr("cfg(…)", Some("cfg"), Some("cfg(${0:predicate})")),
195     attr("cfg_attr(…)", Some("cfg_attr"), Some("cfg_attr(${1:predicate}, ${0:attr})")),
196     attr("cold", None, None),
197     attr(r#"crate_name = """#, Some("crate_name"), Some(r#"crate_name = "${0:crate_name}""#))
198         .prefer_inner(),
199     attr("deny(…)", Some("deny"), Some("deny(${0:lint})")),
200     attr(r#"deprecated"#, Some("deprecated"), Some(r#"deprecated"#)),
201     attr("derive(…)", Some("derive"), Some(r#"derive(${0:Debug})"#)),
202     attr(r#"doc = "…""#, Some("doc"), Some(r#"doc = "${0:docs}""#)),
203     attr(r#"doc(alias = "…")"#, Some("docalias"), Some(r#"doc(alias = "${0:docs}")"#)),
204     attr(r#"doc(hidden)"#, Some("dochidden"), Some(r#"doc(hidden)"#)),
205     attr(
206         r#"export_name = "…""#,
207         Some("export_name"),
208         Some(r#"export_name = "${0:exported_symbol_name}""#),
209     ),
210     attr("feature(…)", Some("feature"), Some("feature(${0:flag})")).prefer_inner(),
211     attr("forbid(…)", Some("forbid"), Some("forbid(${0:lint})")),
212     // FIXME: resolve through macro resolution?
213     attr("global_allocator", None, None).prefer_inner(),
214     attr(r#"ignore = "…""#, Some("ignore"), Some(r#"ignore = "${0:reason}""#)),
215     attr("inline", Some("inline"), Some("inline")),
216     attr("link", None, None),
217     attr(r#"link_name = "…""#, Some("link_name"), Some(r#"link_name = "${0:symbol_name}""#)),
218     attr(
219         r#"link_section = "…""#,
220         Some("link_section"),
221         Some(r#"link_section = "${0:section_name}""#),
222     ),
223     attr("macro_export", None, None),
224     attr("macro_use", None, None),
225     attr(r#"must_use"#, Some("must_use"), Some(r#"must_use"#)),
226     attr("no_implicit_prelude", None, None).prefer_inner(),
227     attr("no_link", None, None).prefer_inner(),
228     attr("no_main", None, None).prefer_inner(),
229     attr("no_mangle", None, None),
230     attr("no_std", None, None).prefer_inner(),
231     attr("non_exhaustive", None, None),
232     attr("panic_handler", None, None).prefer_inner(),
233     attr(r#"path = "…""#, Some("path"), Some(r#"path ="${0:path}""#)),
234     attr("proc_macro", None, None),
235     attr("proc_macro_attribute", None, None),
236     attr("proc_macro_derive(…)", Some("proc_macro_derive"), Some("proc_macro_derive(${0:Trait})")),
237     attr("recursion_limit = …", Some("recursion_limit"), Some("recursion_limit = ${0:128}"))
238         .prefer_inner(),
239     attr("repr(…)", Some("repr"), Some("repr(${0:C})")),
240     attr("should_panic", Some("should_panic"), Some(r#"should_panic"#)),
241     attr(
242         r#"target_feature = "…""#,
243         Some("target_feature"),
244         Some(r#"target_feature = "${0:feature}""#),
245     ),
246     attr("test", None, None),
247     attr("track_caller", None, None),
248     attr("type_length_limit = …", Some("type_length_limit"), Some("type_length_limit = ${0:128}"))
249         .prefer_inner(),
250     attr("used", None, None),
251     attr("warn(…)", Some("warn"), Some("warn(${0:lint})")),
252     attr(
253         r#"windows_subsystem = "…""#,
254         Some("windows_subsystem"),
255         Some(r#"windows_subsystem = "${0:subsystem}""#),
256     )
257     .prefer_inner(),
258 ];
259
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
301             if !current_derive.is_empty() {
302                 input_derives.insert(current_derive);
303             }
304             Ok(input_derives)
305         }
306         _ => Err(()),
307     }
308 }
309
310 #[cfg(test)]
311 mod tests {
312     use expect_test::{expect, Expect};
313
314     use crate::{test_utils::completion_list, CompletionKind};
315
316     fn check(ra_fixture: &str, expect: Expect) {
317         let actual = completion_list(ra_fixture, CompletionKind::Attribute);
318         expect.assert_eq(&actual);
319     }
320
321     #[test]
322     fn test_attribute_completion_inside_nested_attr() {
323         check(r#"#[cfg($0)]"#, expect![[]])
324     }
325
326     #[test]
327     fn test_attribute_completion_with_existing_attr() {
328         check(
329             r#"#[no_mangle] #[$0] mcall!();"#,
330             expect![[r#"
331                 at allow(…)
332                 at cfg(…)
333                 at cfg_attr(…)
334                 at deny(…)
335                 at forbid(…)
336                 at warn(…)
337             "#]],
338         )
339     }
340
341     #[test]
342     fn complete_attribute_on_source_file() {
343         check(
344             r#"#![$0]"#,
345             expect![[r#"
346                 at allow(…)
347                 at cfg(…)
348                 at cfg_attr(…)
349                 at deny(…)
350                 at forbid(…)
351                 at warn(…)
352                 at deprecated
353                 at doc = "…"
354                 at doc(hidden)
355                 at doc(alias = "…")
356                 at must_use
357                 at no_mangle
358                 at crate_name = ""
359                 at feature(…)
360                 at no_implicit_prelude
361                 at no_main
362                 at no_std
363                 at recursion_limit = …
364                 at type_length_limit = …
365                 at windows_subsystem = "…"
366             "#]],
367         );
368     }
369
370     #[test]
371     fn complete_attribute_on_module() {
372         check(
373             r#"#[$0] mod foo;"#,
374             expect![[r#"
375             at allow(…)
376             at cfg(…)
377             at cfg_attr(…)
378             at deny(…)
379             at forbid(…)
380             at warn(…)
381             at deprecated
382             at doc = "…"
383             at doc(hidden)
384             at doc(alias = "…")
385             at must_use
386             at no_mangle
387             at path = "…"
388         "#]],
389         );
390         check(
391             r#"mod foo {#![$0]}"#,
392             expect![[r#"
393                 at allow(…)
394                 at cfg(…)
395                 at cfg_attr(…)
396                 at deny(…)
397                 at forbid(…)
398                 at warn(…)
399                 at deprecated
400                 at doc = "…"
401                 at doc(hidden)
402                 at doc(alias = "…")
403                 at must_use
404                 at no_mangle
405                 at no_implicit_prelude
406             "#]],
407         );
408     }
409
410     #[test]
411     fn complete_attribute_on_macro_rules() {
412         check(
413             r#"#[$0] macro_rules! foo {}"#,
414             expect![[r#"
415                 at allow(…)
416                 at cfg(…)
417                 at cfg_attr(…)
418                 at deny(…)
419                 at forbid(…)
420                 at warn(…)
421                 at deprecated
422                 at doc = "…"
423                 at doc(hidden)
424                 at doc(alias = "…")
425                 at must_use
426                 at no_mangle
427                 at macro_export
428                 at macro_use
429             "#]],
430         );
431     }
432
433     #[test]
434     fn complete_attribute_on_macro_def() {
435         check(
436             r#"#[$0] macro foo {}"#,
437             expect![[r#"
438                 at allow(…)
439                 at cfg(…)
440                 at cfg_attr(…)
441                 at deny(…)
442                 at forbid(…)
443                 at warn(…)
444                 at deprecated
445                 at doc = "…"
446                 at doc(hidden)
447                 at doc(alias = "…")
448                 at must_use
449                 at no_mangle
450             "#]],
451         );
452     }
453
454     #[test]
455     fn complete_attribute_on_extern_crate() {
456         check(
457             r#"#[$0] extern crate foo;"#,
458             expect![[r#"
459                 at allow(…)
460                 at cfg(…)
461                 at cfg_attr(…)
462                 at deny(…)
463                 at forbid(…)
464                 at warn(…)
465                 at deprecated
466                 at doc = "…"
467                 at doc(hidden)
468                 at doc(alias = "…")
469                 at must_use
470                 at no_mangle
471                 at macro_use
472             "#]],
473         );
474     }
475
476     #[test]
477     fn complete_attribute_on_use() {
478         check(
479             r#"#[$0] use foo;"#,
480             expect![[r#"
481                 at allow(…)
482                 at cfg(…)
483                 at cfg_attr(…)
484                 at deny(…)
485                 at forbid(…)
486                 at warn(…)
487                 at deprecated
488                 at doc = "…"
489                 at doc(hidden)
490                 at doc(alias = "…")
491                 at must_use
492                 at no_mangle
493             "#]],
494         );
495     }
496
497     #[test]
498     fn complete_attribute_on_type_alias() {
499         check(
500             r#"#[$0] type foo = ();"#,
501             expect![[r#"
502                 at allow(…)
503                 at cfg(…)
504                 at cfg_attr(…)
505                 at deny(…)
506                 at forbid(…)
507                 at warn(…)
508                 at deprecated
509                 at doc = "…"
510                 at doc(hidden)
511                 at doc(alias = "…")
512                 at must_use
513                 at no_mangle
514             "#]],
515         );
516     }
517
518     #[test]
519     fn complete_attribute_on_struct() {
520         check(
521             r#"#[$0] struct Foo;"#,
522             expect![[r#"
523                 at allow(…)
524                 at cfg(…)
525                 at cfg_attr(…)
526                 at deny(…)
527                 at forbid(…)
528                 at warn(…)
529                 at deprecated
530                 at doc = "…"
531                 at doc(hidden)
532                 at doc(alias = "…")
533                 at must_use
534                 at no_mangle
535                 at derive(…)
536                 at repr(…)
537                 at non_exhaustive
538             "#]],
539         );
540     }
541
542     #[test]
543     fn complete_attribute_on_enum() {
544         check(
545             r#"#[$0] enum Foo {}"#,
546             expect![[r#"
547                 at allow(…)
548                 at cfg(…)
549                 at cfg_attr(…)
550                 at deny(…)
551                 at forbid(…)
552                 at warn(…)
553                 at deprecated
554                 at doc = "…"
555                 at doc(hidden)
556                 at doc(alias = "…")
557                 at must_use
558                 at no_mangle
559                 at derive(…)
560                 at repr(…)
561                 at non_exhaustive
562             "#]],
563         );
564     }
565
566     #[test]
567     fn complete_attribute_on_const() {
568         check(
569             r#"#[$0] const FOO: () = ();"#,
570             expect![[r#"
571                 at allow(…)
572                 at cfg(…)
573                 at cfg_attr(…)
574                 at deny(…)
575                 at forbid(…)
576                 at warn(…)
577                 at deprecated
578                 at doc = "…"
579                 at doc(hidden)
580                 at doc(alias = "…")
581                 at must_use
582                 at no_mangle
583             "#]],
584         );
585     }
586
587     #[test]
588     fn complete_attribute_on_static() {
589         check(
590             r#"#[$0] static FOO: () = ()"#,
591             expect![[r#"
592                 at allow(…)
593                 at cfg(…)
594                 at cfg_attr(…)
595                 at deny(…)
596                 at forbid(…)
597                 at warn(…)
598                 at deprecated
599                 at doc = "…"
600                 at doc(hidden)
601                 at doc(alias = "…")
602                 at must_use
603                 at no_mangle
604                 at export_name = "…"
605                 at link_name = "…"
606                 at link_section = "…"
607                 at used
608             "#]],
609         );
610     }
611
612     #[test]
613     fn complete_attribute_on_trait() {
614         check(
615             r#"#[$0] trait Foo {}"#,
616             expect![[r#"
617                 at allow(…)
618                 at cfg(…)
619                 at cfg_attr(…)
620                 at deny(…)
621                 at forbid(…)
622                 at warn(…)
623                 at deprecated
624                 at doc = "…"
625                 at doc(hidden)
626                 at doc(alias = "…")
627                 at must_use
628                 at no_mangle
629                 at must_use
630             "#]],
631         );
632     }
633
634     #[test]
635     fn complete_attribute_on_impl() {
636         check(
637             r#"#[$0] impl () {}"#,
638             expect![[r#"
639                 at allow(…)
640                 at cfg(…)
641                 at cfg_attr(…)
642                 at deny(…)
643                 at forbid(…)
644                 at warn(…)
645                 at deprecated
646                 at doc = "…"
647                 at doc(hidden)
648                 at doc(alias = "…")
649                 at must_use
650                 at no_mangle
651                 at automatically_derived
652             "#]],
653         );
654         check(
655             r#"impl () {#![$0]}"#,
656             expect![[r#"
657                 at allow(…)
658                 at cfg(…)
659                 at cfg_attr(…)
660                 at deny(…)
661                 at forbid(…)
662                 at warn(…)
663                 at deprecated
664                 at doc = "…"
665                 at doc(hidden)
666                 at doc(alias = "…")
667                 at must_use
668                 at no_mangle
669             "#]],
670         );
671     }
672
673     #[test]
674     fn complete_attribute_on_extern_block() {
675         check(
676             r#"#[$0] extern {}"#,
677             expect![[r#"
678                 at allow(…)
679                 at cfg(…)
680                 at cfg_attr(…)
681                 at deny(…)
682                 at forbid(…)
683                 at warn(…)
684                 at deprecated
685                 at doc = "…"
686                 at doc(hidden)
687                 at doc(alias = "…")
688                 at must_use
689                 at no_mangle
690                 at link
691             "#]],
692         );
693         check(
694             r#"extern {#![$0]}"#,
695             expect![[r#"
696                 at allow(…)
697                 at cfg(…)
698                 at cfg_attr(…)
699                 at deny(…)
700                 at forbid(…)
701                 at warn(…)
702                 at deprecated
703                 at doc = "…"
704                 at doc(hidden)
705                 at doc(alias = "…")
706                 at must_use
707                 at no_mangle
708                 at link
709             "#]],
710         );
711     }
712
713     #[test]
714     fn complete_attribute_on_variant() {
715         check(
716             r#"enum Foo { #[$0] Bar }"#,
717             expect![[r#"
718                 at allow(…)
719                 at cfg(…)
720                 at cfg_attr(…)
721                 at deny(…)
722                 at forbid(…)
723                 at warn(…)
724                 at non_exhaustive
725             "#]],
726         );
727     }
728
729     #[test]
730     fn complete_attribute_on_expr() {
731         check(
732             r#"fn main() { #[$0] foo() }"#,
733             expect![[r#"
734                 at allow(…)
735                 at cfg(…)
736                 at cfg_attr(…)
737                 at deny(…)
738                 at forbid(…)
739                 at warn(…)
740             "#]],
741         );
742         check(
743             r#"fn main() { #[$0] foo(); }"#,
744             expect![[r#"
745                 at allow(…)
746                 at cfg(…)
747                 at cfg_attr(…)
748                 at deny(…)
749                 at forbid(…)
750                 at warn(…)
751             "#]],
752         );
753     }
754 }