Unnamed repository; edit this file 'description' to name the repository.
Merge pull request #19908 from rmehri01/rmehri01/diagnostic_attribute_completions
feat: implement attribute completions for diagnostics module
Lukas Wirth 11 months ago
parent 4fd1cdb · parent b95101c · commit 6acff6c
-rw-r--r--crates/ide-completion/src/completions/attribute.rs77
-rw-r--r--crates/ide-completion/src/completions/attribute/diagnostic.rs60
-rw-r--r--crates/ide-completion/src/tests/attribute.rs78
3 files changed, 190 insertions, 25 deletions
diff --git a/crates/ide-completion/src/completions/attribute.rs b/crates/ide-completion/src/completions/attribute.rs
index 3c195f80fe..705402c785 100644
--- a/crates/ide-completion/src/completions/attribute.rs
+++ b/crates/ide-completion/src/completions/attribute.rs
@@ -25,6 +25,7 @@ use crate::{
mod cfg;
mod derive;
+mod diagnostic;
mod lint;
mod macro_use;
mod repr;
@@ -40,23 +41,22 @@ pub(crate) fn complete_known_attribute_input(
extern_crate: Option<&ast::ExternCrate>,
) -> Option<()> {
let attribute = fake_attribute_under_caret;
- let name_ref = match attribute.path() {
- Some(p) => Some(p.as_single_name_ref()?),
- None => None,
- };
- let (path, tt) = name_ref.zip(attribute.token_tree())?;
- tt.l_paren_token()?;
+ let path = attribute.path()?;
+ let segments = path.segments().map(|s| s.name_ref()).collect::<Option<Vec<_>>>()?;
+ let segments = segments.iter().map(|n| n.text()).collect::<Vec<_>>();
+ let segments = segments.iter().map(|t| t.as_str()).collect::<Vec<_>>();
+ let tt = attribute.token_tree()?;
- match path.text().as_str() {
- "repr" => repr::complete_repr(acc, ctx, tt),
- "feature" => lint::complete_lint(
+ match segments.as_slice() {
+ ["repr"] => repr::complete_repr(acc, ctx, tt),
+ ["feature"] => lint::complete_lint(
acc,
ctx,
colon_prefix,
&parse_tt_as_comma_sep_paths(tt, ctx.edition)?,
FEATURES,
),
- "allow" | "expect" | "deny" | "forbid" | "warn" => {
+ ["allow"] | ["expect"] | ["deny"] | ["forbid"] | ["warn"] => {
let existing_lints = parse_tt_as_comma_sep_paths(tt, ctx.edition)?;
let lints: Vec<Lint> = CLIPPY_LINT_GROUPS
@@ -70,13 +70,14 @@ pub(crate) fn complete_known_attribute_input(
lint::complete_lint(acc, ctx, colon_prefix, &existing_lints, &lints);
}
- "cfg" => cfg::complete_cfg(acc, ctx),
- "macro_use" => macro_use::complete_macro_use(
+ ["cfg"] => cfg::complete_cfg(acc, ctx),
+ ["macro_use"] => macro_use::complete_macro_use(
acc,
ctx,
extern_crate,
&parse_tt_as_comma_sep_paths(tt, ctx.edition)?,
),
+ ["diagnostic", "on_unimplemented"] => diagnostic::complete_on_unimplemented(acc, ctx, tt),
_ => (),
}
Some(())
@@ -139,6 +140,8 @@ pub(crate) fn complete_attribute_path(
}
Qualified::TypeAnchor { .. } | Qualified::With { .. } => {}
}
+ let qualifier_path =
+ if let Qualified::With { path, .. } = qualified { Some(path) } else { None };
let attributes = annotated_item_kind.and_then(|kind| {
if ast::Expr::can_cast(kind) {
@@ -149,18 +152,33 @@ pub(crate) fn complete_attribute_path(
});
let add_completion = |attr_completion: &AttrCompletion| {
- let mut item = CompletionItem::new(
- SymbolKind::Attribute,
- ctx.source_range(),
- attr_completion.label,
- ctx.edition,
- );
+ // if we don't already have the qualifiers of the completion, then
+ // add the missing parts to the label and snippet
+ let mut label = attr_completion.label.to_owned();
+ let mut snippet = attr_completion.snippet.map(|s| s.to_owned());
+ let segments = qualifier_path.iter().flat_map(|q| q.segments()).collect::<Vec<_>>();
+ let qualifiers = attr_completion.qualifiers;
+ let matching_qualifiers = segments
+ .iter()
+ .zip(qualifiers)
+ .take_while(|(s, q)| s.name_ref().is_some_and(|t| t.text() == **q))
+ .count();
+ if matching_qualifiers != qualifiers.len() {
+ let prefix = qualifiers[matching_qualifiers..].join("::");
+ label = format!("{prefix}::{label}");
+ if let Some(s) = snippet.as_mut() {
+ *s = format!("{prefix}::{s}");
+ }
+ }
+
+ let mut item =
+ CompletionItem::new(SymbolKind::Attribute, ctx.source_range(), label, ctx.edition);
if let Some(lookup) = attr_completion.lookup {
item.lookup_by(lookup);
}
- if let Some((snippet, cap)) = attr_completion.snippet.zip(ctx.config.snippet_cap) {
+ if let Some((snippet, cap)) = snippet.zip(ctx.config.snippet_cap) {
item.insert_snippet(cap, snippet);
}
@@ -184,6 +202,7 @@ struct AttrCompletion {
label: &'static str,
lookup: Option<&'static str>,
snippet: Option<&'static str>,
+ qualifiers: &'static [&'static str],
prefer_inner: bool,
}
@@ -192,6 +211,10 @@ impl AttrCompletion {
self.lookup.unwrap_or(self.label)
}
+ const fn qualifiers(self, qualifiers: &'static [&'static str]) -> AttrCompletion {
+ AttrCompletion { qualifiers, ..self }
+ }
+
const fn prefer_inner(self) -> AttrCompletion {
AttrCompletion { prefer_inner: true, ..self }
}
@@ -202,7 +225,7 @@ const fn attr(
lookup: Option<&'static str>,
snippet: Option<&'static str>,
) -> AttrCompletion {
- AttrCompletion { label, lookup, snippet, prefer_inner: false }
+ AttrCompletion { label, lookup, snippet, qualifiers: &[], prefer_inner: false }
}
macro_rules! attrs {
@@ -264,14 +287,14 @@ static KIND_TO_ATTRIBUTES: LazyLock<FxHashMap<SyntaxKind, &[&str]>> = LazyLock::
FN,
attrs!(
item, linkable,
- "cold", "ignore", "inline", "must_use", "panic_handler", "proc_macro",
+ "cold", "ignore", "inline", "panic_handler", "proc_macro",
"proc_macro_derive", "proc_macro_attribute", "should_panic", "target_feature",
"test", "track_caller"
),
),
(STATIC, attrs!(item, linkable, "global_allocator", "used")),
- (TRAIT, attrs!(item, "must_use")),
- (IMPL, attrs!(item, "automatically_derived")),
+ (TRAIT, attrs!(item, "diagnostic::on_unimplemented")),
+ (IMPL, attrs!(item, "automatically_derived", "diagnostic::do_not_recommend")),
(ASSOC_ITEM_LIST, attrs!(item)),
(EXTERN_BLOCK, attrs!(item, "link")),
(EXTERN_ITEM_LIST, attrs!(item, "link")),
@@ -311,6 +334,14 @@ const ATTRIBUTES: &[AttrCompletion] = &[
attr("deny(…)", Some("deny"), Some("deny(${0:lint})")),
attr(r#"deprecated"#, Some("deprecated"), Some(r#"deprecated"#)),
attr("derive(…)", Some("derive"), Some(r#"derive(${0:Debug})"#)),
+ attr("do_not_recommend", Some("diagnostic::do_not_recommend"), None)
+ .qualifiers(&["diagnostic"]),
+ attr(
+ "on_unimplemented",
+ Some("diagnostic::on_unimplemented"),
+ Some(r#"on_unimplemented(${0:keys})"#),
+ )
+ .qualifiers(&["diagnostic"]),
attr(r#"doc = "…""#, Some("doc"), Some(r#"doc = "${0:docs}""#)),
attr(r#"doc(alias = "…")"#, Some("docalias"), Some(r#"doc(alias = "${0:docs}")"#)),
attr(r#"doc(hidden)"#, Some("dochidden"), Some(r#"doc(hidden)"#)),
diff --git a/crates/ide-completion/src/completions/attribute/diagnostic.rs b/crates/ide-completion/src/completions/attribute/diagnostic.rs
new file mode 100644
index 0000000000..8adc974239
--- /dev/null
+++ b/crates/ide-completion/src/completions/attribute/diagnostic.rs
@@ -0,0 +1,60 @@
+//! Completion for diagnostic attributes.
+
+use ide_db::SymbolKind;
+use syntax::ast;
+
+use crate::{CompletionItem, Completions, context::CompletionContext};
+
+use super::AttrCompletion;
+
+pub(super) fn complete_on_unimplemented(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ input: ast::TokenTree,
+) {
+ if let Some(existing_keys) = super::parse_comma_sep_expr(input) {
+ for attr in ATTRIBUTE_ARGS {
+ let already_annotated = existing_keys
+ .iter()
+ .filter_map(|expr| match expr {
+ ast::Expr::PathExpr(path) => path.path()?.as_single_name_ref(),
+ ast::Expr::BinExpr(bin)
+ if bin.op_kind() == Some(ast::BinaryOp::Assignment { op: None }) =>
+ {
+ match bin.lhs()? {
+ ast::Expr::PathExpr(path) => path.path()?.as_single_name_ref(),
+ _ => None,
+ }
+ }
+ _ => None,
+ })
+ .any(|it| {
+ let text = it.text();
+ attr.key() == text && text != "note"
+ });
+ if already_annotated {
+ continue;
+ }
+
+ let mut item = CompletionItem::new(
+ SymbolKind::BuiltinAttr,
+ ctx.source_range(),
+ attr.label,
+ ctx.edition,
+ );
+ if let Some(lookup) = attr.lookup {
+ item.lookup_by(lookup);
+ }
+ if let Some((snippet, cap)) = attr.snippet.zip(ctx.config.snippet_cap) {
+ item.insert_snippet(cap, snippet);
+ }
+ item.add_to(acc, ctx.db);
+ }
+ }
+}
+
+const ATTRIBUTE_ARGS: &[AttrCompletion] = &[
+ super::attr(r#"label = "…""#, Some("label"), Some(r#"label = "${0:label}""#)),
+ super::attr(r#"message = "…""#, Some("message"), Some(r#"message = "${0:message}""#)),
+ super::attr(r#"note = "…""#, Some("note"), Some(r#"note = "${0:note}""#)),
+];
diff --git a/crates/ide-completion/src/tests/attribute.rs b/crates/ide-completion/src/tests/attribute.rs
index 32d3b50f23..411902f111 100644
--- a/crates/ide-completion/src/tests/attribute.rs
+++ b/crates/ide-completion/src/tests/attribute.rs
@@ -30,6 +30,8 @@ pub struct Foo(#[m$0] i32);
at deprecated
at derive macro derive
at derive(…)
+ at diagnostic::do_not_recommend
+ at diagnostic::on_unimplemented
at doc = "…"
at doc(alias = "…")
at doc(hidden)
@@ -472,13 +474,13 @@ fn attr_on_trait() {
at cfg_attr(…)
at deny(…)
at deprecated
+ at diagnostic::on_unimplemented
at doc = "…"
at doc(alias = "…")
at doc(hidden)
at expect(…)
at forbid(…)
at must_use
- at must_use
at no_mangle
at warn(…)
kw crate::
@@ -498,6 +500,7 @@ fn attr_on_impl() {
at cfg_attr(…)
at deny(…)
at deprecated
+ at diagnostic::do_not_recommend
at doc = "…"
at doc(alias = "…")
at doc(hidden)
@@ -533,6 +536,76 @@ fn attr_on_impl() {
}
#[test]
+fn attr_with_qualifier() {
+ check(
+ r#"#[diagnostic::$0] impl () {}"#,
+ expect![[r#"
+ at allow(…)
+ at automatically_derived
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at do_not_recommend
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at expect(…)
+ at forbid(…)
+ at must_use
+ at no_mangle
+ at warn(…)
+ "#]],
+ );
+ check(
+ r#"#[diagnostic::$0] trait Foo {}"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at expect(…)
+ at forbid(…)
+ at must_use
+ at no_mangle
+ at on_unimplemented
+ at warn(…)
+ "#]],
+ );
+}
+
+#[test]
+fn attr_diagnostic_on_unimplemented() {
+ check(
+ r#"#[diagnostic::on_unimplemented($0)] trait Foo {}"#,
+ expect![[r#"
+ ba label = "…"
+ ba message = "…"
+ ba note = "…"
+ "#]],
+ );
+ check(
+ r#"#[diagnostic::on_unimplemented(message = "foo", $0)] trait Foo {}"#,
+ expect![[r#"
+ ba label = "…"
+ ba note = "…"
+ "#]],
+ );
+ check(
+ r#"#[diagnostic::on_unimplemented(note = "foo", $0)] trait Foo {}"#,
+ expect![[r#"
+ ba label = "…"
+ ba message = "…"
+ ba note = "…"
+ "#]],
+ );
+}
+
+#[test]
fn attr_on_extern_block() {
check(
r#"#[$0] extern {}"#,
@@ -619,7 +692,6 @@ fn attr_on_fn() {
at link_name = "…"
at link_section = "…"
at must_use
- at must_use
at no_mangle
at panic_handler
at proc_macro
@@ -649,6 +721,8 @@ fn attr_in_source_file_end() {
at deny(…)
at deprecated
at derive(…)
+ at diagnostic::do_not_recommend
+ at diagnostic::on_unimplemented
at doc = "…"
at doc(alias = "…")
at doc(hidden)