Skip to content

Commit 5dcaf10

Browse files
bors[bot]zbsz
andauthored
Merge #4288
4288: Add rename self to parameter and back. r=zbsz a=zbsz This is a first stab at #3439 I liked the idea to do this as a rename instead of separate assist, so I tried implementing that. It mostly works, but I'm sure there are some cases that I missed, especially in regards to parameter type. Note: I'm playing with this this as a way to learn Rust and this project. So I'm sure it could be cleaner and put in better places`. Any suggestions? Co-authored-by: zbsz <zbigniewo@gmail.com>
2 parents cffa70b + d7d8bfc commit 5dcaf10

File tree

1 file changed

+206
-5
lines changed

1 file changed

+206
-5
lines changed

crates/ra_ide/src/references/rename.rs

Lines changed: 206 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@ use hir::{ModuleSource, Semantics};
44
use ra_db::{RelativePath, RelativePathBuf, SourceDatabaseExt};
55
use ra_ide_db::RootDatabase;
66
use ra_syntax::{
7-
algo::find_node_at_offset, ast, lex_single_valid_syntax_kind, AstNode, SyntaxKind, SyntaxNode,
7+
algo::find_node_at_offset, ast, ast::TypeAscriptionOwner, lex_single_valid_syntax_kind,
8+
AstNode, SyntaxKind, SyntaxNode, SyntaxToken,
89
};
910
use ra_text_edit::TextEdit;
11+
use std::convert::TryInto;
1012
use test_utils::tested_by;
1113

1214
use crate::{
1315
references::find_all_refs, FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind,
14-
SourceChange, SourceFileEdit, TextRange,
16+
SourceChange, SourceFileEdit, TextRange, TextSize,
1517
};
1618

1719
pub(crate) fn rename(
@@ -21,17 +23,21 @@ pub(crate) fn rename(
2123
) -> Option<RangeInfo<SourceChange>> {
2224
match lex_single_valid_syntax_kind(new_name)? {
2325
SyntaxKind::IDENT | SyntaxKind::UNDERSCORE => (),
26+
SyntaxKind::SELF_KW => return rename_to_self(db, position),
2427
_ => return None,
2528
}
2629

2730
let sema = Semantics::new(db);
2831
let source_file = sema.parse(position.file_id);
29-
if let Some((ast_name, ast_module)) =
30-
find_name_and_module_at_offset(source_file.syntax(), position)
31-
{
32+
let syntax = source_file.syntax();
33+
if let Some((ast_name, ast_module)) = find_name_and_module_at_offset(syntax, position) {
3234
let range = ast_name.syntax().text_range();
3335
rename_mod(&sema, &ast_name, &ast_module, position, new_name)
3436
.map(|info| RangeInfo::new(range, info))
37+
} else if let Some(self_token) =
38+
syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW)
39+
{
40+
rename_self_to_param(db, position, self_token, new_name)
3541
} else {
3642
rename_reference(sema.db, position, new_name)
3743
}
@@ -125,6 +131,112 @@ fn rename_mod(
125131
Some(SourceChange::from_edits("Rename", source_file_edits, file_system_edits))
126132
}
127133

134+
fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<SourceChange>> {
135+
let sema = Semantics::new(db);
136+
let source_file = sema.parse(position.file_id);
137+
let syn = source_file.syntax();
138+
139+
let fn_def = find_node_at_offset::<ast::FnDef>(syn, position.offset)?;
140+
let params = fn_def.param_list()?;
141+
if params.self_param().is_some() {
142+
return None; // method already has self param
143+
}
144+
let first_param = params.params().next()?;
145+
let mutable = match first_param.ascribed_type() {
146+
Some(ast::TypeRef::ReferenceType(rt)) => rt.mut_token().is_some(),
147+
_ => return None, // not renaming other types
148+
};
149+
150+
let RangeInfo { range, info: refs } = find_all_refs(db, position, None)?;
151+
152+
let param_range = first_param.syntax().text_range();
153+
let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs
154+
.into_iter()
155+
.partition(|reference| param_range.intersect(reference.file_range.range).is_some());
156+
157+
if param_ref.is_empty() {
158+
return None;
159+
}
160+
161+
let mut edits = usages
162+
.into_iter()
163+
.map(|reference| source_edit_from_reference(reference, "self"))
164+
.collect::<Vec<_>>();
165+
166+
edits.push(SourceFileEdit {
167+
file_id: position.file_id,
168+
edit: TextEdit::replace(
169+
param_range,
170+
String::from(if mutable { "&mut self" } else { "&self" }),
171+
),
172+
});
173+
174+
Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edits)))
175+
}
176+
177+
fn text_edit_from_self_param(
178+
syn: &SyntaxNode,
179+
self_param: &ast::SelfParam,
180+
new_name: &str,
181+
) -> Option<TextEdit> {
182+
fn target_type_name(impl_def: &ast::ImplDef) -> Option<String> {
183+
if let Some(ast::TypeRef::PathType(p)) = impl_def.target_type() {
184+
return Some(p.path()?.segment()?.name_ref()?.text().to_string());
185+
}
186+
None
187+
}
188+
189+
let impl_def =
190+
find_node_at_offset::<ast::ImplDef>(syn, self_param.syntax().text_range().start())?;
191+
let type_name = target_type_name(&impl_def)?;
192+
193+
let mut replacement_text = String::from(new_name);
194+
replacement_text.push_str(": ");
195+
replacement_text.push_str(self_param.mut_token().map_or("&", |_| "&mut "));
196+
replacement_text.push_str(type_name.as_str());
197+
198+
Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))
199+
}
200+
201+
fn rename_self_to_param(
202+
db: &RootDatabase,
203+
position: FilePosition,
204+
self_token: SyntaxToken,
205+
new_name: &str,
206+
) -> Option<RangeInfo<SourceChange>> {
207+
let sema = Semantics::new(db);
208+
let source_file = sema.parse(position.file_id);
209+
let syn = source_file.syntax();
210+
211+
let text = db.file_text(position.file_id);
212+
let fn_def = find_node_at_offset::<ast::FnDef>(syn, position.offset)?;
213+
let search_range = fn_def.syntax().text_range();
214+
215+
let mut edits: Vec<SourceFileEdit> = vec![];
216+
217+
for (idx, _) in text.match_indices("self") {
218+
let offset: TextSize = idx.try_into().unwrap();
219+
if !search_range.contains_inclusive(offset) {
220+
continue;
221+
}
222+
if let Some(ref usage) =
223+
syn.token_at_offset(offset).find(|t| t.kind() == SyntaxKind::SELF_KW)
224+
{
225+
let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) {
226+
text_edit_from_self_param(syn, self_param, new_name)?
227+
} else {
228+
TextEdit::replace(usage.text_range(), String::from(new_name))
229+
};
230+
edits.push(SourceFileEdit { file_id: position.file_id, edit });
231+
}
232+
}
233+
234+
let range = ast::SelfParam::cast(self_token.parent())
235+
.map_or(self_token.text_range(), |p| p.syntax().text_range());
236+
237+
Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edits)))
238+
}
239+
128240
fn rename_reference(
129241
db: &RootDatabase,
130242
position: FilePosition,
@@ -774,6 +886,95 @@ mod tests {
774886
);
775887
}
776888

