feat: implement completion for diagnostic module

This commit is contained in:
Ryan Mehri 2025-06-02 17:17:27 -04:00
parent fb921c3039
commit 7e8079fbad
3 changed files with 175 additions and 17 deletions

View File

@ -25,6 +25,7 @@ use crate::{
mod cfg; mod cfg;
mod derive; mod derive;
mod diagnostic;
mod lint; mod lint;
mod macro_use; mod macro_use;
mod repr; mod repr;
@ -40,14 +41,23 @@ pub(crate) fn complete_known_attribute_input(
extern_crate: Option<&ast::ExternCrate>, extern_crate: Option<&ast::ExternCrate>,
) -> Option<()> { ) -> Option<()> {
let attribute = fake_attribute_under_caret; let attribute = fake_attribute_under_caret;
let name_ref = match attribute.path() { let path = attribute.path()?;
Some(p) => Some(p.as_single_name_ref()?), let name_ref = path.segment()?.name_ref();
None => None, let (name_ref, tt) = name_ref.zip(attribute.token_tree())?;
};
let (path, tt) = name_ref.zip(attribute.token_tree())?;
tt.l_paren_token()?; 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), "repr" => repr::complete_repr(acc, ctx, tt),
"feature" => lint::complete_lint( "feature" => lint::complete_lint(
acc, acc,
@ -139,6 +149,8 @@ pub(crate) fn complete_attribute_path(
} }
Qualified::TypeAnchor { .. } | Qualified::With { .. } => {} 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| { let attributes = annotated_item_kind.and_then(|kind| {
if ast::Expr::can_cast(kind) { if ast::Expr::can_cast(kind) {
@ -149,18 +161,28 @@ pub(crate) fn complete_attribute_path(
}); });
let add_completion = |attr_completion: &AttrCompletion| { let add_completion = |attr_completion: &AttrCompletion| {
let mut item = CompletionItem::new( // if we already have the qualifier of the completion, then trim it from the label and the snippet
SymbolKind::Attribute, let mut label = attr_completion.label;
ctx.source_range(), let mut snippet = attr_completion.snippet;
attr_completion.label, if let Some(name_ref) = qualifier_path.and_then(|q| q.as_single_name_ref()) {
ctx.edition, 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 { if let Some(lookup) = attr_completion.lookup {
item.lookup_by(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); 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")), (STATIC, attrs!(item, linkable, "global_allocator", "used")),
(TRAIT, attrs!(item)), (TRAIT, attrs!(item, "diagnostic::on_unimplemented")),
(IMPL, attrs!(item, "automatically_derived")), (IMPL, attrs!(item, "automatically_derived", "diagnostic::do_not_recommend")),
(ASSOC_ITEM_LIST, attrs!(item)), (ASSOC_ITEM_LIST, attrs!(item)),
(EXTERN_BLOCK, attrs!(item, "link")), (EXTERN_BLOCK, attrs!(item, "link")),
(EXTERN_ITEM_LIST, 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("deny(…)", Some("deny"), Some("deny(${0:lint})")),
attr(r#"deprecated"#, Some("deprecated"), Some(r#"deprecated"#)), attr(r#"deprecated"#, Some("deprecated"), Some(r#"deprecated"#)),
attr("derive(…)", Some("derive"), Some(r#"derive(${0:Debug})"#)), 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 = "…""#, Some("doc"), Some(r#"doc = "${0:docs}""#)),
attr(r#"doc(alias = "…")"#, Some("docalias"), Some(r#"doc(alias = "${0:docs}")"#)), attr(r#"doc(alias = "…")"#, Some("docalias"), Some(r#"doc(alias = "${0:docs}")"#)),
attr(r#"doc(hidden)"#, Some("dochidden"), Some(r#"doc(hidden)"#)), attr(r#"doc(hidden)"#, Some("dochidden"), Some(r#"doc(hidden)"#)),

View File

@ -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}""#)),
];

View File

@ -30,6 +30,8 @@ pub struct Foo(#[m$0] i32);
at deprecated at deprecated
at derive macro derive at derive macro derive
at derive() at derive()
at diagnostic::do_not_recommend
at diagnostic::on_unimplemented
at doc = "" at doc = ""
at doc(alias = "") at doc(alias = "")
at doc(hidden) at doc(hidden)
@ -472,13 +474,13 @@ fn attr_on_trait() {
at cfg_attr() at cfg_attr()
at deny() at deny()
at deprecated at deprecated
at diagnostic::on_unimplemented
at doc = "" at doc = ""
at doc(alias = "") at doc(alias = "")
at doc(hidden) at doc(hidden)
at expect() at expect()
at forbid() at forbid()
at must_use at must_use
at must_use
at no_mangle at no_mangle
at warn() at warn()
kw crate:: kw crate::
@ -498,6 +500,7 @@ fn attr_on_impl() {
at cfg_attr() at cfg_attr()
at deny() at deny()
at deprecated at deprecated
at diagnostic::do_not_recommend
at doc = "" at doc = ""
at doc(alias = "") at doc(alias = "")
at doc(hidden) at doc(hidden)
@ -532,6 +535,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] #[test]
fn attr_on_extern_block() { fn attr_on_extern_block() {
check( check(
@ -619,7 +692,6 @@ fn attr_on_fn() {
at link_name = "" at link_name = ""
at link_section = "" at link_section = ""
at must_use at must_use
at must_use
at no_mangle at no_mangle
at panic_handler at panic_handler
at proc_macro at proc_macro
@ -649,6 +721,8 @@ fn attr_in_source_file_end() {
at deny() at deny()
at deprecated at deprecated
at derive() at derive()
at diagnostic::do_not_recommend
at diagnostic::on_unimplemented
at doc = "" at doc = ""
at doc(alias = "") at doc(alias = "")
at doc(hidden) at doc(hidden)