Skip to content

Commit 6acff6c

Browse files
authored
Merge pull request #19908 from rmehri01/rmehri01/diagnostic_attribute_completions
feat: implement attribute completions for diagnostics module
2 parents 4fd1cdb + b95101c commit 6acff6c

File tree

3 files changed

+190
-25
lines changed

3 files changed

+190
-25
lines changed

crates/ide-completion/src/completions/attribute.rs

Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use crate::{
2525

2626
mod cfg;
2727
mod derive;
28+
mod diagnostic;
2829
mod lint;
2930
mod macro_use;
3031
mod repr;
@@ -40,23 +41,22 @@ pub(crate) fn complete_known_attribute_input(
4041
extern_crate: Option<&ast::ExternCrate>,
4142
) -> Option<()> {
4243
let attribute = fake_attribute_under_caret;
43-
let name_ref = match attribute.path() {
44-
Some(p) => Some(p.as_single_name_ref()?),
45-
None => None,
46-
};
47-
let (path, tt) = name_ref.zip(attribute.token_tree())?;
48-
tt.l_paren_token()?;
44+
let path = attribute.path()?;
45+
let segments = path.segments().map(|s| s.name_ref()).collect::<Option<Vec<_>>>()?;
46+
let segments = segments.iter().map(|n| n.text()).collect::<Vec<_>>();
47+
let segments = segments.iter().map(|t| t.as_str()).collect::<Vec<_>>();
48+
let tt = attribute.token_tree()?;
4949

50-
match path.text().as_str() {
51-
"repr" => repr::complete_repr(acc, ctx, tt),
52-
"feature" => lint::complete_lint(
50+
match segments.as_slice() {
51+
["repr"] => repr::complete_repr(acc, ctx, tt),
52+
["feature"] => lint::complete_lint(
5353
acc,
5454
ctx,
5555
colon_prefix,
5656
&parse_tt_as_comma_sep_paths(tt, ctx.edition)?,
5757
FEATURES,
5858
),
59-
"allow" | "expect" | "deny" | "forbid" | "warn" => {
59+
["allow"] | ["expect"] | ["deny"] | ["forbid"] | ["warn"] => {
6060
let existing_lints = parse_tt_as_comma_sep_paths(tt, ctx.edition)?;
6161

6262
let lints: Vec<Lint> = CLIPPY_LINT_GROUPS
@@ -70,13 +70,14 @@ pub(crate) fn complete_known_attribute_input(
7070

7171
lint::complete_lint(acc, ctx, colon_prefix, &existing_lints, &lints);
7272
}
73-
"cfg" => cfg::complete_cfg(acc, ctx),
74-
"macro_use" => macro_use::complete_macro_use(
73+
["cfg"] => cfg::complete_cfg(acc, ctx),
74+
["macro_use"] => macro_use::complete_macro_use(
7575
acc,
7676
ctx,
7777
extern_crate,
7878
&parse_tt_as_comma_sep_paths(tt, ctx.edition)?,
7979
),
80+
["diagnostic", "on_unimplemented"] => diagnostic::complete_on_unimplemented(acc, ctx, tt),
8081
_ => (),
8182
}
8283
Some(())
@@ -139,6 +140,8 @@ pub(crate) fn complete_attribute_path(
139140
}
140141
Qualified::TypeAnchor { .. } | Qualified::With { .. } => {}
141142
}
143+
let qualifier_path =
144+
if let Qualified::With { path, .. } = qualified { Some(path) } else { None };
142145

143146
let attributes = annotated_item_kind.and_then(|kind| {
144147
if ast::Expr::can_cast(kind) {
@@ -149,18 +152,33 @@ pub(crate) fn complete_attribute_path(
149152
});
150153

151154
let add_completion = |attr_completion: &AttrCompletion| {
152-
let mut item = CompletionItem::new(
153-
SymbolKind::Attribute,
154-
ctx.source_range(),
155-
attr_completion.label,
156-
ctx.edition,
157-
);
155+
// if we don't already have the qualifiers of the completion, then
156+
// add the missing parts to the label and snippet
157+
let mut label = attr_completion.label.to_owned();
158+
let mut snippet = attr_completion.snippet.map(|s| s.to_owned());
159+
let segments = qualifier_path.iter().flat_map(|q| q.segments()).collect::<Vec<_>>();
160+
let qualifiers = attr_completion.qualifiers;
161+
let matching_qualifiers = segments
162+
.iter()
163+
.zip(qualifiers)
164+
.take_while(|(s, q)| s.name_ref().is_some_and(|t| t.text() == **q))
165+
.count();
166+
if matching_qualifiers != qualifiers.len() {
167+
let prefix = qualifiers[matching_qualifiers..].join("::");
168+
label = format!("{prefix}::{label}");
169+
if let Some(s) = snippet.as_mut() {
170+
*s = format!("{prefix}::{s}");
171+
}
172+
}
173+
174+
let mut item =
175+
CompletionItem::new(SymbolKind::Attribute, ctx.source_range(), label, ctx.edition);
158176

159177
if let Some(lookup) = attr_completion.lookup {
160178
item.lookup_by(lookup);
161179
}
162180

163-
if let Some((snippet, cap)) = attr_completion.snippet.zip(ctx.config.snippet_cap) {
181+
if let Some((snippet, cap)) = snippet.zip(ctx.config.snippet_cap) {
164182
item.insert_snippet(cap, snippet);
165183
}
166184

@@ -184,6 +202,7 @@ struct AttrCompletion {
184202
label: &'static str,
185203
lookup: Option<&'static str>,
186204
snippet: Option<&'static str>,
205+
qualifiers: &'static [&'static str],
187206
prefer_inner: bool,
188207
}
189208

@@ -192,6 +211,10 @@ impl AttrCompletion {
192211
self.lookup.unwrap_or(self.label)
193212
}
194213

214+
const fn qualifiers(self, qualifiers: &'static [&'static str]) -> AttrCompletion {
215+
AttrCompletion { qualifiers, ..self }
216+
}
217+
195218
const fn prefer_inner(self) -> AttrCompletion {
196219
AttrCompletion { prefer_inner: true, ..self }
197220
}
@@ -202,7 +225,7 @@ const fn attr(
202225
lookup: Option<&'static str>,
203226
snippet: Option<&'static str>,
204227
) -> AttrCompletion {
205-
AttrCompletion { label, lookup, snippet, prefer_inner: false }
228+
AttrCompletion { label, lookup, snippet, qualifiers: &[], prefer_inner: false }
206229
}
207230

208231
macro_rules! attrs {
@@ -264,14 +287,14 @@ static KIND_TO_ATTRIBUTES: LazyLock<FxHashMap<SyntaxKind, &[&str]>> = LazyLock::
264287
FN,
265288
attrs!(
266289
item, linkable,
267-
"cold", "ignore", "inline", "must_use", "panic_handler", "proc_macro",
290+
"cold", "ignore", "inline", "panic_handler", "proc_macro",
268291
"proc_macro_derive", "proc_macro_attribute", "should_panic", "target_feature",
269292
"test", "track_caller"
270293
),
271294
),
272295
(STATIC, attrs!(item, linkable, "global_allocator", "used")),
273-
(TRAIT, attrs!(item, "must_use")),
274-
(IMPL, attrs!(item, "automatically_derived")),
296+
(TRAIT, attrs!(item, "diagnostic::on_unimplemented")),
297+
(IMPL, attrs!(item, "automatically_derived", "diagnostic::do_not_recommend")),
275298
(ASSOC_ITEM_LIST, attrs!(item)),
276299
(EXTERN_BLOCK, attrs!(item, "link")),
277300
(EXTERN_ITEM_LIST, attrs!(item, "link")),
@@ -311,6 +334,14 @@ const ATTRIBUTES: &[AttrCompletion] = &[
311334
attr("deny(…)", Some("deny"), Some("deny(${0:lint})")),
312335
attr(r#"deprecated"#, Some("deprecated"), Some(r#"deprecated"#)),
313336
attr("derive(…)", Some("derive"), Some(r#"derive(${0:Debug})"#)),
337+
attr("do_not_recommend", Some("diagnostic::do_not_recommend"), None)
338+
.qualifiers(&["diagnostic"]),
339+
attr(
340+
"on_unimplemented",
341+
Some("diagnostic::on_unimplemented"),
342+
Some(r#"on_unimplemented(${0:keys})"#),
343+
)
344+
.qualifiers(&["diagnostic"]),
314345
attr(r#"doc = "…""#, Some("doc"), Some(r#"doc = "${0:docs}""#)),
315346
attr(r#"doc(alias = "…")"#, Some("docalias"), Some(r#"doc(alias = "${0:docs}")"#)),
316347
attr(r#"doc(hidden)"#, Some("dochidden"), Some(r#"doc(hidden)"#)),
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//! Completion for diagnostic attributes.
2+
3+
use ide_db::SymbolKind;
4+
use syntax::ast;
5+
6+
use crate::{CompletionItem, Completions, context::CompletionContext};
7+
8+
use super::AttrCompletion;
9+
10+
pub(super) fn complete_on_unimplemented(
11+
acc: &mut Completions,
12+
ctx: &CompletionContext<'_>,
13+
input: ast::TokenTree,
14+
) {
15+
if let Some(existing_keys) = super::parse_comma_sep_expr(input) {
16+
for attr in ATTRIBUTE_ARGS {
17+
let already_annotated = existing_keys
18+
.iter()
19+
.filter_map(|expr| match expr {
20+
ast::Expr::PathExpr(path) => path.path()?.as_single_name_ref(),
21+
ast::Expr::BinExpr(bin)
22+
if bin.op_kind() == Some(ast::BinaryOp::Assignment { op: None }) =>
23+
{
24+
match bin.lhs()? {
25+
ast::Expr::PathExpr(path) => path.path()?.as_single_name_ref(),
26+
_ => None,
27+
}
28+
}
29+
_ => None,
30+
})
31+
.any(|it| {
32+
let text = it.text();
33+
attr.key() == text && text != "note"
34+
});
35+
if already_annotated {
36+
continue;
37+
}
38+
39+
let mut item = CompletionItem::new(
40+
SymbolKind::BuiltinAttr,
41+
ctx.source_range(),
42+
attr.label,
43+
ctx.edition,
44+
);
45+
if let Some(lookup) = attr.lookup {
46+
item.lookup_by(lookup);
47+
}
48+
if let Some((snippet, cap)) = attr.snippet.zip(ctx.config.snippet_cap) {
49+
item.insert_snippet(cap, snippet);
50+
}
51+
item.add_to(acc, ctx.db);
52+
}
53+
}
54+
}
55+
56+
const ATTRIBUTE_ARGS: &[AttrCompletion] = &[
57+
super::attr(r#"label = "…""#, Some("label"), Some(r#"label = "${0:label}""#)),
58+
super::attr(r#"message = "…""#, Some("message"), Some(r#"message = "${0:message}""#)),
59+
super::attr(r#"note = "…""#, Some("note"), Some(r#"note = "${0:note}""#)),
60+
];

crates/ide-completion/src/tests/attribute.rs

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ pub struct Foo(#[m$0] i32);
3030
at deprecated
3131
at derive macro derive
3232
at derive(…)
33+
at diagnostic::do_not_recommend
34+
at diagnostic::on_unimplemented
3335
at doc = "…"
3436
at doc(alias = "…")
3537
at doc(hidden)
@@ -472,13 +474,13 @@ fn attr_on_trait() {
472474
at cfg_attr(…)
473475
at deny(…)
474476
at deprecated
477+
at diagnostic::on_unimplemented
475478
at doc = "…"
476479
at doc(alias = "…")
477480
at doc(hidden)
478481
at expect(…)
479482
at forbid(…)
480483
at must_use
481-
at must_use
482484
at no_mangle
483485
at warn(…)
484486
kw crate::
@@ -498,6 +500,7 @@ fn attr_on_impl() {
498500
at cfg_attr(…)
499501
at deny(…)
500502
at deprecated
503+
at diagnostic::do_not_recommend
501504
at doc = "…"
502505
at doc(alias = "…")
503506
at doc(hidden)
@@ -532,6 +535,76 @@ fn attr_on_impl() {
532535
);
533536
}
534537

538+
#[test]
539+
fn attr_with_qualifier() {
540+
check(
541+
r#"#[diagnostic::$0] impl () {}"#,
542+
expect![[r#"
543+
at allow(…)
544+
at automatically_derived
545+
at cfg(…)
546+
at cfg_attr(…)
547+
at deny(…)
548+
at deprecated
549+
at do_not_recommend
550+
at doc = "…"
551+
at doc(alias = "…")
552+
at doc(hidden)
553+
at expect(…)
554+
at forbid(…)
555+
at must_use
556+
at no_mangle
557+
at warn(…)
558+
"#]],
559+
);
560+
check(
561+
r#"#[diagnostic::$0] trait Foo {}"#,
562+
expect![[r#"
563+
at allow(…)
564+
at cfg(…)
565+
at cfg_attr(…)
566+
at deny(…)
567+
at deprecated
568+
at doc = "…"
569+
at doc(alias = "…")
570+
at doc(hidden)
571+
at expect(…)
572+
at forbid(…)
573+
at must_use
574+
at no_mangle
575+
at on_unimplemented
576+
at warn(…)
577+
"#]],
578+
);
579+
}
580+
581+
#[test]
582+
fn attr_diagnostic_on_unimplemented() {
583+
check(
584+
r#"#[diagnostic::on_unimplemented($0)] trait Foo {}"#,
585+
expect![[r#"
586+
ba label = "…"
587+
ba message = "…"
588+
ba note = "…"
589+
"#]],
590+
);
591+
check(
592+
r#"#[diagnostic::on_unimplemented(message = "foo", $0)] trait Foo {}"#,
593+
expect![[r#"
594+
ba label = "…"
595+
ba note = "…"
596+
"#]],
597+
);
598+
check(
599+
r#"#[diagnostic::on_unimplemented(note = "foo", $0)] trait Foo {}"#,
600+
expect![[r#"
601+
ba label = "…"
602+
ba message = "…"
603+
ba note = "…"
604+
"#]],
605+
);
606+
}
607+
535608
#[test]
536609
fn attr_on_extern_block() {
537610
check(
@@ -619,7 +692,6 @@ fn attr_on_fn() {
619692
at link_name = "…"
620693
at link_section = "…"
621694
at must_use
622-
at must_use
623695
at no_mangle
624696
at panic_handler
625697
at proc_macro
@@ -649,6 +721,8 @@ fn attr_in_source_file_end() {
649721
at deny(…)
650722
at deprecated
651723
at derive(…)
724+
at diagnostic::do_not_recommend
725+
at diagnostic::on_unimplemented
652726
at doc = "…"
653727
at doc(alias = "…")
654728
at doc(hidden)

0 commit comments

Comments
 (0)