889+
#[test]
890+
fn test_parameter_to_self() {
891+
test_rename(
892+
r#"
893+
struct Foo {
894+
i: i32,
895+
}
896+
897+
impl Foo {
898+
fn f(foo<|>: &mut Foo) -> i32 {
899+
foo.i
900+
}
901+
}
902+
"#,
903+
"self",
904+
r#"
905+
struct Foo {
906+
i: i32,
907+
}
908+
909+
impl Foo {
910+
fn f(&mut self) -> i32 {
911+
self.i
912+
}
913+
}
914+
"#,
915+
);
916+
}
917+
918+
#[test]
919+
fn test_self_to_parameter() {
920+
test_rename(
921+
r#"
922+
struct Foo {
923+
i: i32,
924+
}
925+
926+
impl Foo {
927+
fn f(&mut <|>self) -> i32 {
928+
self.i
929+
}
930+
}
931+
"#,
932+
"foo",
933+
r#"
934+
struct Foo {
935+
i: i32,
936+
}
937+
938+
impl Foo {
939+
fn f(foo: &mut Foo) -> i32 {
940+
foo.i
941+
}
942+
}
943+
"#,
944+
);
945+
}
946+
947+
#[test]
948+
fn test_self_in_path_to_parameter() {
949+
test_rename(
950+
r#"
951+
struct Foo {
952+
i: i32,
953+
}
954+
955+
impl Foo {
956+
fn f(&self) -> i32 {
957+
let self_var = 1;
958+
self<|>.i
959+
}
960+
}
961+
"#,
962+
"foo",
963+
r#"
964+
struct Foo {
965+
i: i32,
966+
}
967+
968+
impl Foo {
969+
fn f(foo: &Foo) -> i32 {
970+
let self_var = 1;
971+
foo.i
972+
}
973+
}
974+
"#,
975+
);
976+
}
977+
777978
fn test_rename(text: &str, new_name: &str, expected: &str) {
778979
let (analysis, position) = single_file_with_position(text);
779980
let source_change = analysis.rename(position, new_name).unwrap();

0 commit comments

Comments
 (0)