diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs index 1f85480b16c..9ce0dd7404a 100644 --- a/crates/ide/src/rename.rs +++ b/crates/ide/src/rename.rs @@ -100,6 +100,7 @@ pub(crate) fn rename( def.rename(&sema, new_name) }) .collect(); + ops?.into_iter() .reduce(|acc, elem| acc.merge(elem)) .ok_or_else(|| format_err!("No references found at position")) @@ -186,13 +187,14 @@ fn find_definitions( res }); - let res: RenameResult> = symbols.collect(); + let res: RenameResult> = symbols.collect(); match res { - // remove duplicates Ok(v) => { if v.is_empty() { + // FIXME: some semantic duplication between "empty vec" and "Err()" Err(format_err!("No references found at position")) } else { + // remove duplicates, comparing `Definition`s Ok(v.into_iter().unique_by(|t| t.1)) } } @@ -569,6 +571,36 @@ fn main() { ); } + #[test] + fn test_rename_macro_multiple_occurrences() { + check( + "Baaah", + r#"macro_rules! foo { + ($ident:ident) => { + const $ident: () = (); + struct $ident {} + }; +} + +foo!($0Foo); +const _: () = Foo; +const _: Foo = Foo {}; + "#, + r#" +macro_rules! foo { + ($ident:ident) => { + const $ident: () = (); + struct $ident {} + }; +} + +foo!(Baaah); +const _: () = Baaah; +const _: Baaah = Baaah {}; + "#, + ) + } + #[test] fn test_rename_for_macro_args() { check( diff --git a/crates/text_edit/src/lib.rs b/crates/text_edit/src/lib.rs index e9700c560ad..c4000d80522 100644 --- a/crates/text_edit/src/lib.rs +++ b/crates/text_edit/src/lib.rs @@ -3,12 +3,14 @@ //! `rust-analyzer` never mutates text itself and only sends diffs to clients, //! so `TextEdit` is the ultimate representation of the work done by //! rust-analyzer. +use std::collections::HashSet; + pub use text_size::{TextRange, TextSize}; /// `InsertDelete` -- a single "atomic" change to text /// /// Must not overlap with other `InDel`s -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Indel { pub insert: String, /// Refers to offsets in the original text @@ -114,13 +116,20 @@ impl TextEdit { } pub fn union(&mut self, other: TextEdit) -> Result<(), TextEdit> { + dbg!(&self, &other); // FIXME: can be done without allocating intermediate vector let mut all = self.iter().chain(other.iter()).collect::>(); - if !check_disjoint(&mut all) { + if !check_disjoint_or_equal(&mut all) { return Err(other); } - self.indels.extend(other.indels); - assert_disjoint(&mut self.indels); + + // remove duplicates + // FIXME: maybe make indels a HashSet instead to get rid of this? + let our_indels = self.indels.clone(); + let our_indels = our_indels.iter().collect::>(); + let other_indels = other.indels.into_iter().filter(|i| !our_indels.contains(i)); + + self.indels.extend(other_indels); Ok(()) } @@ -173,7 +182,7 @@ impl TextEditBuilder { } pub fn finish(self) -> TextEdit { let mut indels = self.indels; - assert_disjoint(&mut indels); + assert_disjoint_or_equal(&mut indels); TextEdit { indels } } pub fn invalidates_offset(&self, offset: TextSize) -> bool { @@ -182,18 +191,19 @@ impl TextEditBuilder { fn indel(&mut self, indel: Indel) { self.indels.push(indel); if self.indels.len() <= 16 { - assert_disjoint(&mut self.indels); + assert_disjoint_or_equal(&mut self.indels); } } } -fn assert_disjoint(indels: &mut [impl std::borrow::Borrow]) { - assert!(check_disjoint(indels)); +fn assert_disjoint_or_equal(indels: &mut [impl std::borrow::Borrow]) { + assert!(check_disjoint_or_equal(indels)); } -fn check_disjoint(indels: &mut [impl std::borrow::Borrow]) -> bool { +fn check_disjoint_or_equal(indels: &mut [impl std::borrow::Borrow]) -> bool { indels.sort_by_key(|indel| (indel.borrow().delete.start(), indel.borrow().delete.end())); - indels - .iter() - .zip(indels.iter().skip(1)) - .all(|(l, r)| l.borrow().delete.end() <= r.borrow().delete.start()) + indels.iter().zip(indels.iter().skip(1)).all(|(l, r)| { + let l = l.borrow(); + let r = r.borrow(); + l.delete.end() <= r.delete.start() || l == r + }) }