let source_file = sema.parse(position.file_id);
let syntax = source_file.syntax();
- let mut defs = find_definitions(&sema, syntax, position)?;
+ let res = find_definitions(&sema, syntax, position)?
+ .map(|(name_like, def)| {
+ // ensure all ranges are valid
- // TODO:
- // - `find_definitions` is implemented so that it returns a non-empty vec
- // in the `Ok` case. But that's not expressed by the type signature, hence `unwrap()`
- // here which ... wart.
- // - is "just take the first `name_like`" correct? If not, what do?
- let (name_like, _def) = defs.next().unwrap();
- let frange = sema.original_range(name_like.syntax());
- always!(frange.range.contains_inclusive(position.offset) && frange.file_id == position.file_id);
- Ok(RangeInfo::new(frange.range, ()))
+ if def.range_for_rename(&sema).is_none() {
+ bail!("No references found at position")
+ }
+ let frange = sema.original_range(name_like.syntax());
+
+ always!(
+ frange.range.contains_inclusive(position.offset)
+ && frange.file_id == position.file_id
+ );
+ Ok(frange.range)
+ })
+ .reduce(|acc, cur| match (acc, cur) {
+ // ensure all ranges are the same
+ (Ok(acc_inner), Ok(cur_inner)) if acc_inner == cur_inner => Ok(acc_inner),
+ (Err(e), _) => Err(e),
+ _ => bail!("inconsistent text range"),
+ });
+
+ match res {
+ // ensure at least one definition was found
+ Some(res) => res.map(|range| RangeInfo::new(range, ())),
+ None => bail!("No references found at position"),
+ }
}
// Feature: 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"))
})
.map(|def| (name_like.clone(), def))
.ok_or_else(|| format_err!("No references found at position")),
- ast::NameLike::NameRef(name_ref) => NameRefClass::classify(sema, name_ref)
- .map(|class| match class {
- NameRefClass::Definition(def) => def,
- NameRefClass::FieldShorthand { local_ref, field_ref: _ } => {
- Definition::Local(local_ref)
- }
- })
- .and_then(|def| {
- // if the name differs from the definitions name it has to be an alias
- if def.name(sema.db).map_or(false, |it| it.to_string() != name_ref.text()) {
- None
- } else {
- Some((name_like.clone(), def))
- }
- })
- .ok_or_else(|| format_err!("Renaming aliases is currently unsupported")),
+ ast::NameLike::NameRef(name_ref) => {
+ NameRefClass::classify(sema, name_ref)
+ .map(|class| match class {
+ NameRefClass::Definition(def) => def,
+ NameRefClass::FieldShorthand { local_ref, field_ref: _ } => {
+ Definition::Local(local_ref)
+ }
+ })
+ .ok_or_else(|| format_err!("No references found at position"))
+ .and_then(|def| {
+ // if the name differs from the definitions name it has to be an alias
+ if def
+ .name(sema.db)
+ .map_or(false, |it| it.to_smol_str() != name_ref.text().as_str())
+ {
+ Err(format_err!("Renaming aliases is currently unsupported"))
+ } else {
+ Ok((name_like.clone(), def))
+ }
+ })
+ }
ast::NameLike::Lifetime(lifetime) => {
NameRefClass::classify_lifetime(sema, lifetime)
.and_then(|class| match class {
res
});
- // TODO avoid collect() somehow?
- let v: RenameResult<Vec<(ast::NameLike, Definition)>> = symbols.collect();
- match v {
- // remove duplicates
- // TODO is "unique by `Definition`" correct?
+ let res: RenameResult<Vec<_>> = symbols.collect();
+ match res {
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))
}
}
);
}
+ #[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(