about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute.rs54
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/diagnostic.rs60
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs78
3 files changed, 175 insertions, 17 deletions
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute.rs
index 352e4444b72..f696f0c0b95 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute.rs
+++ b/src/tools/rust-analyzer/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,14 +41,23 @@ 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())?;
+    let path = attribute.path()?;
+    let name_ref = path.segment()?.name_ref();
+    let (name_ref, tt) = name_ref.zip(attribute.token_tree())?;
     tt.l_paren_token()?;
 
-    match path.text().as_str() {
+    if let Some(qualifier) = path.qualifier() {
+        let qualifier_name_ref = qualifier.as_single_name_ref()?;
+        match (qualifier_name_ref.text().as_str(), name_ref.text().as_str()) {
+            ("diagnostic", "on_unimplemented") => {
+                diagnostic::complete_on_unimplemented(acc, ctx, tt)
+            }
+            _ => (),
+        }
+        return Some(());
+    }
+
+    match name_ref.text().as_str() {
         "repr" => repr::complete_repr(acc, ctx, tt),
         "feature" => lint::complete_lint(
             acc,
@@ -139,6 +149,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 +161,28 @@ 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 already have the qualifier of the completion, then trim it from the label and the snippet
+        let mut label = attr_completion.label;
+        let mut snippet = attr_completion.snippet;
+        if let Some(name_ref) = qualifier_path.and_then(|q| q.as_single_name_ref()) {
+            if let Some((label_qual, label_seg)) = attr_completion.label.split_once("::") {
+                if name_ref.text() == label_qual {
+                    label = label_seg;
+                    snippet = snippet.map(|snippet| {
+                        snippet.trim_start_matches(label_qual).trim_start_matches("::")
+                    });
+                }
+            }
+        }
+
+        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);
         }
 
@@ -270,8 +292,8 @@ static KIND_TO_ATTRIBUTES: LazyLock<FxHashMap<SyntaxKind, &[&str]>> = LazyLock::
             ),
         ),
         (STATIC, attrs!(item, linkable, "global_allocator", "used")),
-        (TRAIT, attrs!(item)),
-        (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 +333,8 @@ 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("diagnostic::do_not_recommend", None, None),
+    attr("diagnostic::on_unimplemented", None, Some(r#"diagnostic::on_unimplemented(${0:keys})"#)),
     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/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/diagnostic.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/diagnostic.rs
new file mode 100644
index 00000000000..10c5135b4b5
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/diagnostic.rs
@@ -0,0 +1,60 @@
+//! Completion for diagnostic attributes.
+
+use ide_db::SymbolKind;
+use syntax::ast::{self};
+
+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 ATTRIBUTES {
+            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 ATTRIBUTES: &[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/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs
index 32d3b50f237..411902f1117 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs
+++ b/src/tools/rust-analyzer/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)