Skip to content

Commit 6decfce

Browse files
author
Anatol Ulrich
committed
WIP: fix: make rename multi-token mapping aware
1 parent a3830df commit 6decfce

File tree

2 files changed

+108
-66
lines changed

2 files changed

+108
-66
lines changed

crates/ide/src/rename.rs

Lines changed: 95 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,20 @@
33
//! This is mostly front-end for [`ide_db::rename`], but it also includes the
44
//! tests. This module also implements a couple of magic tricks, like renaming
55
//! `self` and to `self` (to switch between associated function and method).
6+
67
use hir::{AsAssocItem, InFile, Semantics};
78
use ide_db::{
89
base_db::FileId,
910
defs::{Definition, NameClass, NameRefClass},
1011
rename::{bail, format_err, source_edit_from_references, IdentifierKind},
1112
RootDatabase,
1213
};
14+
use itertools::Itertools;
1315
use stdx::{always, never};
14-
use syntax::{ast, AstNode, SyntaxNode};
16+
use syntax::{
17+
ast::{self},
18+
AstNode, SyntaxNode,
19+
};
1520

1621
use text_edit::TextEdit;
1722

@@ -31,11 +36,12 @@ pub(crate) fn prepare_rename(
3136
let source_file = sema.parse(position.file_id);
3237
let syntax = source_file.syntax();
3338

34-
let (name_like, def) = find_definition(&sema, syntax, position)?;
35-
if def.range_for_rename(&sema).is_none() {
36-
bail!("No references found at position")
37-
}
39+
let mut defs = find_definitions(&sema, syntax, position)?;
3840

41+
// TODO:
42+
// - empty case possible or already caught by `find_definitions`?
43+
// - is "just take the first" correct? If not, what do?
44+
let (name_like, _def) = defs.next().unwrap();
3945
let frange = sema.original_range(name_like.syntax());
4046
always!(frange.range.contains_inclusive(position.offset) && frange.file_id == position.file_id);
4147
Ok(RangeInfo::new(frange.range, ()))
@@ -61,20 +67,26 @@ pub(crate) fn rename(
6167
let source_file = sema.parse(position.file_id);
6268
let syntax = source_file.syntax();
6369

64-
let (_name_like, def) = find_definition(&sema, syntax, position)?;
65-
66-
if let Definition::Local(local) = def {
67-
if let Some(self_param) = local.as_self_param(sema.db) {
68-
cov_mark::hit!(rename_self_to_param);
69-
return rename_self_to_param(&sema, local, self_param, new_name);
70-
}
71-
if new_name == "self" {
72-
cov_mark::hit!(rename_to_self);
73-
return rename_to_self(&sema, local);
74-
}
75-
}
70+
let defs = find_definitions(&sema, syntax, position)?;
7671

77-
def.rename(&sema, new_name)
72+
let ops: RenameResult<Vec<SourceChange>> = defs
73+
.map(|(_namelike, def)| {
74+
if let Definition::Local(local) = def {
75+
if let Some(self_param) = local.as_self_param(sema.db) {
76+
cov_mark::hit!(rename_self_to_param);
77+
return rename_self_to_param(&sema, local, self_param, new_name);
78+
}
79+
if new_name == "self" {
80+
cov_mark::hit!(rename_to_self);
81+
return rename_to_self(&sema, local);
82+
}
83+
}
84+
def.rename(&sema, new_name)
85+
})
86+
.collect();
87+
ops?.into_iter()
88+
.reduce(|acc, elem| acc.merge(elem))
89+
.ok_or_else(|| format_err!("No references found at position"))
7890
}
7991

8092
/// Called by the client when it is about to rename a file.
@@ -91,59 +103,76 @@ pub(crate) fn will_rename_file(
91103
Some(change)
92104
}
93105

94-
fn find_definition(
106+
fn find_definitions(
95107
sema: &Semantics<RootDatabase>,
96108
syntax: &SyntaxNode,
97109
position: FilePosition,
98-
) -> RenameResult<(ast::NameLike, Definition)> {
99-
let name_like = sema
100-
.find_node_at_offset_with_descend::<ast::NameLike>(syntax, position.offset)
101-
.ok_or_else(|| format_err!("No references found at position"))?;
102-
103-
let def = match &name_like {
104-
// renaming aliases would rename the item being aliased as the HIR doesn't track aliases yet
105-
ast::NameLike::Name(name)
106-
if name.syntax().parent().map_or(false, |it| ast::Rename::can_cast(it.kind())) =>
107-
{
108-
bail!("Renaming aliases is currently unsupported")
109-
}
110-
ast::NameLike::Name(name) => NameClass::classify(sema, name).map(|class| match class {
111-
NameClass::Definition(it) | NameClass::ConstReference(it) => it,
112-
NameClass::PatFieldShorthand { local_def, field_ref: _ } => {
113-
Definition::Local(local_def)
114-
}
115-
}),
116-
ast::NameLike::NameRef(name_ref) => {
117-
if let Some(def) = NameRefClass::classify(sema, name_ref).map(|class| match class {
118-
NameRefClass::Definition(def) => def,
119-
NameRefClass::FieldShorthand { local_ref, field_ref: _ } => {
120-
Definition::Local(local_ref)
110+
) -> RenameResult<impl Iterator<Item = (ast::NameLike, Definition)>> {
111+
let symbols = sema
112+
.find_nodes_at_offset_with_descend::<ast::NameLike>(syntax, position.offset)
113+
.map(|name_like| {
114+
let res = match &name_like {
115+
// renaming aliases would rename the item being aliased as the HIR doesn't track aliases yet
116+
ast::NameLike::Name(name)
117+
if name
118+
.syntax()
119+
.parent()
120+
.map_or(false, |it| ast::Rename::can_cast(it.kind())) =>
121+
{
122+
bail!("Renaming aliases is currently unsupported")
121123
}
122-
}) {
123-
// if the name differs from the definitions name it has to be an alias
124-
if def.name(sema.db).map_or(false, |it| it.to_string() != name_ref.text()) {
125-
bail!("Renaming aliases is currently unsupported");
124+
ast::NameLike::Name(name) => NameClass::classify(sema, name)
125+
.map(|class| match class {
126+
NameClass::Definition(it) | NameClass::ConstReference(it) => it,
127+
NameClass::PatFieldShorthand { local_def, field_ref: _ } => {
128+
Definition::Local(local_def)
129+
}
130+
})
131+
.map(|def| (name_like.clone(), def))
132+
.ok_or_else(|| format_err!("No references found at position")),
133+
ast::NameLike::NameRef(name_ref) => NameRefClass::classify(sema, name_ref)
134+
.map(|class| match class {
135+
NameRefClass::Definition(def) => def,
136+
NameRefClass::FieldShorthand { local_ref, field_ref: _ } => {
137+
Definition::Local(local_ref)
138+
}
139+
})
140+
.and_then(|def| {
141+
// if the name differs from the definitions name it has to be an alias
142+
if def.name(sema.db).map_or(false, |it| it.to_string() != name_ref.text()) {
143+
None
144+
} else {
145+
Some((name_like.clone(), def))
146+
}
147+
})
148+
.ok_or_else(|| format_err!("Renaming aliases is currently unsupported")),
149+
ast::NameLike::Lifetime(lifetime) => {
150+
NameRefClass::classify_lifetime(sema, lifetime)
151+
.and_then(|class| match class {
152+
NameRefClass::Definition(def) => Some(def),
153+
_ => None,
154+
})
155+
.or_else(|| {
156+
NameClass::classify_lifetime(sema, lifetime).and_then(|it| match it {
157+
NameClass::Definition(it) => Some(it),
158+
_ => None,
159+
})
160+
})
161+
.map(|def| (name_like, def))
162+
.ok_or_else(|| format_err!("No references found at position"))
126163
}
127-
Some(def)
128-
} else {
129-
None
130-
}
131-
}
132-
ast::NameLike::Lifetime(lifetime) => NameRefClass::classify_lifetime(sema, lifetime)
133-
.and_then(|class| match class {
134-
NameRefClass::Definition(def) => Some(def),
135-
_ => None,
136-
})
137-
.or_else(|| {
138-
NameClass::classify_lifetime(sema, lifetime).and_then(|it| match it {
139-
NameClass::Definition(it) => Some(it),
140-
_ => None,
141-
})
142-
}),
143-
}
144-
.ok_or_else(|| format_err!("No references found at position"))?;
145-
146-
Ok((name_like, def))
164+
};
165+
res
166+
});
167+
168+
// TODO avoid collect() somehow?
169+
let v: RenameResult<Vec<(ast::NameLike, Definition)>> = symbols.collect();
170+
match v {
171+
// remove duplicates
172+
// TODO is "unique by `Definition`" correct?
173+
Ok(v) => Ok(v.into_iter().unique_by(|t| t.1)),
174+
Err(e) => Err(e),
175+
}
147176
}
148177

149178
fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameResult<SourceChange> {

crates/ide_db/src/source_change.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ impl SourceChange {
5454
pub fn get_source_edit(&self, file_id: FileId) -> Option<&TextEdit> {
5555
self.source_file_edits.get(&file_id)
5656
}
57+
58+
pub fn merge(mut self, other: SourceChange) -> SourceChange {
59+
self.extend(other.source_file_edits);
60+
self.extend(other.file_system_edits);
61+
self.is_snippet |= other.is_snippet; // TODO correct?
62+
self
63+
}
5764
}
5865

5966
impl Extend<(FileId, TextEdit)> for SourceChange {
@@ -62,6 +69,12 @@ impl Extend<(FileId, TextEdit)> for SourceChange {
6269
}
6370
}
6471

72+
impl Extend<FileSystemEdit> for SourceChange {
73+
fn extend<T: IntoIterator<Item = FileSystemEdit>>(&mut self, iter: T) {
74+
iter.into_iter().for_each(|edit| self.push_file_system_edit(edit));
75+
}
76+
}
77+
6578
impl From<FxHashMap<FileId, TextEdit>> for SourceChange {
6679
fn from(source_file_edits: FxHashMap<FileId, TextEdit>) -> SourceChange {
6780
SourceChange { source_file_edits, file_system_edits: Vec::new(), is_snippet: false }

0 commit comments

Comments
 (0)