aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/references
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src/references')
-rw-r--r--crates/ide/src/references/rename.rs932
1 files changed, 537 insertions, 395 deletions
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 4df189c98..22ddeeae3 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -1,25 +1,23 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2use std::fmt::{self, Display}; 2use std::fmt::{self, Display};
3 3
4use hir::{Module, ModuleDef, ModuleSource, Semantics}; 4use either::Either;
5use hir::{HasSource, InFile, Module, ModuleDef, ModuleSource, Semantics};
5use ide_db::{ 6use ide_db::{
6 base_db::{AnchoredPathBuf, FileId, FileRange}, 7 base_db::{AnchoredPathBuf, FileId},
7 defs::{Definition, NameClass, NameRefClass}, 8 defs::{Definition, NameClass, NameRefClass},
8 search::FileReference, 9 search::FileReference,
9 RootDatabase, 10 RootDatabase,
10}; 11};
12use stdx::never;
11use syntax::{ 13use syntax::{
12 algo::find_node_at_offset,
13 ast::{self, NameOwner}, 14 ast::{self, NameOwner},
14 lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, T, 15 lex_single_syntax_kind, AstNode, SyntaxKind, SyntaxNode, T,
15}; 16};
16use test_utils::mark; 17use test_utils::mark;
17use text_edit::TextEdit; 18use text_edit::TextEdit;
18 19
19use crate::{ 20use crate::{display::TryToNav, FilePosition, FileSystemEdit, RangeInfo, SourceChange, TextRange};
20 FilePosition, FileSystemEdit, RangeInfo, ReferenceKind, ReferenceSearchResult, SourceChange,
21 TextRange,
22};
23 21
24type RenameResult<T> = Result<T, RenameError>; 22type RenameResult<T> = Result<T, RenameError>;
25#[derive(Debug)] 23#[derive(Debug)]
@@ -40,6 +38,8 @@ macro_rules! bail {
40 ($($tokens:tt)*) => {return Err(format_err!($($tokens)*))} 38 ($($tokens:tt)*) => {return Err(format_err!($($tokens)*))}
41} 39}
42 40
41/// Prepares a rename. The sole job of this function is to return the TextRange of the thing that is
42/// being targeted for a rename.
43pub(crate) fn prepare_rename( 43pub(crate) fn prepare_rename(
44 db: &RootDatabase, 44 db: &RootDatabase,
45 position: FilePosition, 45 position: FilePosition,
@@ -47,20 +47,32 @@ pub(crate) fn prepare_rename(
47 let sema = Semantics::new(db); 47 let sema = Semantics::new(db);
48 let source_file = sema.parse(position.file_id); 48 let source_file = sema.parse(position.file_id);
49 let syntax = source_file.syntax(); 49 let syntax = source_file.syntax();
50 if let Some(module) = find_module_at_offset(&sema, position, syntax) { 50 let range = match &sema
51 rename_mod(&sema, position, module, "dummy") 51 .find_node_at_offset_with_descend(&syntax, position.offset)
52 } else { 52 .ok_or_else(|| format_err!("No references found at position"))?
53 let RangeInfo { range, .. } = find_all_refs(&sema, position)?; 53 {
54 Ok(RangeInfo::new(range, SourceChange::default())) 54 ast::NameLike::Name(it) => it.syntax(),
55 } 55 ast::NameLike::NameRef(it) => it.syntax(),
56 .map(|info| RangeInfo::new(info.range, ())) 56 ast::NameLike::Lifetime(it) => it.syntax(),
57} 57 }
58 58 .text_range();
59 Ok(RangeInfo::new(range, ()))
60}
61
62// Feature: Rename
63//
64// Renames the item below the cursor and all of its references
65//
66// |===
67// | Editor | Shortcut
68//
69// | VS Code | kbd:[F2]
70// |===
59pub(crate) fn rename( 71pub(crate) fn rename(
60 db: &RootDatabase, 72 db: &RootDatabase,
61 position: FilePosition, 73 position: FilePosition,
62 new_name: &str, 74 new_name: &str,
63) -> RenameResult<RangeInfo<SourceChange>> { 75) -> RenameResult<SourceChange> {
64 let sema = Semantics::new(db); 76 let sema = Semantics::new(db);
65 rename_with_semantics(&sema, position, new_name) 77 rename_with_semantics(&sema, position, new_name)
66} 78}
@@ -69,14 +81,14 @@ pub(crate) fn rename_with_semantics(
69 sema: &Semantics<RootDatabase>, 81 sema: &Semantics<RootDatabase>,
70 position: FilePosition, 82 position: FilePosition,
71 new_name: &str, 83 new_name: &str,
72) -> RenameResult<RangeInfo<SourceChange>> { 84) -> RenameResult<SourceChange> {
73 let source_file = sema.parse(position.file_id); 85 let source_file = sema.parse(position.file_id);
74 let syntax = source_file.syntax(); 86 let syntax = source_file.syntax();
75 87
76 if let Some(module) = find_module_at_offset(&sema, position, syntax) { 88 let def = find_definition(sema, syntax, position)?;
77 rename_mod(&sema, position, module, new_name) 89 match def {
78 } else { 90 Definition::ModuleDef(ModuleDef::Module(module)) => rename_mod(&sema, module, new_name),
79 rename_reference(&sema, position, new_name) 91 def => rename_reference(sema, def, new_name),
80 } 92 }
81} 93}
82 94
@@ -87,12 +99,7 @@ pub(crate) fn will_rename_file(
87) -> Option<SourceChange> { 99) -> Option<SourceChange> {
88 let sema = Semantics::new(db); 100 let sema = Semantics::new(db);
89 let module = sema.to_module_def(file_id)?; 101 let module = sema.to_module_def(file_id)?;
90 102 let mut change = rename_mod(&sema, module, new_name_stem).ok()?;
91 let decl = module.declaration_source(db)?;
92 let range = decl.value.name()?.syntax().text_range();
93
94 let position = FilePosition { file_id: decl.file_id.original_file(db), offset: range.start() };
95 let mut change = rename_mod(&sema, position, module, new_name_stem).ok()?.info;
96 change.file_system_edits.clear(); 103 change.file_system_edits.clear();
97 Some(change) 104 Some(change)
98} 105}
@@ -124,179 +131,232 @@ fn check_identifier(new_name: &str) -> RenameResult<IdentifierKind> {
124 } 131 }
125} 132}
126 133
127fn find_module_at_offset( 134fn find_definition(
128 sema: &Semantics<RootDatabase>, 135 sema: &Semantics<RootDatabase>,
129 position: FilePosition,
130 syntax: &SyntaxNode, 136 syntax: &SyntaxNode,
131) -> Option<Module> {
132 let ident = syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::IDENT)?;
133
134 let module = match_ast! {
135 match (ident.parent()) {
136 ast::NameRef(name_ref) => {
137 match NameRefClass::classify(sema, &name_ref)? {
138 NameRefClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module,
139 _ => return None,
140 }
141 },
142 ast::Name(name) => {
143 match NameClass::classify(&sema, &name)? {
144 NameClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module,
145 _ => return None,
146 }
147 },
148 _ => return None,
149 }
150 };
151
152 Some(module)
153}
154
155fn find_all_refs(
156 sema: &Semantics<RootDatabase>,
157 position: FilePosition, 137 position: FilePosition,
158) -> RenameResult<RangeInfo<ReferenceSearchResult>> { 138) -> RenameResult<Definition> {
159 crate::references::find_all_refs(sema, position, None) 139 match sema
160 .ok_or_else(|| format_err!("No references found at position")) 140 .find_node_at_offset_with_descend(syntax, position.offset)
141 .ok_or_else(|| format_err!("No references found at position"))?
142 {
143 // renaming aliases would rename the item being aliased as the HIR doesn't track aliases yet
144 ast::NameLike::Name(name)
145 if name.syntax().parent().map_or(false, |it| ast::Rename::can_cast(it.kind())) =>
146 {
147 bail!("Renaming aliases is currently unsupported")
148 }
149 ast::NameLike::Name(name) => {
150 NameClass::classify(sema, &name).map(|class| class.referenced_or_defined(sema.db))
151 }
152 ast::NameLike::NameRef(name_ref) => {
153 NameRefClass::classify(sema, &name_ref).map(|class| class.referenced(sema.db))
154 }
155 ast::NameLike::Lifetime(lifetime) => NameRefClass::classify_lifetime(sema, &lifetime)
156 .map(|class| NameRefClass::referenced(class, sema.db))
157 .or_else(|| {
158 NameClass::classify_lifetime(sema, &lifetime)
159 .map(|it| it.referenced_or_defined(sema.db))
160 }),
161 }
162 .ok_or_else(|| format_err!("No references found at position"))
161} 163}
162 164
163fn source_edit_from_references( 165fn source_edit_from_references(
164 sema: &Semantics<RootDatabase>, 166 _sema: &Semantics<RootDatabase>,
165 file_id: FileId, 167 file_id: FileId,
166 references: &[FileReference], 168 references: &[FileReference],
169 def: Definition,
167 new_name: &str, 170 new_name: &str,
168) -> (FileId, TextEdit) { 171) -> (FileId, TextEdit) {
169 let mut edit = TextEdit::builder(); 172 let mut edit = TextEdit::builder();
170 for reference in references { 173 for reference in references {
171 let mut replacement_text = String::new(); 174 let (range, replacement) = match &reference.name {
172 let range = match reference.kind { 175 // if the ranges differ then the node is inside a macro call, we can't really attempt
173 ReferenceKind::FieldShorthandForField => { 176 // to make special rewrites like shorthand syntax and such, so just rename the node in
174 mark::hit!(test_rename_struct_field_for_shorthand); 177 // the macro input
175 replacement_text.push_str(new_name); 178 ast::NameLike::NameRef(name_ref)
176 replacement_text.push_str(": "); 179 if name_ref.syntax().text_range() == reference.range =>
177 TextRange::new(reference.range.start(), reference.range.start()) 180 {
178 } 181 source_edit_from_name_ref(name_ref, new_name, def)
179 ReferenceKind::FieldShorthandForLocal => {
180 mark::hit!(test_rename_local_for_field_shorthand);
181 replacement_text.push_str(": ");
182 replacement_text.push_str(new_name);
183 TextRange::new(reference.range.end(), reference.range.end())
184 } 182 }
185 ReferenceKind::RecordFieldExprOrPat => { 183 ast::NameLike::Name(name) if name.syntax().text_range() == reference.range => {
186 mark::hit!(test_rename_field_expr_pat); 184 source_edit_from_name(name, new_name)
187 replacement_text.push_str(new_name);
188 edit_text_range_for_record_field_expr_or_pat(
189 sema,
190 FileRange { file_id, range: reference.range },
191 new_name,
192 )
193 } 185 }
194 _ => { 186 _ => None,
195 replacement_text.push_str(new_name); 187 }
196 reference.range 188 .unwrap_or_else(|| (reference.range, new_name.to_string()));
197 } 189 edit.replace(range, replacement);
198 };
199 edit.replace(range, replacement_text);
200 } 190 }
201 (file_id, edit.finish()) 191 (file_id, edit.finish())
202} 192}
203 193
204fn edit_text_range_for_record_field_expr_or_pat( 194fn source_edit_from_name(name: &ast::Name, new_name: &str) -> Option<(TextRange, String)> {
205 sema: &Semantics<RootDatabase>, 195 if let Some(_) = ast::RecordPatField::for_field_name(name) {
206 file_range: FileRange, 196 if let Some(ident_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) {
197 return Some((
198 TextRange::empty(ident_pat.syntax().text_range().start()),
199 format!("{}: ", new_name),
200 ));
201 }
202 }
203 None
204}
205
206fn source_edit_from_name_ref(
207 name_ref: &ast::NameRef,
207 new_name: &str, 208 new_name: &str,
208) -> TextRange { 209 def: Definition,
209 let source_file = sema.parse(file_range.file_id); 210) -> Option<(TextRange, String)> {
210 let file_syntax = source_file.syntax(); 211 if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) {
211 let original_range = file_range.range; 212 let rcf_name_ref = record_field.name_ref();
212 213 let rcf_expr = record_field.expr();
213 syntax::algo::find_node_at_range::<ast::RecordExprField>(file_syntax, original_range) 214 match (rcf_name_ref, rcf_expr.and_then(|it| it.name_ref())) {
214 .and_then(|field_expr| match field_expr.expr().and_then(|e| e.name_ref()) { 215 // field: init-expr, check if we can use a field init shorthand
215 Some(name) if &name.to_string() == new_name => Some(field_expr.syntax().text_range()), 216 (Some(field_name), Some(init)) => {
216 _ => None, 217 if field_name == *name_ref {
217 }) 218 if init.text() == new_name {
218 .or_else(|| { 219 mark::hit!(test_rename_field_put_init_shorthand);
219 syntax::algo::find_node_at_range::<ast::RecordPatField>(file_syntax, original_range) 220 // same names, we can use a shorthand here instead.
220 .and_then(|field_pat| match field_pat.pat() { 221 // we do not want to erase attributes hence this range start
221 Some(ast::Pat::IdentPat(pat)) 222 let s = field_name.syntax().text_range().start();
222 if pat.name().map(|n| n.to_string()).as_deref() == Some(new_name) => 223 let e = record_field.syntax().text_range().end();
223 { 224 return Some((TextRange::new(s, e), new_name.to_owned()));
224 Some(field_pat.syntax().text_range())
225 } 225 }
226 _ => None, 226 } else if init == *name_ref {
227 }) 227 if field_name.text() == new_name {
228 }) 228 mark::hit!(test_rename_local_put_init_shorthand);
229 .unwrap_or(original_range) 229 // same names, we can use a shorthand here instead.
230 // we do not want to erase attributes hence this range start
231 let s = field_name.syntax().text_range().start();
232 let e = record_field.syntax().text_range().end();
233 return Some((TextRange::new(s, e), new_name.to_owned()));
234 }
235 }
236 None
237 }
238 // init shorthand
239 // FIXME: instead of splitting the shorthand, recursively trigger a rename of the
240 // other name https://github.com/rust-analyzer/rust-analyzer/issues/6547
241 (None, Some(_)) if matches!(def, Definition::Field(_)) => {
242 mark::hit!(test_rename_field_in_field_shorthand);
243 let s = name_ref.syntax().text_range().start();
244 Some((TextRange::empty(s), format!("{}: ", new_name)))
245 }
246 (None, Some(_)) if matches!(def, Definition::Local(_)) => {
247 mark::hit!(test_rename_local_in_field_shorthand);
248 let s = name_ref.syntax().text_range().end();
249 Some((TextRange::empty(s), format!(": {}", new_name)))
250 }
251 _ => None,
252 }
253 } else if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) {
254 let rcf_name_ref = record_field.name_ref();
255 let rcf_pat = record_field.pat();
256 match (rcf_name_ref, rcf_pat) {
257 // field: rename
258 (Some(field_name), Some(ast::Pat::IdentPat(pat))) if field_name == *name_ref => {
259 // field name is being renamed
260 if pat.name().map_or(false, |it| it.text() == new_name) {
261 mark::hit!(test_rename_field_put_init_shorthand_pat);
262 // same names, we can use a shorthand here instead/
263 // we do not want to erase attributes hence this range start
264 let s = field_name.syntax().text_range().start();
265 let e = record_field.syntax().text_range().end();
266 Some((TextRange::new(s, e), pat.to_string()))
267 } else {
268 None
269 }
270 }
271 _ => None,
272 }
273 } else {
274 None
275 }
230} 276}
231 277
232fn rename_mod( 278fn rename_mod(
233 sema: &Semantics<RootDatabase>, 279 sema: &Semantics<RootDatabase>,
234 position: FilePosition,
235 module: Module, 280 module: Module,
236 new_name: &str, 281 new_name: &str,
237) -> RenameResult<RangeInfo<SourceChange>> { 282) -> RenameResult<SourceChange> {
238 if IdentifierKind::Ident != check_identifier(new_name)? { 283 if IdentifierKind::Ident != check_identifier(new_name)? {
239 bail!("Invalid name `{0}`: cannot rename module to {0}", new_name); 284 bail!("Invalid name `{0}`: cannot rename module to {0}", new_name);
240 } 285 }
241 286
242 let mut source_change = SourceChange::default(); 287 let mut source_change = SourceChange::default();
243 288
244 let src = module.definition_source(sema.db); 289 let InFile { file_id, value: def_source } = module.definition_source(sema.db);
245 let file_id = src.file_id.original_file(sema.db); 290 let file_id = file_id.original_file(sema.db);
246 match src.value { 291 if let ModuleSource::SourceFile(..) = def_source {
247 ModuleSource::SourceFile(..) => { 292 // mod is defined in path/to/dir/mod.rs
248 // mod is defined in path/to/dir/mod.rs 293 let path = if module.is_mod_rs(sema.db) {
249 let path = if module.is_mod_rs(sema.db) { 294 format!("../{}/mod.rs", new_name)
250 format!("../{}/mod.rs", new_name) 295 } else {
251 } else { 296 format!("{}.rs", new_name)
252 format!("{}.rs", new_name) 297 };
253 }; 298 let dst = AnchoredPathBuf { anchor: file_id, path };
254 let dst = AnchoredPathBuf { anchor: file_id, path }; 299 let move_file = FileSystemEdit::MoveFile { src: file_id, dst };
255 let move_file = FileSystemEdit::MoveFile { src: file_id, dst }; 300 source_change.push_file_system_edit(move_file);
256 source_change.push_file_system_edit(move_file); 301 }
257 } 302
258 ModuleSource::Module(..) => {} 303 if let Some(InFile { file_id, value: decl_source }) = module.declaration_source(sema.db) {
259 } 304 let file_id = file_id.original_file(sema.db);
260 305 match decl_source.name() {
261 if let Some(src) = module.declaration_source(sema.db) { 306 Some(name) => source_change.insert_source_edit(
262 let file_id = src.file_id.original_file(sema.db); 307 file_id,
263 let name = src.value.name().unwrap(); 308 TextEdit::replace(name.syntax().text_range(), new_name.to_string()),
264 source_change.insert_source_edit( 309 ),
265 file_id, 310 _ => unreachable!(),
266 TextEdit::replace(name.syntax().text_range(), new_name.into()), 311 };
267 );
268 } 312 }
269 313 let def = Definition::ModuleDef(ModuleDef::Module(module));
270 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; 314 let usages = def.usages(sema).all();
271 let ref_edits = refs.references().iter().map(|(&file_id, references)| { 315 let ref_edits = usages.iter().map(|(&file_id, references)| {
272 source_edit_from_references(sema, file_id, references, new_name) 316 source_edit_from_references(sema, file_id, references, def, new_name)
273 }); 317 });
274 source_change.extend(ref_edits); 318 source_change.extend(ref_edits);
275 319
276 Ok(RangeInfo::new(range, source_change)) 320 Ok(source_change)
277} 321}
278 322
279fn rename_to_self( 323fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameResult<SourceChange> {
280 sema: &Semantics<RootDatabase>, 324 if never!(local.is_self(sema.db)) {
281 position: FilePosition, 325 bail!("rename_to_self invoked on self");
282) -> Result<RangeInfo<SourceChange>, RenameError> { 326 }
283 let source_file = sema.parse(position.file_id); 327
284 let syn = source_file.syntax(); 328 let fn_def = match local.parent(sema.db) {
329 hir::DefWithBody::Function(func) => func,
330 _ => bail!("Cannot rename non-param local to self"),
331 };
285 332
286 let (fn_def, fn_ast) = find_node_at_offset::<ast::Fn>(syn, position.offset) 333 // FIXME: reimplement this on the hir instead
287 .and_then(|fn_ast| sema.to_def(&fn_ast).zip(Some(fn_ast))) 334 // as of the time of this writing params in hir don't keep their names
288 .ok_or_else(|| format_err!("No surrounding method declaration found"))?; 335 let fn_ast = fn_def
289 let param_range = fn_ast 336 .source(sema.db)
337 .ok_or_else(|| format_err!("Cannot rename non-param local to self"))?
338 .value;
339
340 let first_param_range = fn_ast
290 .param_list() 341 .param_list()
291 .and_then(|p| p.params().next()) 342 .and_then(|p| p.params().next())
292 .ok_or_else(|| format_err!("Method has no parameters"))? 343 .ok_or_else(|| format_err!("Method has no parameters"))?
293 .syntax() 344 .syntax()
294 .text_range(); 345 .text_range();
295 if !param_range.contains(position.offset) { 346 let InFile { file_id, value: local_source } = local.source(sema.db);
296 bail!("Only the first parameter can be self"); 347 match local_source {
348 either::Either::Left(pat)
349 if !first_param_range.contains_range(pat.syntax().text_range()) =>
350 {
351 bail!("Only the first parameter can be self");
352 }
353 _ => (),
297 } 354 }
298 355
299 let impl_block = find_node_at_offset::<ast::Impl>(syn, position.offset) 356 let impl_block = fn_ast
357 .syntax()
358 .ancestors()
359 .find_map(|node| ast::Impl::cast(node))
300 .and_then(|def| sema.to_def(&def)) 360 .and_then(|def| sema.to_def(&def))
301 .ok_or_else(|| format_err!("No impl block found for function"))?; 361 .ok_or_else(|| format_err!("No impl block found for function"))?;
302 if fn_def.self_param(sema.db).is_some() { 362 if fn_def.self_param(sema.db).is_some() {
@@ -320,25 +380,21 @@ fn rename_to_self(
320 bail!("Parameter type differs from impl block type"); 380 bail!("Parameter type differs from impl block type");
321 } 381 }
322 382
323 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; 383 let def = Definition::Local(local);
324 384 let usages = def.usages(sema).all();
325 let mut source_change = SourceChange::default(); 385 let mut source_change = SourceChange::default();
326 source_change.extend(refs.references().iter().map(|(&file_id, references)| { 386 source_change.extend(usages.iter().map(|(&file_id, references)| {
327 source_edit_from_references(sema, file_id, references, "self") 387 source_edit_from_references(sema, file_id, references, def, "self")
328 })); 388 }));
329 source_change.insert_source_edit( 389 source_change.insert_source_edit(
330 position.file_id, 390 file_id.original_file(sema.db),
331 TextEdit::replace(param_range, String::from(self_param)), 391 TextEdit::replace(first_param_range, String::from(self_param)),
332 ); 392 );
333 393
334 Ok(RangeInfo::new(range, source_change)) 394 Ok(source_change)
335} 395}
336 396
337fn text_edit_from_self_param( 397fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Option<TextEdit> {
338 syn: &SyntaxNode,
339 self_param: &ast::SelfParam,
340 new_name: &str,
341) -> Option<TextEdit> {
342 fn target_type_name(impl_def: &ast::Impl) -> Option<String> { 398 fn target_type_name(impl_def: &ast::Impl) -> Option<String> {
343 if let Some(ast::Type::PathType(p)) = impl_def.self_ty() { 399 if let Some(ast::Type::PathType(p)) = impl_def.self_ty() {
344 return Some(p.path()?.segment()?.name_ref()?.text().to_string()); 400 return Some(p.path()?.segment()?.name_ref()?.text().to_string());
@@ -346,7 +402,7 @@ fn text_edit_from_self_param(
346 None 402 None
347 } 403 }
348 404
349 let impl_def = find_node_at_offset::<ast::Impl>(syn, self_param.syntax().text_range().start())?; 405 let impl_def = self_param.syntax().ancestors().find_map(|it| ast::Impl::cast(it))?;
350 let type_name = target_type_name(&impl_def)?; 406 let type_name = target_type_name(&impl_def)?;
351 407
352 let mut replacement_text = String::from(new_name); 408 let mut replacement_text = String::from(new_name);
@@ -363,94 +419,119 @@ fn text_edit_from_self_param(
363 419
364fn rename_self_to_param( 420fn rename_self_to_param(
365 sema: &Semantics<RootDatabase>, 421 sema: &Semantics<RootDatabase>,
366 position: FilePosition, 422 local: hir::Local,
367 new_name: &str, 423 new_name: &str,
368 ident_kind: IdentifierKind, 424 identifier_kind: IdentifierKind,
369 range: TextRange, 425) -> RenameResult<SourceChange> {
370 refs: ReferenceSearchResult, 426 let (file_id, self_param) = match local.source(sema.db) {
371) -> Result<RangeInfo<SourceChange>, RenameError> { 427 InFile { file_id, value: Either::Right(self_param) } => (file_id, self_param),
372 match ident_kind { 428 _ => {
373 IdentifierKind::Lifetime => bail!("Invalid name `{}`: not an identifier", new_name), 429 never!(true, "rename_self_to_param invoked on a non-self local");
374 IdentifierKind::ToSelf => { 430 bail!("rename_self_to_param invoked on a non-self local");
375 // no-op
376 mark::hit!(rename_self_to_self);
377 return Ok(RangeInfo::new(range, SourceChange::default()));
378 } 431 }
379 _ => (), 432 };
380 }
381 let source_file = sema.parse(position.file_id);
382 let syn = source_file.syntax();
383
384 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)
385 .ok_or_else(|| format_err!("No surrounding method declaration found"))?;
386
387 let mut source_change = SourceChange::default();
388 if let Some(self_param) = fn_def.param_list().and_then(|it| it.self_param()) {
389 if self_param
390 .syntax()
391 .text_range()
392 .contains_range(refs.declaration().nav.focus_or_full_range())
393 {
394 let edit = text_edit_from_self_param(syn, &self_param, new_name)
395 .ok_or_else(|| format_err!("No target type found"))?;
396 source_change.insert_source_edit(position.file_id, edit);
397
398 source_change.extend(refs.references().iter().map(|(&file_id, references)| {
399 source_edit_from_references(sema, file_id, &references, new_name)
400 }));
401
402 if source_change.source_file_edits.len() > 1 && ident_kind == IdentifierKind::Underscore
403 {
404 bail!("Cannot rename reference to `_` as it is being referenced multiple times");
405 }
406 433
407 return Ok(RangeInfo::new(range, source_change)); 434 let def = Definition::Local(local);
408 } 435 let usages = def.usages(sema).all();
436 let edit = text_edit_from_self_param(&self_param, new_name)
437 .ok_or_else(|| format_err!("No target type found"))?;
438 if usages.len() > 1 && identifier_kind == IdentifierKind::Underscore {
439 bail!("Cannot rename reference to `_` as it is being referenced multiple times");
409 } 440 }
410 Err(format_err!("Method has no self param")) 441 let mut source_change = SourceChange::default();
442 source_change.insert_source_edit(file_id.original_file(sema.db), edit);
443 source_change.extend(usages.iter().map(|(&file_id, references)| {
444 source_edit_from_references(sema, file_id, &references, def, new_name)
445 }));
446 Ok(source_change)
411} 447}
412 448
413fn rename_reference( 449fn rename_reference(
414 sema: &Semantics<RootDatabase>, 450 sema: &Semantics<RootDatabase>,
415 position: FilePosition, 451 def: Definition,
416 new_name: &str, 452 new_name: &str,
417) -> Result<RangeInfo<SourceChange>, RenameError> { 453) -> RenameResult<SourceChange> {
418 let ident_kind = check_identifier(new_name)?; 454 let ident_kind = check_identifier(new_name)?;
419 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
420 455
421 match (ident_kind, &refs.declaration.kind) { 456 let def_is_lbl_or_lt = matches!(
422 (IdentifierKind::ToSelf, ReferenceKind::Lifetime) 457 def,
423 | (IdentifierKind::Underscore, ReferenceKind::Lifetime) 458 Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_)
424 | (IdentifierKind::Ident, ReferenceKind::Lifetime) => { 459 );
460 match (ident_kind, def) {
461 (IdentifierKind::ToSelf, _)
462 | (IdentifierKind::Underscore, _)
463 | (IdentifierKind::Ident, _)
464 if def_is_lbl_or_lt =>
465 {
425 mark::hit!(rename_not_a_lifetime_ident_ref); 466 mark::hit!(rename_not_a_lifetime_ident_ref);
426 bail!("Invalid name `{}`: not a lifetime identifier", new_name) 467 bail!("Invalid name `{}`: not a lifetime identifier", new_name)
427 } 468 }
428 (IdentifierKind::Lifetime, ReferenceKind::Lifetime) => mark::hit!(rename_lifetime), 469 (IdentifierKind::Lifetime, _) if def_is_lbl_or_lt => mark::hit!(rename_lifetime),
429 (IdentifierKind::Lifetime, _) => { 470 (IdentifierKind::Lifetime, _) => {
430 mark::hit!(rename_not_an_ident_ref); 471 mark::hit!(rename_not_an_ident_ref);
431 bail!("Invalid name `{}`: not an identifier", new_name) 472 bail!("Invalid name `{}`: not an identifier", new_name)
432 } 473 }
433 (_, ReferenceKind::SelfParam) => { 474 (IdentifierKind::ToSelf, Definition::Local(local)) if local.is_self(sema.db) => {
475 // no-op
476 mark::hit!(rename_self_to_self);
477 return Ok(SourceChange::default());
478 }
479 (ident_kind, Definition::Local(local)) if local.is_self(sema.db) => {
434 mark::hit!(rename_self_to_param); 480 mark::hit!(rename_self_to_param);
435 return rename_self_to_param(sema, position, new_name, ident_kind, range, refs); 481 return rename_self_to_param(sema, local, new_name, ident_kind);
436 } 482 }
437 (IdentifierKind::ToSelf, _) => { 483 (IdentifierKind::ToSelf, Definition::Local(local)) => {
438 mark::hit!(rename_to_self); 484 mark::hit!(rename_to_self);
439 return rename_to_self(sema, position); 485 return rename_to_self(sema, local);
440 }
441 (IdentifierKind::Underscore, _) if !refs.references.is_empty() => {
442 mark::hit!(rename_underscore_multiple);
443 bail!("Cannot rename reference to `_` as it is being referenced multiple times")
444 } 486 }
487 (IdentifierKind::ToSelf, _) => bail!("Invalid name `{}`: not an identifier", new_name),
445 (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident), 488 (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident),
446 } 489 }
447 490
491 let usages = def.usages(sema).all();
492 if !usages.is_empty() && ident_kind == IdentifierKind::Underscore {
493 mark::hit!(rename_underscore_multiple);
494 bail!("Cannot rename reference to `_` as it is being referenced multiple times");
495 }
448 let mut source_change = SourceChange::default(); 496 let mut source_change = SourceChange::default();
449 source_change.extend(refs.into_iter().map(|(file_id, references)| { 497 source_change.extend(usages.iter().map(|(&file_id, references)| {
450 source_edit_from_references(sema, file_id, &references, new_name) 498 source_edit_from_references(sema, file_id, &references, def, new_name)
451 })); 499 }));
452 500
453 Ok(RangeInfo::new(range, source_change)) 501 let (file_id, edit) = source_edit_from_def(sema, def, new_name)?;
502 source_change.insert_source_edit(file_id, edit);
503 Ok(source_change)
504}
505
506fn source_edit_from_def(
507 sema: &Semantics<RootDatabase>,
508 def: Definition,
509 new_name: &str,
510) -> RenameResult<(FileId, TextEdit)> {
511 let nav = def.try_to_nav(sema.db).unwrap();
512
513 let mut replacement_text = String::new();
514 let mut repl_range = nav.focus_or_full_range();
515 if let Definition::Local(local) = def {
516 if let Either::Left(pat) = local.source(sema.db).value {
517 if matches!(
518 pat.syntax().parent().and_then(ast::RecordPatField::cast),
519 Some(pat_field) if pat_field.name_ref().is_none()
520 ) {
521 replacement_text.push_str(": ");
522 replacement_text.push_str(new_name);
523 repl_range = TextRange::new(
524 pat.syntax().text_range().end(),
525 pat.syntax().text_range().end(),
526 );
527 }
528 }
529 }
530 if replacement_text.is_empty() {
531 replacement_text.push_str(new_name);
532 }
533 let edit = TextEdit::replace(repl_range, replacement_text);
534 Ok((nav.file_id, edit))
454} 535}
455 536
456#[cfg(test)] 537#[cfg(test)]
@@ -472,7 +553,7 @@ mod tests {
472 Ok(source_change) => { 553 Ok(source_change) => {
473 let mut text_edit_builder = TextEdit::builder(); 554 let mut text_edit_builder = TextEdit::builder();
474 let mut file_id: Option<FileId> = None; 555 let mut file_id: Option<FileId> = None;
475 for edit in source_change.info.source_file_edits { 556 for edit in source_change.source_file_edits {
476 file_id = Some(edit.0); 557 file_id = Some(edit.0);
477 for indel in edit.1.into_iter() { 558 for indel in edit.1.into_iter() {
478 text_edit_builder.replace(indel.delete, indel.insert); 559 text_edit_builder.replace(indel.delete, indel.insert);
@@ -502,10 +583,8 @@ mod tests {
502 583
503 fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) { 584 fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) {
504 let (analysis, position) = fixture::position(ra_fixture); 585 let (analysis, position) = fixture::position(ra_fixture);
505 let source_change = analysis 586 let source_change =
506 .rename(position, new_name) 587 analysis.rename(position, new_name).unwrap().expect("Expect returned a RenameError");
507 .unwrap()
508 .expect("Expect returned RangeInfo to be Some, but was None");
509 expect.assert_debug_eq(&source_change) 588 expect.assert_debug_eq(&source_change)
510 } 589 }
511 590
@@ -749,8 +828,8 @@ impl Foo {
749 } 828 }
750 829
751 #[test] 830 #[test]
752 fn test_rename_struct_field_for_shorthand() { 831 fn test_rename_field_in_field_shorthand() {
753 mark::check!(test_rename_struct_field_for_shorthand); 832 mark::check!(test_rename_field_in_field_shorthand);
754 check( 833 check(
755 "j", 834 "j",
756 r#" 835 r#"
@@ -775,8 +854,8 @@ impl Foo {
775 } 854 }
776 855
777 #[test] 856 #[test]
778 fn test_rename_local_for_field_shorthand() { 857 fn test_rename_local_in_field_shorthand() {
779 mark::check!(test_rename_local_for_field_shorthand); 858 mark::check!(test_rename_local_in_field_shorthand);
780 check( 859 check(
781 "j", 860 "j",
782 r#" 861 r#"
@@ -871,36 +950,33 @@ mod foo$0;
871// empty 950// empty
872"#, 951"#,
873 expect![[r#" 952 expect![[r#"
874 RangeInfo { 953 SourceChange {
875 range: 4..7, 954 source_file_edits: {
876 info: SourceChange { 955 FileId(
877 source_file_edits: { 956 1,
878 FileId( 957 ): TextEdit {
879 1, 958 indels: [
880 ): TextEdit { 959 Indel {
881 indels: [ 960 insert: "foo2",
882 Indel { 961 delete: 4..7,
883 insert: "foo2", 962 },
884 delete: 4..7, 963 ],
885 },
886 ],
887 },
888 }, 964 },
889 file_system_edits: [ 965 },
890 MoveFile { 966 file_system_edits: [
891 src: FileId( 967 MoveFile {
968 src: FileId(
969 2,
970 ),
971 dst: AnchoredPathBuf {
972 anchor: FileId(
892 2, 973 2,
893 ), 974 ),
894 dst: AnchoredPathBuf { 975 path: "foo2.rs",
895 anchor: FileId(
896 2,
897 ),
898 path: "foo2.rs",
899 },
900 }, 976 },
901 ], 977 },
902 is_snippet: false, 978 ],
903 }, 979 is_snippet: false,
904 } 980 }
905 "#]], 981 "#]],
906 ); 982 );
@@ -923,46 +999,43 @@ pub struct FooContent;
923use crate::foo$0::FooContent; 999use crate::foo$0::FooContent;
924"#, 1000"#,
925 expect![[r#" 1001 expect![[r#"
926 RangeInfo { 1002 SourceChange {
927 range: 11..14, 1003 source_file_edits: {
928 info: SourceChange { 1004 FileId(
929 source_file_edits: { 1005 0,
930 FileId( 1006 ): TextEdit {
931 0, 1007 indels: [
932 ): TextEdit { 1008 Indel {
933 indels: [ 1009 insert: "quux",
934 Indel { 1010 delete: 8..11,
935 insert: "quux", 1011 },
936 delete: 8..11, 1012 ],
937 },
938 ],
939 },
940 FileId(
941 2,
942 ): TextEdit {
943 indels: [
944 Indel {
945 insert: "quux",
946 delete: 11..14,
947 },
948 ],
949 },
950 }, 1013 },
951 file_system_edits: [ 1014 FileId(
952 MoveFile { 1015 2,
953 src: FileId( 1016 ): TextEdit {
1017 indels: [
1018 Indel {
1019 insert: "quux",
1020 delete: 11..14,
1021 },
1022 ],
1023 },
1024 },
1025 file_system_edits: [
1026 MoveFile {
1027 src: FileId(
1028 1,
1029 ),
1030 dst: AnchoredPathBuf {
1031 anchor: FileId(
954 1, 1032 1,
955 ), 1033 ),
956 dst: AnchoredPathBuf { 1034 path: "quux.rs",
957 anchor: FileId(
958 1,
959 ),
960 path: "quux.rs",
961 },
962 }, 1035 },
963 ], 1036 },
964 is_snippet: false, 1037 ],
965 }, 1038 is_snippet: false,
966 } 1039 }
967 "#]], 1040 "#]],
968 ); 1041 );
@@ -979,36 +1052,33 @@ mod fo$0o;
979// empty 1052// empty
980"#, 1053"#,
981 expect![[r#" 1054 expect![[r#"
982 RangeInfo { 1055 SourceChange {
983 range: 4..7, 1056 source_file_edits: {
984 info: SourceChange { 1057 FileId(
985 source_file_edits: { 1058 0,
986 FileId( 1059 ): TextEdit {
987 0, 1060 indels: [
988 ): TextEdit { 1061 Indel {
989 indels: [ 1062 insert: "foo2",
990 Indel { 1063 delete: 4..7,
991 insert: "foo2", 1064 },
992 delete: 4..7, 1065 ],
993 },
994 ],
995 },
996 }, 1066 },
997 file_system_edits: [ 1067 },
998 MoveFile { 1068 file_system_edits: [
999 src: FileId( 1069 MoveFile {
1070 src: FileId(
1071 1,
1072 ),
1073 dst: AnchoredPathBuf {
1074 anchor: FileId(
1000 1, 1075 1,
1001 ), 1076 ),
1002 dst: AnchoredPathBuf { 1077 path: "../foo2/mod.rs",
1003 anchor: FileId(
1004 1,
1005 ),
1006 path: "../foo2/mod.rs",
1007 },
1008 }, 1078 },
1009 ], 1079 },
1010 is_snippet: false, 1080 ],
1011 }, 1081 is_snippet: false,
1012 } 1082 }
1013 "#]], 1083 "#]],
1014 ); 1084 );
@@ -1026,36 +1096,33 @@ mod outer { mod fo$0o; }
1026// empty 1096// empty
1027"#, 1097"#,
1028 expect![[r#" 1098 expect![[r#"
1029 RangeInfo { 1099 SourceChange {
1030 range: 16..19, 1100 source_file_edits: {
1031 info: SourceChange { 1101 FileId(
1032 source_file_edits: { 1102 0,
1033 FileId( 1103 ): TextEdit {
1034 0, 1104 indels: [
1035 ): TextEdit { 1105 Indel {
1036 indels: [ 1106 insert: "bar",
1037 Indel { 1107 delete: 16..19,
1038 insert: "bar", 1108 },
1039 delete: 16..19, 1109 ],
1040 },
1041 ],
1042 },
1043 }, 1110 },
1044 file_system_edits: [ 1111 },
1045 MoveFile { 1112 file_system_edits: [
1046 src: FileId( 1113 MoveFile {
1114 src: FileId(
1115 1,
1116 ),
1117 dst: AnchoredPathBuf {
1118 anchor: FileId(
1047 1, 1119 1,
1048 ), 1120 ),
1049 dst: AnchoredPathBuf { 1121 path: "bar.rs",
1050 anchor: FileId(
1051 1,
1052 ),
1053 path: "bar.rs",
1054 },
1055 }, 1122 },
1056 ], 1123 },
1057 is_snippet: false, 1124 ],
1058 }, 1125 is_snippet: false,
1059 } 1126 }
1060 "#]], 1127 "#]],
1061 ); 1128 );
@@ -1096,46 +1163,43 @@ pub mod foo$0;
1096// pub fn fun() {} 1163// pub fn fun() {}
1097"#, 1164"#,
1098 expect![[r#" 1165 expect![[r#"
1099 RangeInfo { 1166 SourceChange {
1100 range: 8..11, 1167 source_file_edits: {
1101 info: SourceChange { 1168 FileId(
1102 source_file_edits: { 1169 0,
1103 FileId( 1170 ): TextEdit {
1104 0, 1171 indels: [
1105 ): TextEdit { 1172 Indel {
1106 indels: [ 1173 insert: "foo2",
1107 Indel { 1174 delete: 27..30,
1108 insert: "foo2", 1175 },
1109 delete: 27..30, 1176 ],
1110 }, 1177 },
1111 ], 1178 FileId(
1112 }, 1179 1,
1113 FileId( 1180 ): TextEdit {
1114 1, 1181 indels: [
1115 ): TextEdit { 1182 Indel {
1116 indels: [ 1183 insert: "foo2",
1117 Indel { 1184 delete: 8..11,
1118 insert: "foo2", 1185 },
1119 delete: 8..11, 1186 ],
1120 },
1121 ],
1122 },
1123 }, 1187 },
1124 file_system_edits: [ 1188 },
1125 MoveFile { 1189 file_system_edits: [
1126 src: FileId( 1190 MoveFile {
1191 src: FileId(
1192 2,
1193 ),
1194 dst: AnchoredPathBuf {
1195 anchor: FileId(
1127 2, 1196 2,
1128 ), 1197 ),
1129 dst: AnchoredPathBuf { 1198 path: "foo2.rs",
1130 anchor: FileId(
1131 2,
1132 ),
1133 path: "foo2.rs",
1134 },
1135 }, 1199 },
1136 ], 1200 },
1137 is_snippet: false, 1201 ],
1138 }, 1202 is_snippet: false,
1139 } 1203 }
1140 "#]], 1204 "#]],
1141 ); 1205 );
@@ -1389,8 +1453,8 @@ impl Foo {
1389 } 1453 }
1390 1454
1391 #[test] 1455 #[test]
1392 fn test_initializer_use_field_init_shorthand() { 1456 fn test_rename_field_put_init_shorthand() {
1393 mark::check!(test_rename_field_expr_pat); 1457 mark::check!(test_rename_field_put_init_shorthand);
1394 check( 1458 check(
1395 "bar", 1459 "bar",
1396 r#" 1460 r#"
@@ -1411,23 +1475,46 @@ fn foo(bar: i32) -> Foo {
1411 } 1475 }
1412 1476
1413 #[test] 1477 #[test]
1414 fn test_struct_field_destructure_into_shorthand() { 1478 fn test_rename_local_put_init_shorthand() {
1479 mark::check!(test_rename_local_put_init_shorthand);
1480 check(
1481 "i",
1482 r#"
1483struct Foo { i: i32 }
1484
1485fn foo(bar$0: i32) -> Foo {
1486 Foo { i: bar }
1487}
1488"#,
1489 r#"
1490struct Foo { i: i32 }
1491
1492fn foo(i: i32) -> Foo {
1493 Foo { i }
1494}
1495"#,
1496 );
1497 }
1498
1499 #[test]
1500 fn test_struct_field_pat_into_shorthand() {
1501 mark::check!(test_rename_field_put_init_shorthand_pat);
1415 check( 1502 check(
1416 "baz", 1503 "baz",
1417 r#" 1504 r#"
1418struct Foo { i$0: i32 } 1505struct Foo { i$0: i32 }
1419 1506
1420fn foo(foo: Foo) { 1507fn foo(foo: Foo) {
1421 let Foo { i: baz } = foo; 1508 let Foo { i: ref baz @ qux } = foo;
1422 let _ = baz; 1509 let _ = qux;
1423} 1510}
1424"#, 1511"#,
1425 r#" 1512 r#"
1426struct Foo { baz: i32 } 1513struct Foo { baz: i32 }
1427 1514
1428fn foo(foo: Foo) { 1515fn foo(foo: Foo) {
1429 let Foo { baz } = foo; 1516 let Foo { ref baz @ qux } = foo;
1430 let _ = baz; 1517 let _ = qux;
1431} 1518}
1432"#, 1519"#,
1433 ); 1520 );
@@ -1501,6 +1588,27 @@ fn foo(Foo { i: bar }: foo) -> i32 {
1501 } 1588 }
1502 1589
1503 #[test] 1590 #[test]
1591 fn test_struct_field_complex_ident_pat() {
1592 check(
1593 "baz",
1594 r#"
1595struct Foo { i$0: i32 }
1596
1597fn foo(foo: Foo) {
1598 let Foo { ref i } = foo;
1599}
1600"#,
1601 r#"
1602struct Foo { baz: i32 }
1603
1604fn foo(foo: Foo) {
1605 let Foo { baz: ref i } = foo;
1606}
1607"#,
1608 );
1609 }
1610
1611 #[test]
1504 fn test_rename_lifetimes() { 1612 fn test_rename_lifetimes() {
1505 mark::check!(rename_lifetime); 1613 mark::check!(rename_lifetime);
1506 check( 1614 check(
@@ -1607,4 +1715,38 @@ impl Foo {
1607"#, 1715"#,
1608 ) 1716 )
1609 } 1717 }
1718
1719 #[test]
1720 fn test_rename_field_in_pat_in_macro_doesnt_shorthand() {
1721 // ideally we would be able to make this emit a short hand, but I doubt this is easily possible
1722 check(
1723 "baz",
1724 r#"
1725macro_rules! foo {
1726 ($pattern:pat) => {
1727 let $pattern = loop {};
1728 };
1729}
1730struct Foo {
1731 bar$0: u32,
1732}
1733fn foo() {
1734 foo!(Foo { bar: baz });
1735}
1736"#,
1737 r#"
1738macro_rules! foo {
1739 ($pattern:pat) => {
1740 let $pattern = loop {};
1741 };
1742}
1743struct Foo {
1744 baz: u32,
1745}
1746fn foo() {
1747 foo!(Foo { baz: baz });
1748}
1749"#,
1750 )
1751 }
1610} 1752}