diff --git a/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs b/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs index 72418f155962..2446ffb84a92 100644 --- a/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs +++ b/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs @@ -1,5 +1,5 @@ use either::Either; -use ide_db::{defs::Definition, search::FileReference}; +use ide_db::{FxHashSet, defs::Definition, search::FileReference}; use syntax::{ NodeOrToken, SyntaxKind, SyntaxNode, T, algo::next_non_trivia_token, @@ -166,8 +166,12 @@ fn edit_struct_references( for (file_id, refs) in usages { let source = ctx.sema.parse(file_id); let editor = builder.make_editor(source.syntax()); + // a macro expanding $e twice yields two FileReferences with identical source ranges + let mut seen = FxHashSet::default(); for r in refs { - process_struct_name_reference(ctx, r, &editor, &source); + if seen.insert(r.range) { + process_struct_name_reference(ctx, r, &editor, &source); + } } builder.add_file_edits(file_id.file_id(ctx.db()), editor); } @@ -298,15 +302,17 @@ fn edit_field_references( let editor = builder.make_editor(source.syntax()); let make = editor.make(); + // a macro expanding $e twice yields two FileReferences with identical source ranges + let mut seen = FxHashSet::default(); for r in refs { - if let Some(name_ref) = r.name.as_name_ref() { - // Only edit the field reference if it's part of a `.field` access - if name_ref.syntax().parent().and_then(ast::FieldExpr::cast).is_some() { - editor.replace_all( - cover_edit_range(source.syntax(), r.range), - vec![make.name_ref(&index.to_string()).syntax().clone().into()], - ); - } + if let Some(name_ref) = r.name.as_name_ref() + && name_ref.syntax().parent().and_then(ast::FieldExpr::cast).is_some() + && seen.insert(r.range) + { + editor.replace_all( + cover_edit_range(source.syntax(), r.range), + vec![make.name_ref(&index.to_string()).syntax().clone().into()], + ); } } @@ -1302,6 +1308,23 @@ pub struct $0Foo { "#, r#" pub struct Foo(#[my_custom_attr]u32); +"#, + ); + } + + #[test] + fn field_ref_in_macro_expanding_arg_twice() { + check_assist( + convert_named_struct_to_tuple_struct, + r#" +struct $0Foo { x: i32 } +macro_rules! m { ($e:expr) => { ($e, $e) }; } +fn f(foo: Foo) { m!(foo.x); } +"#, + r#" +struct Foo(i32); +macro_rules! m { ($e:expr) => { ($e, $e) }; } +fn f(foo: Foo) { m!(foo.0); } "#, ); } diff --git a/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs b/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs index a6a47d78a8e0..0ea6bd938146 100644 --- a/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs +++ b/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs @@ -1,5 +1,6 @@ use either::Either; use ide_db::{ + FxHashSet, defs::{Definition, NameRefClass}, search::FileReference, }; @@ -154,9 +155,12 @@ fn edit_struct_references( for (file_id, refs) in usages { let source = ctx.sema.parse(file_id); let editor = edit.make_editor(source.syntax()); - + // a macro expanding $e twice yields two FileReferences with identical source ranges + let mut seen = FxHashSet::default(); for r in refs { - process_struct_name_reference(ctx, r, &editor, &source, &strukt_def, names); + if seen.insert(r.range) { + process_struct_name_reference(ctx, r, &editor, &source, &strukt_def, names); + } } edit.add_file_edits(file_id.file_id(ctx.db()), editor); @@ -285,9 +289,12 @@ fn edit_field_references( for (file_id, refs) in usages { let source = ctx.sema.parse(file_id); let editor = edit.make_editor(source.syntax()); + // a macro expanding $e twice yields two FileReferences with identical source ranges + let mut seen = FxHashSet::default(); for r in refs { if let Some(name_ref) = r.name.as_name_ref() && let Some(original) = ctx.sema.original_range_opt(name_ref.syntax()) + && seen.insert(r.range) { editor.replace_all( cover_edit_range(source.syntax(), original.range), @@ -1345,4 +1352,21 @@ impl T2 for S { "#, ); } + + #[test] + fn field_ref_in_macro_expanding_arg_twice() { + check_assist( + convert_tuple_struct_to_named_struct, + r#" +struct $0Foo(i32); +macro_rules! m { ($e:expr) => { ($e, $e) }; } +fn f(foo: Foo) { m!(foo.0); } +"#, + r#" +struct Foo { field1: i32 } +macro_rules! m { ($e:expr) => { ($e, $e) }; } +fn f(foo: Foo) { m!(foo.field1); } +"#, + ); + } } diff --git a/crates/ide-assists/src/handlers/destructure_struct_binding.rs b/crates/ide-assists/src/handlers/destructure_struct_binding.rs index 2b5b2ca13bb4..24fc6e925e16 100644 --- a/crates/ide-assists/src/handlers/destructure_struct_binding.rs +++ b/crates/ide-assists/src/handlers/destructure_struct_binding.rs @@ -360,9 +360,12 @@ fn update_usages( field_names: &FxHashMap, ) { let source = ctx.source_file().syntax(); + // a macro expanding $e twice yields two FileReferences with identical source ranges + let mut seen = FxHashSet::default(); let edits = data .usages .iter() + .filter(|r| seen.insert(r.range)) .filter_map(|r| build_usage_edit(ctx, editor.make(), data, r, field_names)) .collect_vec(); for (old, new) in edits { @@ -1034,6 +1037,29 @@ fn main() { let Foo { y } = Foo { y: 8 }; write!(s, "{}", y).unwrap(); } +"#, + ) + } + + #[test] + fn struct_field_in_macro_expanding_arg_twice() { + check_assist( + destructure_struct_binding, + r#" +struct Foo { x: i32 } +macro_rules! m { ($e:expr) => { ($e, $e) }; } +fn main() { + let $0foo = Foo { x: 1 }; + m!(foo.x); +} +"#, + r#" +struct Foo { x: i32 } +macro_rules! m { ($e:expr) => { ($e, $e) }; } +fn main() { + let Foo { x } = Foo { x: 1 }; + m!(x); +} "#, ) } diff --git a/crates/ide-assists/src/handlers/destructure_tuple_binding.rs b/crates/ide-assists/src/handlers/destructure_tuple_binding.rs index a272351e134c..6c7bb6fdfaaf 100644 --- a/crates/ide-assists/src/handlers/destructure_tuple_binding.rs +++ b/crates/ide-assists/src/handlers/destructure_tuple_binding.rs @@ -1,4 +1,5 @@ use ide_db::{ + FxHashSet, assists::AssistId, defs::Definition, search::{FileReference, SearchScope}, @@ -250,11 +251,14 @@ fn edit_tuple_usages( // tree mutation in the same file breaks when `builder.edit_file` // is called + // a macro expanding $e twice yields two FileReferences with identical source ranges + let mut seen = FxHashSet::default(); let edits = data .usages .as_ref()? .as_slice() .iter() + .filter(|r| seen.insert(r.range)) .filter_map(|r| edit_tuple_usage(ctx, make, r, data, in_sub_pattern)) .collect_vec(); @@ -1652,6 +1656,26 @@ macro_rules! m { fn main() { let t @ ($0_0, _1) = (1,2); m!(t, _0); +} + "#, + ) + } + + #[test] + fn tuple_index_in_macro_expanding_arg_twice() { + check_in_place_assist( + r#" +macro_rules! m { ($e:expr) => { ($e, $e) }; } +fn main() { + let $0t = (1,); + m!(t.0); +} + "#, + r#" +macro_rules! m { ($e:expr) => { ($e, $e) }; } +fn main() { + let ($0_0,) = (1,); + m!(_0); } "#, )