Skip to content

Commit ac1029f

Browse files
committedFeb 19, 2024
Auto merge of rust-lang#16489 - UserIsntAvailable:feat/use-import-alias, r=Veykril
feat!: create alias when renaming an import. ![gif](https://github.com/rust-lang/rust-analyzer/assets/57047985/c593d9a8-b8a0-4e13-9e50-a69c7d0d8749) Closes rust-lang#15858 Implemented: - [x] - Prevent using `reserved` keywords (e.g self) and `_`. - [x] - Rename other modules that might be referencing the import. - [x] - Fix "broken" tests. - [ ] - Rename **only** "direct" references. - [ ] - Test more cases. Future possibilities: 1. Also support `extern crate <name>` syntax. 2. Allow aliasing `self` when it is inside an `UseTreeList`. ~3. If import path already has an alias, "rename" the alias.~ ~[4. Create alias even if path is not the last path segment.](rust-lang/rust-analyzer#16489 (comment)
2 parents ff8fe52 + 6dd5dc1 commit ac1029f

File tree

1 file changed

+108
-15
lines changed

1 file changed

+108
-15
lines changed
 

‎crates/ide/src/rename.rs

+108-15
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use ide_db::{
99
base_db::{FileId, FileRange},
1010
defs::{Definition, NameClass, NameRefClass},
1111
rename::{bail, format_err, source_edit_from_references, IdentifierKind},
12+
source_change::SourceChangeBuilder,
1213
RootDatabase,
1314
};
1415
use itertools::Itertools;
@@ -90,24 +91,60 @@ pub(crate) fn rename(
9091
let syntax = source_file.syntax();
9192

9293
let defs = find_definitions(&sema, syntax, position)?;
94+
let alias_fallback = alias_fallback(syntax, position, new_name);
95+
96+
let ops: RenameResult<Vec<SourceChange>> = match alias_fallback {
97+
Some(_) => defs
98+
// FIXME: This can use the `ide_db::rename_reference` (or def.rename) method once we can
99+
// properly find "direct" usages/references.
100+
.map(|(.., def)| {
101+
match IdentifierKind::classify(new_name)? {
102+
IdentifierKind::Ident => (),
103+
IdentifierKind::Lifetime => {
104+
bail!("Cannot alias reference to a lifetime identifier")
105+
}
106+
IdentifierKind::Underscore => bail!("Cannot alias reference to `_`"),
107+
};
93108

94-
let ops: RenameResult<Vec<SourceChange>> = defs
95-
.map(|(.., def)| {
96-
if let Definition::Local(local) = def {
97-
if let Some(self_param) = local.as_self_param(sema.db) {
98-
cov_mark::hit!(rename_self_to_param);
99-
return rename_self_to_param(&sema, local, self_param, new_name);
100-
}
101-
if new_name == "self" {
102-
cov_mark::hit!(rename_to_self);
103-
return rename_to_self(&sema, local);
109+
let mut usages = def.usages(&sema).all();
110+
111+
// FIXME: hack - removes the usage that triggered this rename operation.
112+
match usages.references.get_mut(&position.file_id).and_then(|refs| {
113+
refs.iter()
114+
.position(|ref_| ref_.range.contains_inclusive(position.offset))
115+
.map(|idx| refs.remove(idx))
116+
}) {
117+
Some(_) => (),
118+
None => never!(),
119+
};
120+
121+
let mut source_change = SourceChange::default();
122+
source_change.extend(usages.iter().map(|(&file_id, refs)| {
123+
(file_id, source_edit_from_references(refs, def, new_name))
124+
}));
125+
126+
Ok(source_change)
127+
})
128+
.collect(),
129+
None => defs
130+
.map(|(.., def)| {
131+
if let Definition::Local(local) = def {
132+
if let Some(self_param) = local.as_self_param(sema.db) {
133+
cov_mark::hit!(rename_self_to_param);
134+
return rename_self_to_param(&sema, local, self_param, new_name);
135+
}
136+
if new_name == "self" {
137+
cov_mark::hit!(rename_to_self);
138+
return rename_to_self(&sema, local);
139+
}
104140
}
105-
}
106-
def.rename(&sema, new_name)
107-
})
108-
.collect();
141+
def.rename(&sema, new_name)
142+
})
143+
.collect(),
144+
};
109145

110146
ops?.into_iter()
147+
.chain(alias_fallback)
111148
.reduce(|acc, elem| acc.merge(elem))
112149
.ok_or_else(|| format_err!("No references found at position"))
113150
}
@@ -130,6 +167,38 @@ pub(crate) fn will_rename_file(
130167
Some(change)
131168
}
132169

170+
// FIXME: Should support `extern crate`.
171+
fn alias_fallback(
172+
syntax: &SyntaxNode,
173+
FilePosition { file_id, offset }: FilePosition,
174+
new_name: &str,
175+
) -> Option<SourceChange> {
176+
let use_tree = syntax
177+
.token_at_offset(offset)
178+
.flat_map(|syntax| syntax.parent_ancestors())
179+
.find_map(ast::UseTree::cast)?;
180+
181+
let last_path_segment = use_tree.path()?.segments().last()?.name_ref()?;
182+
if !last_path_segment.syntax().text_range().contains_inclusive(offset) {
183+
return None;
184+
};
185+
186+
let mut builder = SourceChangeBuilder::new(file_id);
187+
188+
match use_tree.rename() {
189+
Some(rename) => {
190+
let offset = rename.syntax().text_range();
191+
builder.replace(offset, format!("as {new_name}"));
192+
}
193+
None => {
194+
let offset = use_tree.syntax().text_range().end();
195+
builder.insert(offset, format!(" as {new_name}"));
196+
}
197+
}
198+
199+
Some(builder.finish())
200+
}
201+
133202
fn find_definitions(
134203
sema: &Semantics<'_, RootDatabase>,
135204
syntax: &SyntaxNode,
@@ -2626,7 +2695,8 @@ use qux as frob;
26262695
//- /lib.rs crate:lib new_source_root:library
26272696
pub struct S;
26282697
//- /main.rs crate:main deps:lib new_source_root:local
2629-
use lib::S$0;
2698+
use lib::S;
2699+
fn main() { let _: S$0; }
26302700
"#,
26312701
"error: Cannot rename a non-local definition",
26322702
);
@@ -2686,4 +2756,27 @@ fn test() {
26862756
"#,
26872757
);
26882758
}
2759+
2760+
#[test]
2761+
fn rename_path_inside_use_tree() {
2762+
check(
2763+
"Baz",
2764+
r#"
2765+
mod foo { pub struct Foo; }
2766+
mod bar { use super::Foo; }
2767+
2768+
use foo::Foo$0;
2769+
2770+
fn main() { let _: Foo; }
2771+
"#,
2772+
r#"
2773+
mod foo { pub struct Foo; }
2774+
mod bar { use super::Baz; }
2775+
2776+
use foo::Foo as Baz;
2777+
2778+
fn main() { let _: Baz; }
2779+
"#,
2780+
)
2781+
}
26892782
}

0 commit comments

Comments
 (0)
Please sign in to comment.