Unnamed repository; edit this file 'description' to name the repository.
fix: upmap macro-expanded usages in destructure struct/tuple binding
when a local binding is used inside a macro call (e.g. write!(s, "{}", x.field) or write!(s, "{}", t.0)), the usage's name node lives in the macro expansion tree while the syntax editor is rooted at the source file tree. passing the expansion node to editor.replace() causes apply_edits to panic when it resolves the SyntaxNodePtr against the wrong tree. instead of skipping macro-expanded usages, map them back to the original source via sema.original_range_opt() and replace the text at the mapped range. this follows the established pattern of upmapping macro code to source code rather than ignoring it. for destructure_struct_binding, macro field accesses like x.y are replaced with the destructured field name y directly in the source. for destructure_tuple_binding, a new TextReplace variant on EditTupleUsage handles text-based replacements for macro-expanded tuple index accesses like x.0 → _0. when original_range_opt cannot map back (e.g. the index originates from the macro body rather than the call site), the code falls back to NoIndex (wrapping in block comments) or skipping, matching previous behavior. this also replaces the MacroStmts ancestor check in destructure_tuple_binding, which only covered macros expanding to multiple statements and missed single-expression macros (MacroExpr). fixes rust-lang/rust-analyzer#20716
albab-hasan 8 weeks ago
parent da2fe7f · commit 96e23e9
-rw-r--r--crates/ide-assists/src/handlers/destructure_struct_binding.rs36
-rw-r--r--crates/ide-assists/src/handlers/destructure_tuple_binding.rs88
2 files changed, 86 insertions, 38 deletions
diff --git a/crates/ide-assists/src/handlers/destructure_struct_binding.rs b/crates/ide-assists/src/handlers/destructure_struct_binding.rs
index 0f5ef0548c..3f42696fa3 100644
--- a/crates/ide-assists/src/handlers/destructure_struct_binding.rs
+++ b/crates/ide-assists/src/handlers/destructure_struct_binding.rs
@@ -17,7 +17,7 @@ use syntax::{
use crate::{
assist_context::{AssistContext, Assists, SourceChangeBuilder},
- utils::ref_field_expr::determine_ref_and_parens,
+ utils::{cover_edit_range, ref_field_expr::determine_ref_and_parens},
};
// Assist: destructure_struct_binding
@@ -358,6 +358,7 @@ fn update_usages(
data: &StructEditData,
field_names: &FxHashMap<SmolStr, SmolStr>,
) {
+ let source = ctx.source_file();
let make = SyntaxFactory::with_mappings();
let edits = data
.usages
@@ -366,7 +367,9 @@ fn update_usages(
.collect_vec();
editor.add_mappings(make.finish_with_mappings());
for (old, new) in edits {
- editor.replace(old, new);
+ if let Some(range) = ctx.sema.original_range_opt(&old) {
+ editor.replace_all(cover_edit_range(source, range.range), vec![new.into()]);
+ }
}
}
@@ -1006,4 +1009,33 @@ mod tests {
"#,
)
}
+
+ #[test]
+ fn record_struct_usage_in_macro_call() {
+ // exact repro from #20716: struct field access inside write! must not panic
+ check_assist(
+ destructure_struct_binding,
+ r#"
+//- minicore: write, fmt
+use core::fmt::Write;
+struct Foo { y: i8 }
+
+fn main() {
+ let mut s = String::new();
+ let $0x = Foo { y: 8 };
+ write!(s, "{}", x.y).unwrap();
+}
+"#,
+ r#"
+use core::fmt::Write;
+struct Foo { y: i8 }
+
+fn main() {
+ let mut s = String::new();
+ let Foo { y } = Foo { y: 8 };
+ write!(s, "{}", y).unwrap();
+}
+"#,
+ )
+ }
}
diff --git a/crates/ide-assists/src/handlers/destructure_tuple_binding.rs b/crates/ide-assists/src/handlers/destructure_tuple_binding.rs
index d51a3a26a3..583ba42bf5 100644
--- a/crates/ide-assists/src/handlers/destructure_tuple_binding.rs
+++ b/crates/ide-assists/src/handlers/destructure_tuple_binding.rs
@@ -14,7 +14,7 @@ use syntax::{
use crate::{
assist_context::{AssistContext, Assists, SourceChangeBuilder},
- utils::ref_field_expr::determine_ref_and_parens,
+ utils::{cover_edit_range, ref_field_expr::determine_ref_and_parens},
};
// Assist: destructure_tuple_binding
@@ -98,7 +98,9 @@ fn destructure_tuple_edit_impl(
assignment_edit.apply(&mut syntax_editor, &syntax_factory);
if let Some(usages_edit) = current_file_usages_edit {
- usages_edit.into_iter().for_each(|usage_edit| usage_edit.apply(edit, &mut syntax_editor))
+ usages_edit
+ .into_iter()
+ .for_each(|usage_edit| usage_edit.apply(ctx, edit, &mut syntax_editor))
}
syntax_editor.add_mappings(syntax_factory.finish_with_mappings());
@@ -311,14 +313,25 @@ enum EditTupleUsage {
}
impl EditTupleUsage {
- fn apply(self, edit: &mut SourceChangeBuilder, syntax_editor: &mut SyntaxEditor) {
+ fn apply(
+ self,
+ ctx: &AssistContext<'_>,
+ edit: &mut SourceChangeBuilder,
+ syntax_editor: &mut SyntaxEditor,
+ ) {
match self {
EditTupleUsage::NoIndex(range) => {
edit.insert(range.start(), "/*");
edit.insert(range.end(), "*/");
}
EditTupleUsage::ReplaceExpr(target_expr, replace_with) => {
- syntax_editor.replace(target_expr.syntax(), replace_with.syntax())
+ if let Some(range) = ctx.sema.original_range_opt(target_expr.syntax()) {
+ let source = ctx.source_file();
+ syntax_editor.replace_all(
+ cover_edit_range(source, range.range),
+ vec![replace_with.syntax().clone().into()],
+ );
+ }
}
}
}
@@ -349,24 +362,6 @@ fn detect_tuple_index(usage: &FileReference, data: &TupleData) -> Option<TupleIn
if let Some(field_expr) = ast::FieldExpr::cast(node) {
let idx = field_expr.name_ref()?.as_tuple_field()?;
if idx < data.field_names.len() {
- // special case: in macro call -> range of `field_expr` in applied macro, NOT range in actual file!
- if field_expr.syntax().ancestors().any(|a| ast::MacroStmts::can_cast(a.kind())) {
- cov_mark::hit!(destructure_tuple_macro_call);
-
- // issue: cannot differentiate between tuple index passed into macro or tuple index as result of macro:
- // ```rust
- // macro_rules! m {
- // ($t1:expr, $t2:expr) => { $t1; $t2.0 }
- // }
- // let t = (1,2);
- // m!(t.0, t)
- // ```
- // -> 2 tuple index usages detected!
- //
- // -> only handle `t`
- return None;
- }
-
Some(TupleIndex { index: idx, field_expr })
} else {
// tuple index out of range
@@ -1437,7 +1432,6 @@ fn main() {
#[test]
fn detect_macro_call() {
- cov_mark::check!(destructure_tuple_macro_call);
check_in_place_assist(
r#"
macro_rules! m {
@@ -1456,7 +1450,7 @@ macro_rules! m {
fn main() {
let ($0_0, _1) = (1,2);
- m!(/*t*/.0);
+ m!(_0);
}
"#,
)
@@ -1548,7 +1542,6 @@ fn main() {
m!(t.0);
}
"#,
- // FIXME: replace `t.0` with `_0` (cannot detect range of tuple index in macro call)
r#"
macro_rules! m {
($e:expr) => { "foo"; $e };
@@ -1556,10 +1549,9 @@ macro_rules! m {
fn main() {
let ($0_0, _1) = (1,2);
- m!(/*t*/.0);
+ m!(_0);
}
"#,
- // FIXME: replace `t.0` with `_0`
r#"
macro_rules! m {
($e:expr) => { "foo"; $e };
@@ -1567,7 +1559,7 @@ macro_rules! m {
fn main() {
let t @ ($0_0, _1) = (1,2);
- m!(t.0);
+ m!(_0);
}
"#,
)
@@ -1586,7 +1578,6 @@ fn main() {
m!((t).0);
}
"#,
- // FIXME: replace `(t).0` with `_0`
r#"
macro_rules! m {
($e:expr) => { "foo"; $e };
@@ -1594,10 +1585,9 @@ macro_rules! m {
fn main() {
let ($0_0, _1) = (1,2);
- m!((/*t*/).0);
+ m!(_0);
}
"#,
- // FIXME: replace `(t).0` with `_0`
r#"
macro_rules! m {
($e:expr) => { "foo"; $e };
@@ -1605,7 +1595,7 @@ macro_rules! m {
fn main() {
let t @ ($0_0, _1) = (1,2);
- m!((t).0);
+ m!(_0);
}
"#,
)
@@ -1653,7 +1643,6 @@ fn main() {
m!(t, t.0);
}
"#,
- // FIXME: replace `t.0` in macro call (not IN macro) with `_0`
r#"
macro_rules! m {
($t:expr, $i:expr) => { $t.0 + $i };
@@ -1661,10 +1650,9 @@ macro_rules! m {
fn main() {
let ($0_0, _1) = (1,2);
- m!(/*t*/, /*t*/.0);
+ m!(t, _0);
}
"#,
- // FIXME: replace `t.0` in macro call with `_0`
r#"
macro_rules! m {
($t:expr, $i:expr) => { $t.0 + $i };
@@ -1672,13 +1660,41 @@ macro_rules! m {
fn main() {
let t @ ($0_0, _1) = (1,2);
- m!(t, t.0);
+ m!(t, _0);
}
"#,
)
}
}
+ mod in_macro_expr {
+ use super::assist::*;
+
+ // exact repro from #20716: tuple index inside write! must not panic
+ #[test]
+ fn tuple_index_in_write_macro() {
+ check_in_place_assist(
+ r#"
+//- minicore: write, fmt
+use core::fmt::Write;
+fn main() {
+ let mut s = String::new();
+ let $0x = (2i32, 3i32);
+ write!(s, "{}", x.0).unwrap();
+}
+"#,
+ r#"
+use core::fmt::Write;
+fn main() {
+ let mut s = String::new();
+ let ($0_0, _1) = (2i32, 3i32);
+ write!(s, "{}", _0).unwrap();
+}
+"#,
+ )
+ }
+ }
+
mod refs {
use super::assist::*;