aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src')
-rw-r--r--crates/ide/src/diagnostics/fixes.rs2
-rw-r--r--crates/ide/src/doc_links.rs8
-rw-r--r--crates/ide/src/lib.rs4
-rw-r--r--crates/ide/src/references.rs20
-rw-r--r--crates/ide/src/references/rename.rs656
5 files changed, 370 insertions, 320 deletions
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs
index e4335119b..579d5a308 100644
--- a/crates/ide/src/diagnostics/fixes.rs
+++ b/crates/ide/src/diagnostics/fixes.rs
@@ -140,7 +140,7 @@ impl DiagnosticWithFix for IncorrectCase {
140 rename_with_semantics(sema, file_position, &self.suggested_text).ok()?; 140 rename_with_semantics(sema, file_position, &self.suggested_text).ok()?;
141 141
142 let label = format!("Rename to {}", self.suggested_text); 142 let label = format!("Rename to {}", self.suggested_text);
143 Some(Fix::new(&label, rename_changes.info, rename_changes.range)) 143 Some(Fix::new(&label, rename_changes, frange.range))
144 } 144 }
145} 145}
146 146
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs
index de10406bc..1f08d7810 100644
--- a/crates/ide/src/doc_links.rs
+++ b/crates/ide/src/doc_links.rs
@@ -438,10 +438,10 @@ fn get_symbol_fragment(db: &dyn HirDatabase, field_or_assoc: &FieldOrAssocItem)
438 FieldOrAssocItem::Field(field) => format!("#structfield.{}", field.name(db)), 438 FieldOrAssocItem::Field(field) => format!("#structfield.{}", field.name(db)),
439 FieldOrAssocItem::AssocItem(assoc) => match assoc { 439 FieldOrAssocItem::AssocItem(assoc) => match assoc {
440 AssocItem::Function(function) => { 440 AssocItem::Function(function) => {
441 let is_trait_method = matches!( 441 let is_trait_method = function
442 function.as_assoc_item(db).map(|assoc| assoc.container(db)), 442 .as_assoc_item(db)
443 Some(AssocItemContainer::Trait(..)) 443 .and_then(|assoc| assoc.containing_trait(db))
444 ); 444 .is_some();
445 // This distinction may get more complicated when specialization is available. 445 // This distinction may get more complicated when specialization is available.
446 // Rustdoc makes this decision based on whether a method 'has defaultness'. 446 // Rustdoc makes this decision based on whether a method 'has defaultness'.
447 // Currently this is only the case for provided trait methods. 447 // Currently this is only the case for provided trait methods.
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 6c94c26b5..3abbb14c6 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -478,6 +478,7 @@ impl Analysis {
478 position: FilePosition, 478 position: FilePosition,
479 full_import_path: &str, 479 full_import_path: &str,
480 imported_name: String, 480 imported_name: String,
481 import_for_trait_assoc_item: bool,
481 ) -> Cancelable<Vec<TextEdit>> { 482 ) -> Cancelable<Vec<TextEdit>> {
482 Ok(self 483 Ok(self
483 .with_db(|db| { 484 .with_db(|db| {
@@ -487,6 +488,7 @@ impl Analysis {
487 position, 488 position,
488 full_import_path, 489 full_import_path,
489 imported_name, 490 imported_name,
491 import_for_trait_assoc_item,
490 ) 492 )
491 })? 493 })?
492 .unwrap_or_default()) 494 .unwrap_or_default())
@@ -520,7 +522,7 @@ impl Analysis {
520 &self, 522 &self,
521 position: FilePosition, 523 position: FilePosition,
522 new_name: &str, 524 new_name: &str,
523 ) -> Cancelable<Result<RangeInfo<SourceChange>, RenameError>> { 525 ) -> Cancelable<Result<SourceChange, RenameError>> {
524 self.with_db(|db| references::rename::rename(db, position, new_name)) 526 self.with_db(|db| references::rename::rename(db, position, new_name))
525 } 527 }
526 528
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index 51a2f4327..df9c31aef 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -930,6 +930,26 @@ impl Foo {
930 ); 930 );
931 } 931 }
932 932
933 #[test]
934 fn test_find_self_refs_decl() {
935 check(
936 r#"
937struct Foo { bar: i32 }
938
939impl Foo {
940 fn foo(self$0) {
941 self;
942 }
943}
944"#,
945 expect![[r#"
946 self SelfParam FileId(0) 47..51 47..51 SelfParam
947
948 FileId(0) 63..67 Other Read
949 "#]],
950 );
951 }
952
933 fn check(ra_fixture: &str, expect: Expect) { 953 fn check(ra_fixture: &str, expect: Expect) {
934 check_with_scope(ra_fixture, None, expect) 954 check_with_scope(ra_fixture, None, expect)
935 } 955 }
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 4df189c98..c25bcce50 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -1,23 +1,24 @@
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, FileRange},
7 defs::{Definition, NameClass, NameRefClass}, 8 defs::{Definition, NameClass, NameRefClass},
8 search::FileReference, 9 search::FileReference,
9 RootDatabase, 10 RootDatabase,
10}; 11};
12use stdx::assert_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::{
20 FilePosition, FileSystemEdit, RangeInfo, ReferenceKind, ReferenceSearchResult, SourceChange, 21 display::TryToNav, FilePosition, FileSystemEdit, RangeInfo, ReferenceKind, SourceChange,
21 TextRange, 22 TextRange,
22}; 23};
23 24
@@ -47,20 +48,22 @@ pub(crate) fn prepare_rename(
47 let sema = Semantics::new(db); 48 let sema = Semantics::new(db);
48 let source_file = sema.parse(position.file_id); 49 let source_file = sema.parse(position.file_id);
49 let syntax = source_file.syntax(); 50 let syntax = source_file.syntax();
50 if let Some(module) = find_module_at_offset(&sema, position, syntax) { 51 let range = match &find_name_like(&sema, &syntax, position)
51 rename_mod(&sema, position, module, "dummy") 52 .ok_or_else(|| format_err!("No references found at position"))?
52 } else { 53 {
53 let RangeInfo { range, .. } = find_all_refs(&sema, position)?; 54 NameLike::Name(it) => it.syntax(),
54 Ok(RangeInfo::new(range, SourceChange::default())) 55 NameLike::NameRef(it) => it.syntax(),
56 NameLike::Lifetime(it) => it.syntax(),
55 } 57 }
56 .map(|info| RangeInfo::new(info.range, ())) 58 .text_range();
59 Ok(RangeInfo::new(range, ()))
57} 60}
58 61
59pub(crate) fn rename( 62pub(crate) fn rename(
60 db: &RootDatabase, 63 db: &RootDatabase,
61 position: FilePosition, 64 position: FilePosition,
62 new_name: &str, 65 new_name: &str,
63) -> RenameResult<RangeInfo<SourceChange>> { 66) -> RenameResult<SourceChange> {
64 let sema = Semantics::new(db); 67 let sema = Semantics::new(db);
65 rename_with_semantics(&sema, position, new_name) 68 rename_with_semantics(&sema, position, new_name)
66} 69}
@@ -69,14 +72,15 @@ pub(crate) fn rename_with_semantics(
69 sema: &Semantics<RootDatabase>, 72 sema: &Semantics<RootDatabase>,
70 position: FilePosition, 73 position: FilePosition,
71 new_name: &str, 74 new_name: &str,
72) -> RenameResult<RangeInfo<SourceChange>> { 75) -> RenameResult<SourceChange> {
73 let source_file = sema.parse(position.file_id); 76 let source_file = sema.parse(position.file_id);
74 let syntax = source_file.syntax(); 77 let syntax = source_file.syntax();
75 78
76 if let Some(module) = find_module_at_offset(&sema, position, syntax) { 79 let def = find_definition(sema, syntax, position)
77 rename_mod(&sema, position, module, new_name) 80 .ok_or_else(|| format_err!("No references found at position"))?;
78 } else { 81 match def {
79 rename_reference(&sema, position, new_name) 82 Definition::ModuleDef(ModuleDef::Module(module)) => rename_mod(&sema, module, new_name),
83 def => rename_reference(sema, def, new_name),
80 } 84 }
81} 85}
82 86
@@ -87,12 +91,7 @@ pub(crate) fn will_rename_file(
87) -> Option<SourceChange> { 91) -> Option<SourceChange> {
88 let sema = Semantics::new(db); 92 let sema = Semantics::new(db);
89 let module = sema.to_module_def(file_id)?; 93 let module = sema.to_module_def(file_id)?;
90 94 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(); 95 change.file_system_edits.clear();
97 Some(change) 96 Some(change)
98} 97}
@@ -124,40 +123,51 @@ fn check_identifier(new_name: &str) -> RenameResult<IdentifierKind> {
124 } 123 }
125} 124}
126 125
127fn find_module_at_offset( 126enum NameLike {
127 Name(ast::Name),
128 NameRef(ast::NameRef),
129 Lifetime(ast::Lifetime),
130}
131
132fn find_name_like(
128 sema: &Semantics<RootDatabase>, 133 sema: &Semantics<RootDatabase>,
129 position: FilePosition,
130 syntax: &SyntaxNode, 134 syntax: &SyntaxNode,
131) -> Option<Module> { 135 position: FilePosition,
132 let ident = syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::IDENT)?; 136) -> Option<NameLike> {
133 137 let namelike = if let Some(name_ref) =
134 let module = match_ast! { 138 sema.find_node_at_offset_with_descend::<ast::NameRef>(syntax, position.offset)
135 match (ident.parent()) { 139 {
136 ast::NameRef(name_ref) => { 140 NameLike::NameRef(name_ref)
137 match NameRefClass::classify(sema, &name_ref)? { 141 } else if let Some(name) =
138 NameRefClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module, 142 sema.find_node_at_offset_with_descend::<ast::Name>(syntax, position.offset)
139 _ => return None, 143 {
140 } 144 NameLike::Name(name)
141 }, 145 } else if let Some(lifetime) =
142 ast::Name(name) => { 146 sema.find_node_at_offset_with_descend::<ast::Lifetime>(syntax, position.offset)
143 match NameClass::classify(&sema, &name)? { 147 {
144 NameClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module, 148 NameLike::Lifetime(lifetime)
145 _ => return None, 149 } else {
146 } 150 return None;
147 },
148 _ => return None,
149 }
150 }; 151 };
151 152 Some(namelike)
152 Some(module)
153} 153}
154 154
155fn find_all_refs( 155fn find_definition(
156 sema: &Semantics<RootDatabase>, 156 sema: &Semantics<RootDatabase>,
157 syntax: &SyntaxNode,
157 position: FilePosition, 158 position: FilePosition,
158) -> RenameResult<RangeInfo<ReferenceSearchResult>> { 159) -> Option<Definition> {
159 crate::references::find_all_refs(sema, position, None) 160 let def = match find_name_like(sema, syntax, position)? {
160 .ok_or_else(|| format_err!("No references found at position")) 161 NameLike::Name(name) => NameClass::classify(sema, &name)?.referenced_or_defined(sema.db),
162 NameLike::NameRef(name_ref) => NameRefClass::classify(sema, &name_ref)?.referenced(sema.db),
163 NameLike::Lifetime(lifetime) => NameRefClass::classify_lifetime(sema, &lifetime)
164 .map(|class| NameRefClass::referenced(class, sema.db))
165 .or_else(|| {
166 NameClass::classify_lifetime(sema, &lifetime)
167 .map(|it| it.referenced_or_defined(sema.db))
168 })?,
169 };
170 Some(def)
161} 171}
162 172
163fn source_edit_from_references( 173fn source_edit_from_references(
@@ -231,72 +241,84 @@ fn edit_text_range_for_record_field_expr_or_pat(
231 241
232fn rename_mod( 242fn rename_mod(
233 sema: &Semantics<RootDatabase>, 243 sema: &Semantics<RootDatabase>,
234 position: FilePosition,
235 module: Module, 244 module: Module,
236 new_name: &str, 245 new_name: &str,
237) -> RenameResult<RangeInfo<SourceChange>> { 246) -> RenameResult<SourceChange> {
238 if IdentifierKind::Ident != check_identifier(new_name)? { 247 if IdentifierKind::Ident != check_identifier(new_name)? {
239 bail!("Invalid name `{0}`: cannot rename module to {0}", new_name); 248 bail!("Invalid name `{0}`: cannot rename module to {0}", new_name);
240 } 249 }
241 250
242 let mut source_change = SourceChange::default(); 251 let mut source_change = SourceChange::default();
243 252
244 let src = module.definition_source(sema.db); 253 let InFile { file_id, value: def_source } = module.definition_source(sema.db);
245 let file_id = src.file_id.original_file(sema.db); 254 let file_id = file_id.original_file(sema.db);
246 match src.value { 255 if let ModuleSource::SourceFile(..) = def_source {
247 ModuleSource::SourceFile(..) => { 256 // mod is defined in path/to/dir/mod.rs
248 // mod is defined in path/to/dir/mod.rs 257 let path = if module.is_mod_rs(sema.db) {
249 let path = if module.is_mod_rs(sema.db) { 258 format!("../{}/mod.rs", new_name)
250 format!("../{}/mod.rs", new_name) 259 } else {
251 } else { 260 format!("{}.rs", new_name)
252 format!("{}.rs", new_name) 261 };
253 }; 262 let dst = AnchoredPathBuf { anchor: file_id, path };
254 let dst = AnchoredPathBuf { anchor: file_id, path }; 263 let move_file = FileSystemEdit::MoveFile { src: file_id, dst };
255 let move_file = FileSystemEdit::MoveFile { src: file_id, dst }; 264 source_change.push_file_system_edit(move_file);
256 source_change.push_file_system_edit(move_file); 265 }
257 } 266
258 ModuleSource::Module(..) => {} 267 if let Some(InFile { file_id, value: decl_source }) = module.declaration_source(sema.db) {
259 } 268 let file_id = file_id.original_file(sema.db);
260 269 match decl_source.name() {
261 if let Some(src) = module.declaration_source(sema.db) { 270 Some(name) => source_change.insert_source_edit(
262 let file_id = src.file_id.original_file(sema.db); 271 file_id,
263 let name = src.value.name().unwrap(); 272 TextEdit::replace(name.syntax().text_range(), new_name.to_string()),
264 source_change.insert_source_edit( 273 ),
265 file_id, 274 _ => unreachable!(),
266 TextEdit::replace(name.syntax().text_range(), new_name.into()), 275 };
267 );
268 } 276 }
269 277 let def = Definition::ModuleDef(ModuleDef::Module(module));
270 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; 278 let usages = def.usages(sema).all();
271 let ref_edits = refs.references().iter().map(|(&file_id, references)| { 279 let ref_edits = usages.iter().map(|(&file_id, references)| {
272 source_edit_from_references(sema, file_id, references, new_name) 280 source_edit_from_references(sema, file_id, references, new_name)
273 }); 281 });
274 source_change.extend(ref_edits); 282 source_change.extend(ref_edits);
275 283
276 Ok(RangeInfo::new(range, source_change)) 284 Ok(source_change)
277} 285}
278 286
279fn rename_to_self( 287fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameResult<SourceChange> {
280 sema: &Semantics<RootDatabase>, 288 if assert_never!(local.is_self(sema.db)) {
281 position: FilePosition, 289 bail!("rename_to_self invoked on self");
282) -> Result<RangeInfo<SourceChange>, RenameError> { 290 }
283 let source_file = sema.parse(position.file_id); 291
284 let syn = source_file.syntax(); 292 let fn_def = match local.parent(sema.db) {
293 hir::DefWithBody::Function(func) => func,
294 _ => bail!("Cannot rename non-param local to self"),
295 };
285 296
286 let (fn_def, fn_ast) = find_node_at_offset::<ast::Fn>(syn, position.offset) 297 // FIXME: reimplement this on the hir instead
287 .and_then(|fn_ast| sema.to_def(&fn_ast).zip(Some(fn_ast))) 298 // 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"))?; 299 let fn_ast =
289 let param_range = fn_ast 300 fn_def.source(sema.db).ok_or(format_err!("Cannot rename non-param local to self"))?.value;
301
302 let first_param_range = fn_ast
290 .param_list() 303 .param_list()
291 .and_then(|p| p.params().next()) 304 .and_then(|p| p.params().next())
292 .ok_or_else(|| format_err!("Method has no parameters"))? 305 .ok_or_else(|| format_err!("Method has no parameters"))?
293 .syntax() 306 .syntax()
294 .text_range(); 307 .text_range();
295 if !param_range.contains(position.offset) { 308 let InFile { file_id, value: local_source } = local.source(sema.db);
296 bail!("Only the first parameter can be self"); 309 match local_source {
310 either::Either::Left(pat)
311 if !first_param_range.contains_range(pat.syntax().text_range()) =>
312 {
313 bail!("Only the first parameter can be self");
314 }
315 _ => (),
297 } 316 }
298 317
299 let impl_block = find_node_at_offset::<ast::Impl>(syn, position.offset) 318 let impl_block = fn_ast
319 .syntax()
320 .ancestors()
321 .find_map(|node| ast::Impl::cast(node))
300 .and_then(|def| sema.to_def(&def)) 322 .and_then(|def| sema.to_def(&def))
301 .ok_or_else(|| format_err!("No impl block found for function"))?; 323 .ok_or_else(|| format_err!("No impl block found for function"))?;
302 if fn_def.self_param(sema.db).is_some() { 324 if fn_def.self_param(sema.db).is_some() {
@@ -320,25 +342,21 @@ fn rename_to_self(
320 bail!("Parameter type differs from impl block type"); 342 bail!("Parameter type differs from impl block type");
321 } 343 }
322 344
323 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; 345 let def = Definition::Local(local);
324 346 let usages = def.usages(sema).all();
325 let mut source_change = SourceChange::default(); 347 let mut source_change = SourceChange::default();
326 source_change.extend(refs.references().iter().map(|(&file_id, references)| { 348 source_change.extend(usages.iter().map(|(&file_id, references)| {
327 source_edit_from_references(sema, file_id, references, "self") 349 source_edit_from_references(sema, file_id, references, "self")
328 })); 350 }));
329 source_change.insert_source_edit( 351 source_change.insert_source_edit(
330 position.file_id, 352 file_id.original_file(sema.db),
331 TextEdit::replace(param_range, String::from(self_param)), 353 TextEdit::replace(first_param_range, String::from(self_param)),
332 ); 354 );
333 355
334 Ok(RangeInfo::new(range, source_change)) 356 Ok(source_change)
335} 357}
336 358
337fn text_edit_from_self_param( 359fn 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> { 360 fn target_type_name(impl_def: &ast::Impl) -> Option<String> {
343 if let Some(ast::Type::PathType(p)) = impl_def.self_ty() { 361 if let Some(ast::Type::PathType(p)) = impl_def.self_ty() {
344 return Some(p.path()?.segment()?.name_ref()?.text().to_string()); 362 return Some(p.path()?.segment()?.name_ref()?.text().to_string());
@@ -346,7 +364,7 @@ fn text_edit_from_self_param(
346 None 364 None
347 } 365 }
348 366
349 let impl_def = find_node_at_offset::<ast::Impl>(syn, self_param.syntax().text_range().start())?; 367 let impl_def = self_param.syntax().ancestors().find_map(|it| ast::Impl::cast(it))?;
350 let type_name = target_type_name(&impl_def)?; 368 let type_name = target_type_name(&impl_def)?;
351 369
352 let mut replacement_text = String::from(new_name); 370 let mut replacement_text = String::from(new_name);
@@ -363,94 +381,119 @@ fn text_edit_from_self_param(
363 381
364fn rename_self_to_param( 382fn rename_self_to_param(
365 sema: &Semantics<RootDatabase>, 383 sema: &Semantics<RootDatabase>,
366 position: FilePosition, 384 local: hir::Local,
367 new_name: &str, 385 new_name: &str,
368 ident_kind: IdentifierKind, 386 identifier_kind: IdentifierKind,
369 range: TextRange, 387) -> RenameResult<SourceChange> {
370 refs: ReferenceSearchResult, 388 let (file_id, self_param) = match local.source(sema.db) {
371) -> Result<RangeInfo<SourceChange>, RenameError> { 389 InFile { file_id, value: Either::Right(self_param) } => (file_id, self_param),
372 match ident_kind { 390 _ => {
373 IdentifierKind::Lifetime => bail!("Invalid name `{}`: not an identifier", new_name), 391 assert_never!(true, "rename_self_to_param invoked on a non-self local");
374 IdentifierKind::ToSelf => { 392 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 } 393 }
379 _ => (), 394 };
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 395
407 return Ok(RangeInfo::new(range, source_change)); 396 let def = Definition::Local(local);
408 } 397 let usages = def.usages(sema).all();
398 let edit = text_edit_from_self_param(&self_param, new_name)
399 .ok_or_else(|| format_err!("No target type found"))?;
400 if usages.len() > 1 && identifier_kind == IdentifierKind::Underscore {
401 bail!("Cannot rename reference to `_` as it is being referenced multiple times");
409 } 402 }
410 Err(format_err!("Method has no self param")) 403 let mut source_change = SourceChange::default();
404 source_change.insert_source_edit(file_id.original_file(sema.db), edit);
405 source_change.extend(usages.iter().map(|(&file_id, references)| {
406 source_edit_from_references(sema, file_id, &references, new_name)
407 }));
408 Ok(source_change)
411} 409}
412 410
413fn rename_reference( 411fn rename_reference(
414 sema: &Semantics<RootDatabase>, 412 sema: &Semantics<RootDatabase>,
415 position: FilePosition, 413 def: Definition,
416 new_name: &str, 414 new_name: &str,
417) -> Result<RangeInfo<SourceChange>, RenameError> { 415) -> RenameResult<SourceChange> {
418 let ident_kind = check_identifier(new_name)?; 416 let ident_kind = check_identifier(new_name)?;
419 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
420 417
421 match (ident_kind, &refs.declaration.kind) { 418 let def_is_lbl_or_lt = matches!(def,
422 (IdentifierKind::ToSelf, ReferenceKind::Lifetime) 419 Definition::GenericParam(hir::GenericParam::LifetimeParam(_))
423 | (IdentifierKind::Underscore, ReferenceKind::Lifetime) 420 | Definition::Label(_)
424 | (IdentifierKind::Ident, ReferenceKind::Lifetime) => { 421 );
422 match (ident_kind, def) {
423 (IdentifierKind::ToSelf, _)
424 | (IdentifierKind::Underscore, _)
425 | (IdentifierKind::Ident, _)
426 if def_is_lbl_or_lt =>
427 {
425 mark::hit!(rename_not_a_lifetime_ident_ref); 428 mark::hit!(rename_not_a_lifetime_ident_ref);
426 bail!("Invalid name `{}`: not a lifetime identifier", new_name) 429 bail!("Invalid name `{}`: not a lifetime identifier", new_name)
427 } 430 }
428 (IdentifierKind::Lifetime, ReferenceKind::Lifetime) => mark::hit!(rename_lifetime), 431 (IdentifierKind::Lifetime, _) if def_is_lbl_or_lt => mark::hit!(rename_lifetime),
429 (IdentifierKind::Lifetime, _) => { 432 (IdentifierKind::Lifetime, _) => {
430 mark::hit!(rename_not_an_ident_ref); 433 mark::hit!(rename_not_an_ident_ref);
431 bail!("Invalid name `{}`: not an identifier", new_name) 434 bail!("Invalid name `{}`: not an identifier", new_name)
432 } 435 }
433 (_, ReferenceKind::SelfParam) => { 436 (IdentifierKind::ToSelf, Definition::Local(local)) if local.is_self(sema.db) => {
437 // no-op
438 mark::hit!(rename_self_to_self);
439 return Ok(SourceChange::default());
440 }
441 (ident_kind, Definition::Local(local)) if local.is_self(sema.db) => {
434 mark::hit!(rename_self_to_param); 442 mark::hit!(rename_self_to_param);
435 return rename_self_to_param(sema, position, new_name, ident_kind, range, refs); 443 return rename_self_to_param(sema, local, new_name, ident_kind);
436 } 444 }
437 (IdentifierKind::ToSelf, _) => { 445 (IdentifierKind::ToSelf, Definition::Local(local)) => {
438 mark::hit!(rename_to_self); 446 mark::hit!(rename_to_self);
439 return rename_to_self(sema, position); 447 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 } 448 }
449 (IdentifierKind::ToSelf, _) => bail!("Invalid name `{}`: not an identifier", new_name),
445 (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident), 450 (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident),
446 } 451 }
447 452
453 let usages = def.usages(sema).all();
454 if !usages.is_empty() && ident_kind == IdentifierKind::Underscore {
455 mark::hit!(rename_underscore_multiple);
456 bail!("Cannot rename reference to `_` as it is being referenced multiple times");
457 }
448 let mut source_change = SourceChange::default(); 458 let mut source_change = SourceChange::default();
449 source_change.extend(refs.into_iter().map(|(file_id, references)| { 459 source_change.extend(usages.iter().map(|(&file_id, references)| {
450 source_edit_from_references(sema, file_id, &references, new_name) 460 source_edit_from_references(sema, file_id, &references, new_name)
451 })); 461 }));
452 462
453 Ok(RangeInfo::new(range, source_change)) 463 let (file_id, edit) = source_edit_from_def(sema, def, new_name)?;
464 source_change.insert_source_edit(file_id, edit);
465 Ok(source_change)
466}
467
468fn source_edit_from_def(
469 sema: &Semantics<RootDatabase>,
470 def: Definition,
471 new_name: &str,
472) -> RenameResult<(FileId, TextEdit)> {
473 let nav = def.try_to_nav(sema.db).unwrap();
474
475 let mut replacement_text = String::new();
476 let mut repl_range = nav.focus_or_full_range();
477 if let Definition::Local(local) = def {
478 if let Either::Left(pat) = local.source(sema.db).value {
479 if matches!(
480 pat.syntax().parent().and_then(ast::RecordPatField::cast),
481 Some(pat_field) if pat_field.name_ref().is_none()
482 ) {
483 replacement_text.push_str(": ");
484 replacement_text.push_str(new_name);
485 repl_range = TextRange::new(
486 pat.syntax().text_range().end(),
487 pat.syntax().text_range().end(),
488 );
489 }
490 }
491 }
492 if replacement_text.is_empty() {
493 replacement_text.push_str(new_name);
494 }
495 let edit = TextEdit::replace(repl_range, replacement_text);
496 Ok((nav.file_id, edit))
454} 497}
455 498
456#[cfg(test)] 499#[cfg(test)]
@@ -472,7 +515,7 @@ mod tests {
472 Ok(source_change) => { 515 Ok(source_change) => {
473 let mut text_edit_builder = TextEdit::builder(); 516 let mut text_edit_builder = TextEdit::builder();
474 let mut file_id: Option<FileId> = None; 517 let mut file_id: Option<FileId> = None;
475 for edit in source_change.info.source_file_edits { 518 for edit in source_change.source_file_edits {
476 file_id = Some(edit.0); 519 file_id = Some(edit.0);
477 for indel in edit.1.into_iter() { 520 for indel in edit.1.into_iter() {
478 text_edit_builder.replace(indel.delete, indel.insert); 521 text_edit_builder.replace(indel.delete, indel.insert);
@@ -871,36 +914,33 @@ mod foo$0;
871// empty 914// empty
872"#, 915"#,
873 expect![[r#" 916 expect![[r#"
874 RangeInfo { 917 SourceChange {
875 range: 4..7, 918 source_file_edits: {
876 info: SourceChange { 919 FileId(
877 source_file_edits: { 920 1,
878 FileId( 921 ): TextEdit {
879 1, 922 indels: [
880 ): TextEdit { 923 Indel {
881 indels: [ 924 insert: "foo2",
882 Indel { 925 delete: 4..7,
883 insert: "foo2", 926 },
884 delete: 4..7, 927 ],
885 },
886 ],
887 },
888 }, 928 },
889 file_system_edits: [ 929 },
890 MoveFile { 930 file_system_edits: [
891 src: FileId( 931 MoveFile {
932 src: FileId(
933 2,
934 ),
935 dst: AnchoredPathBuf {
936 anchor: FileId(
892 2, 937 2,
893 ), 938 ),
894 dst: AnchoredPathBuf { 939 path: "foo2.rs",
895 anchor: FileId(
896 2,
897 ),
898 path: "foo2.rs",
899 },
900 }, 940 },
901 ], 941 },
902 is_snippet: false, 942 ],
903 }, 943 is_snippet: false,
904 } 944 }
905 "#]], 945 "#]],
906 ); 946 );
@@ -923,46 +963,43 @@ pub struct FooContent;
923use crate::foo$0::FooContent; 963use crate::foo$0::FooContent;
924"#, 964"#,
925 expect![[r#" 965 expect![[r#"
926 RangeInfo { 966 SourceChange {
927 range: 11..14, 967 source_file_edits: {
928 info: SourceChange { 968 FileId(
929 source_file_edits: { 969 0,
930 FileId( 970 ): TextEdit {
931 0, 971 indels: [
932 ): TextEdit { 972 Indel {
933 indels: [ 973 insert: "quux",
934 Indel { 974 delete: 8..11,
935 insert: "quux", 975 },
936 delete: 8..11, 976 ],
937 },
938 ],
939 },
940 FileId(
941 2,
942 ): TextEdit {
943 indels: [
944 Indel {
945 insert: "quux",
946 delete: 11..14,
947 },
948 ],
949 },
950 }, 977 },
951 file_system_edits: [ 978 FileId(
952 MoveFile { 979 2,
953 src: FileId( 980 ): TextEdit {
981 indels: [
982 Indel {
983 insert: "quux",
984 delete: 11..14,
985 },
986 ],
987 },
988 },
989 file_system_edits: [
990 MoveFile {
991 src: FileId(
992 1,
993 ),
994 dst: AnchoredPathBuf {
995 anchor: FileId(
954 1, 996 1,
955 ), 997 ),
956 dst: AnchoredPathBuf { 998 path: "quux.rs",
957 anchor: FileId(
958 1,
959 ),
960 path: "quux.rs",
961 },
962 }, 999 },
963 ], 1000 },
964 is_snippet: false, 1001 ],
965 }, 1002 is_snippet: false,
966 } 1003 }
967 "#]], 1004 "#]],
968 ); 1005 );
@@ -979,36 +1016,33 @@ mod fo$0o;
979// empty 1016// empty
980"#, 1017"#,
981 expect![[r#" 1018 expect![[r#"
982 RangeInfo { 1019 SourceChange {
983 range: 4..7, 1020 source_file_edits: {
984 info: SourceChange { 1021 FileId(
985 source_file_edits: { 1022 0,
986 FileId( 1023 ): TextEdit {
987 0, 1024 indels: [
988 ): TextEdit { 1025 Indel {
989 indels: [ 1026 insert: "foo2",
990 Indel { 1027 delete: 4..7,
991 insert: "foo2", 1028 },
992 delete: 4..7, 1029 ],
993 },
994 ],
995 },
996 }, 1030 },
997 file_system_edits: [ 1031 },
998 MoveFile { 1032 file_system_edits: [
999 src: FileId( 1033 MoveFile {
1034 src: FileId(
1035 1,
1036 ),
1037 dst: AnchoredPathBuf {
1038 anchor: FileId(
1000 1, 1039 1,
1001 ), 1040 ),
1002 dst: AnchoredPathBuf { 1041 path: "../foo2/mod.rs",
1003 anchor: FileId(
1004 1,
1005 ),
1006 path: "../foo2/mod.rs",
1007 },
1008 }, 1042 },
1009 ], 1043 },
1010 is_snippet: false, 1044 ],
1011 }, 1045 is_snippet: false,
1012 } 1046 }
1013 "#]], 1047 "#]],
1014 ); 1048 );
@@ -1026,36 +1060,33 @@ mod outer { mod fo$0o; }
1026// empty 1060// empty
1027"#, 1061"#,
1028 expect![[r#" 1062 expect![[r#"
1029 RangeInfo { 1063 SourceChange {
1030 range: 16..19, 1064 source_file_edits: {
1031 info: SourceChange { 1065 FileId(
1032 source_file_edits: { 1066 0,
1033 FileId( 1067 ): TextEdit {
1034 0, 1068 indels: [
1035 ): TextEdit { 1069 Indel {
1036 indels: [ 1070 insert: "bar",
1037 Indel { 1071 delete: 16..19,
1038 insert: "bar", 1072 },
1039 delete: 16..19, 1073 ],
1040 },
1041 ],
1042 },
1043 }, 1074 },
1044 file_system_edits: [ 1075 },
1045 MoveFile { 1076 file_system_edits: [
1046 src: FileId( 1077 MoveFile {
1078 src: FileId(
1079 1,
1080 ),
1081 dst: AnchoredPathBuf {
1082 anchor: FileId(
1047 1, 1083 1,
1048 ), 1084 ),
1049 dst: AnchoredPathBuf { 1085 path: "bar.rs",
1050 anchor: FileId(
1051 1,
1052 ),
1053 path: "bar.rs",
1054 },
1055 }, 1086 },
1056 ], 1087 },
1057 is_snippet: false, 1088 ],
1058 }, 1089 is_snippet: false,
1059 } 1090 }
1060 "#]], 1091 "#]],
1061 ); 1092 );
@@ -1096,46 +1127,43 @@ pub mod foo$0;
1096// pub fn fun() {} 1127// pub fn fun() {}
1097"#, 1128"#,
1098 expect![[r#" 1129 expect![[r#"
1099 RangeInfo { 1130 SourceChange {
1100 range: 8..11, 1131 source_file_edits: {
1101 info: SourceChange { 1132 FileId(
1102 source_file_edits: { 1133 0,
1103 FileId( 1134 ): TextEdit {
1104 0, 1135 indels: [
1105 ): TextEdit { 1136 Indel {
1106 indels: [ 1137 insert: "foo2",
1107 Indel { 1138 delete: 27..30,
1108 insert: "foo2", 1139 },
1109 delete: 27..30, 1140 ],
1110 },
1111 ],
1112 },
1113 FileId(
1114 1,
1115 ): TextEdit {
1116 indels: [
1117 Indel {
1118 insert: "foo2",
1119 delete: 8..11,
1120 },
1121 ],
1122 },
1123 }, 1141 },
1124 file_system_edits: [ 1142 FileId(
1125 MoveFile { 1143 1,
1126 src: FileId( 1144 ): TextEdit {
1145 indels: [
1146 Indel {
1147 insert: "foo2",
1148 delete: 8..11,
1149 },
1150 ],
1151 },
1152 },
1153 file_system_edits: [
1154 MoveFile {
1155 src: FileId(
1156 2,
1157 ),
1158 dst: AnchoredPathBuf {
1159 anchor: FileId(
1127 2, 1160 2,
1128 ), 1161 ),
1129 dst: AnchoredPathBuf { 1162 path: "foo2.rs",
1130 anchor: FileId(
1131 2,
1132 ),
1133 path: "foo2.rs",
1134 },
1135 }, 1163 },
1136 ], 1164 },
1137 is_snippet: false, 1165 ],
1138 }, 1166 is_snippet: false,
1139 } 1167 }
1140 "#]], 1168 "#]],
1141 ); 1169 );