Skip to content

Commit 59fe399

Browse files
committed
feat: implement completion for diagnostic module
1 parent 3f0df08 commit 59fe399

File tree

3 files changed

+175
-17
lines changed

3 files changed

+175
-17
lines changed

src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute.rs

Lines changed: 39 additions & 15 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,14 +41,23 @@ 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())?;
44+
let path = attribute.path()?;
45+
let name_ref = path.segment()?.name_ref();
46+
let (name_ref, tt) = name_ref.zip(attribute.token_tree())?;
4847
tt.l_paren_token()?;
4948

50-
match path.text().as_str() {
49+
if let Some(qualifier) = path.qualifier() {
50+
let qualifier_name_ref = qualifier.as_single_name_ref()?;
51+
match (qualifier_name_ref.text().as_str(), name_ref.text().as_str()) {
52+
("diagnostic", "on_unimplemented") => {
53+
diagnostic::complete_on_unimplemented(acc, ctx, tt)
54+
}
55+
_ => (),
56+
}
57+
return Some(());
58+
}
59+
60+
match name_ref.text().as_str() {
5161
"repr" => repr::complete_repr(acc, ctx, tt),
5262
"feature" => lint::complete_lint(
5363
acc,
@@ -139,6 +149,8 @@ pub(crate) fn complete_attribute_path(
139149
}
140150
Qualified::TypeAnchor { .. } | Qualified::With { .. } => {}
141151
}
152+
let qualifier_path =
153+
if let Qualified::With { path, .. } = qualified { Some(path) } else { None };
142154

143155
let attributes = annotated_item_kind.and_then(|kind| {
144156
if ast::Expr::can_cast(kind) {
@@ -149,18 +161,28 @@ pub(crate) fn complete_attribute_path(
149161
});
150162

151163
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-
);
164+
// if we already have the qualifier of the completion, then trim it from the label and the snippet
165+
let mut label = attr_completion.label;
166+
let mut snippet = attr_completion.snippet;
167+
if let Some(name_ref) = qualifier_path.and_then(|q| q.as_single_name_ref()) {
168+
if let Some((label_qual, label_seg)) = attr_completion.label.split_once("::") {
169+
if name_ref.text() == label_qual {
170+
label = label_seg;
171+
snippet = snippet.map(|snippet| {
172+
snippet.trim_start_matches(label_qual).trim_start_matches("::")
173+
});
174+
}
175+
}
176+
}
177+
178+
let mut item =
179+
CompletionItem::new(SymbolKind::Attribute, ctx.source_range(), label, ctx.edition);
158180

159181
if let Some(lookup) = attr_completion.lookup {
160182
item.lookup_by(lookup);
161183
}
162184

163-
if let Some((snippet, cap)) = attr_completion.snippet.zip(ctx.config.snippet_cap) {
185+
if let Some((snippet, cap)) = snippet.zip(ctx.config.snippet_cap) {
164186
item.insert_snippet(cap, snippet);
165187
}
166188

@@ -270,8 +292,8 @@ static KIND_TO_ATTRIBUTES: LazyLock<FxHashMap<SyntaxKind, &[&str]>> = LazyLock::
270292
),
271293
),
272294
(STATIC, attrs!(item, linkable, "global_allocator", "used")),
273-
(TRAIT, attrs!(item)),
274-
(IMPL, attrs!(item, "automatically_derived")),
295+
(TRAIT, attrs!(item, "diagnostic::on_unimplemented")),
296+
(IMPL, attrs!(item, "automatically_derived", "diagnostic::do_not_recommend")),
275297
(ASSOC_ITEM_LIST, attrs!(item)),
276298
(EXTERN_BLOCK, attrs!(item, "link")),
277299
(EXTERN_ITEM_LIST, attrs!(item, "link")),
@@ -311,6 +333,8 @@ const ATTRIBUTES: &[AttrCompletion] = &[
311333
attr("deny(…)", Some("deny"), Some("deny(${0:lint})")),
312334
attr(r#"deprecated"#, Some("deprecated"), Some(r#"deprecated"#)),
313335
attr("derive(…)", Some("derive"), Some(r#"derive(${0:Debug})"#)),
336+
attr("diagnostic::do_not_recommend", None, None),
337+
attr("diagnostic::on_unimplemented", None, Some(r#"diagnostic::on_unimplemented(${0:keys})"#)),
314338
attr(r#"doc = "…""#, Some("doc"), Some(r#"doc = "${0:docs}""#)),
315339
attr(r#"doc(alias = "…")"#, Some("docalias"), Some(r#"doc(alias = "${0:docs}")"#)),
316340
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::{self};
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 ATTRIBUTES {
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 ATTRIBUTES: &[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+
];

src/tools/rust-analyzer/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)