Unnamed repository; edit this file 'description' to name the repository.
fix: Crash on completion inside macros
find_attr_range_with_source assumes that all syntax nodes came from the original user source code. This isn't true when we do speculative completion of macros: we create additional SyntaxNode values. Ensure that completion is defensive against created syntax nodes, and add a unit test. AI disclosure: Used GPT-5.5 to help implement.
Wilfred Hughes 10 days ago
parent 81d8b1d · commit 5f49900
-rw-r--r--crates/hir-expand/src/attrs.rs22
-rw-r--r--crates/hir-expand/src/db.rs2
-rw-r--r--crates/ide-completion/src/tests/attribute.rs19
3 files changed, 38 insertions, 5 deletions
diff --git a/crates/hir-expand/src/attrs.rs b/crates/hir-expand/src/attrs.rs
index 395b997972..896baacf04 100644
--- a/crates/hir-expand/src/attrs.rs
+++ b/crates/hir-expand/src/attrs.rs
@@ -375,12 +375,28 @@ impl AttrId {
/// Returns the containing `ast::Attr` (note that it may contain other attributes as well due
/// to `cfg_attr`) and its [`ast::Meta`].
+ ///
+ /// Assumes that the attribute syntax node was present in the
+ /// original file (not speculatively expanded macro output).
pub fn find_attr_range_with_source(
self,
db: &dyn ExpandDatabase,
krate: Crate,
owner: &dyn ast::HasAttrs,
) -> (ast::Attr, ast::Meta) {
+ self.find_attr_range_with_source_opt(db, krate, owner).unwrap_or_else(|| {
+ panic!("used an incorrect `AttrId`; crate={krate:?}, attr_id={self:?}");
+ })
+ }
+
+ /// Returns the containing `ast::Attr` (note that it may contain other attributes as well due
+ /// to `cfg_attr`) and its [`ast::Meta`].
+ pub(crate) fn find_attr_range_with_source_opt(
+ self,
+ db: &dyn ExpandDatabase,
+ krate: Crate,
+ owner: &dyn ast::HasAttrs,
+ ) -> Option<(ast::Attr, ast::Meta)> {
let cfg_options = OnceCell::new();
let mut index = 0;
let result = collect_item_tree_attrs(
@@ -395,10 +411,8 @@ impl AttrId {
},
);
match result {
- Some(Either::Left(it)) => it,
- _ => {
- panic!("used an incorrect `AttrId`; crate={krate:?}, attr_id={self:?}");
- }
+ Some(Either::Left(it)) => Some(it),
+ _ => None,
}
}
diff --git a/crates/hir-expand/src/db.rs b/crates/hir-expand/src/db.rs
index 87abee94cb..90e4e0086e 100644
--- a/crates/hir-expand/src/db.rs
+++ b/crates/hir-expand/src/db.rs
@@ -217,7 +217,7 @@ pub fn expand_speculative(
// then try finding a token id for our token if it is inside this input subtree.
let item = ast::Item::cast(speculative_args.clone())?;
let (_, meta) =
- attr_ids.invoc_attr().find_attr_range_with_source(db, loc.krate, &item);
+ attr_ids.invoc_attr().find_attr_range_with_source_opt(db, loc.krate, &item)?;
if let ast::Meta::TokenTreeMeta(meta) = meta
&& let Some(tt) = meta.token_tree()
{
diff --git a/crates/ide-completion/src/tests/attribute.rs b/crates/ide-completion/src/tests/attribute.rs
index 6dcd4f9cdd..300ea9bb11 100644
--- a/crates/ide-completion/src/tests/attribute.rs
+++ b/crates/ide-completion/src/tests/attribute.rs
@@ -1047,6 +1047,25 @@ mod cfg {
}
#[test]
+ fn inside_cfg_attr_gating_attr_macro() {
+ check(
+ r#"
+//- proc_macros: identity
+//- /main.rs cfg:feature=on
+#[cfg_attr(feat$0ure = "on", proc_macros::identity)]
+fn f() {}
+"#,
+ expect![[r#"
+ ba all
+ ba any
+ ba feature
+ ba not
+ ba true
+ "#]],
+ );
+ }
+
+ #[test]
fn complete_key_attr() {
check_edit(
"test",