diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2021-01-18 19:00:04 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2021-01-18 19:00:04 +0000 |
commit | 39282ec419046d84ad5972d5b02b1af9885e1282 (patch) | |
tree | 124f7d3ee6a18bf22b9ba5d1fc78b34c5f1b8b2b | |
parent | 08efb8a94360735f505699021eaa901464c76f4a (diff) | |
parent | 6015a66cae81456d0de14c9bcff73c990c27dcb1 (diff) |
Merge #7332
7332: Rename reference search uses ide_db::search directly r=matklad a=Veykril
https://github.com/rust-analyzer/rust-analyzer/pull/7251#discussion_r556644241
Co-authored-by: Lukas Wirth <[email protected]>
-rw-r--r-- | crates/ide/src/diagnostics/fixes.rs | 2 | ||||
-rw-r--r-- | crates/ide/src/references/rename.rs | 372 |
2 files changed, 210 insertions, 164 deletions
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs index e4335119b..4b4afd40c 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.info, frange.range)) |
144 | } | 144 | } |
145 | } | 145 | } |
146 | 146 | ||
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs index 4df189c98..c5010df0a 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 |
2 | use std::fmt::{self, Display}; | 2 | use std::fmt::{self, Display}; |
3 | 3 | ||
4 | use hir::{Module, ModuleDef, ModuleSource, Semantics}; | 4 | use either::Either; |
5 | use hir::{HasSource, InFile, Module, ModuleDef, ModuleSource, Semantics}; | ||
5 | use ide_db::{ | 6 | use 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 | }; |
12 | use stdx::assert_never; | ||
11 | use syntax::{ | 13 | use 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 | }; |
16 | use test_utils::mark; | 17 | use test_utils::mark; |
17 | use text_edit::TextEdit; | 18 | use text_edit::TextEdit; |
18 | 19 | ||
19 | use crate::{ | 20 | use 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,13 +48,15 @@ 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 | ||
59 | pub(crate) fn rename( | 62 | pub(crate) fn rename( |
@@ -73,10 +76,11 @@ pub(crate) fn rename_with_semantics( | |||
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()?.info; | |
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 | ||
127 | fn find_module_at_offset( | 126 | enum NameLike { |
127 | Name(ast::Name), | ||
128 | NameRef(ast::NameRef), | ||
129 | Lifetime(ast::Lifetime), | ||
130 | } | ||
131 | |||
132 | fn 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 | ||
155 | fn find_all_refs( | 155 | fn 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 | ||
163 | fn source_edit_from_references( | 173 | fn source_edit_from_references( |
@@ -231,7 +241,6 @@ fn edit_text_range_for_record_field_expr_or_pat( | |||
231 | 241 | ||
232 | fn rename_mod( | 242 | fn 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<RangeInfo<SourceChange>> { |
@@ -241,62 +250,78 @@ fn rename_mod( | |||
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(RangeInfo::new(TextRange::default(), source_change)) |
277 | } | 285 | } |
278 | 286 | ||
279 | fn rename_to_self( | 287 | fn rename_to_self( |
280 | sema: &Semantics<RootDatabase>, | 288 | sema: &Semantics<RootDatabase>, |
281 | position: FilePosition, | 289 | local: hir::Local, |
282 | ) -> Result<RangeInfo<SourceChange>, RenameError> { | 290 | ) -> RenameResult<RangeInfo<SourceChange>> { |
283 | let source_file = sema.parse(position.file_id); | 291 | if assert_never!(local.is_self(sema.db)) { |
284 | let syn = source_file.syntax(); | 292 | bail!("rename_to_self invoked on self"); |
293 | } | ||
294 | |||
295 | let fn_def = match local.parent(sema.db) { | ||
296 | hir::DefWithBody::Function(func) => func, | ||
297 | _ => bail!("Cannot rename non-param local to self"), | ||
298 | }; | ||
299 | |||
300 | // FIXME: reimplement this on the hir instead | ||
301 | // as of the time of this writing params in hir don't keep their names | ||
302 | let fn_ast = | ||
303 | fn_def.source(sema.db).ok_or(format_err!("Cannot rename non-param local to self"))?.value; | ||
285 | 304 | ||
286 | let (fn_def, fn_ast) = find_node_at_offset::<ast::Fn>(syn, position.offset) | 305 | let first_param_range = fn_ast |
287 | .and_then(|fn_ast| sema.to_def(&fn_ast).zip(Some(fn_ast))) | ||
288 | .ok_or_else(|| format_err!("No surrounding method declaration found"))?; | ||
289 | let param_range = fn_ast | ||
290 | .param_list() | 306 | .param_list() |
291 | .and_then(|p| p.params().next()) | 307 | .and_then(|p| p.params().next()) |
292 | .ok_or_else(|| format_err!("Method has no parameters"))? | 308 | .ok_or_else(|| format_err!("Method has no parameters"))? |
293 | .syntax() | 309 | .syntax() |
294 | .text_range(); | 310 | .text_range(); |
295 | if !param_range.contains(position.offset) { | 311 | let InFile { file_id, value: local_source } = local.source(sema.db); |
296 | bail!("Only the first parameter can be self"); | 312 | match local_source { |
313 | either::Either::Left(pat) | ||
314 | if !first_param_range.contains_range(pat.syntax().text_range()) => | ||
315 | { | ||
316 | bail!("Only the first parameter can be self"); | ||
317 | } | ||
318 | _ => (), | ||
297 | } | 319 | } |
298 | 320 | ||
299 | let impl_block = find_node_at_offset::<ast::Impl>(syn, position.offset) | 321 | let impl_block = fn_ast |
322 | .syntax() | ||
323 | .ancestors() | ||
324 | .find_map(|node| ast::Impl::cast(node)) | ||
300 | .and_then(|def| sema.to_def(&def)) | 325 | .and_then(|def| sema.to_def(&def)) |
301 | .ok_or_else(|| format_err!("No impl block found for function"))?; | 326 | .ok_or_else(|| format_err!("No impl block found for function"))?; |
302 | if fn_def.self_param(sema.db).is_some() { | 327 | if fn_def.self_param(sema.db).is_some() { |
@@ -320,25 +345,21 @@ fn rename_to_self( | |||
320 | bail!("Parameter type differs from impl block type"); | 345 | bail!("Parameter type differs from impl block type"); |
321 | } | 346 | } |
322 | 347 | ||
323 | let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; | 348 | let def = Definition::Local(local); |
324 | 349 | let usages = def.usages(sema).all(); | |
325 | let mut source_change = SourceChange::default(); | 350 | let mut source_change = SourceChange::default(); |
326 | source_change.extend(refs.references().iter().map(|(&file_id, references)| { | 351 | source_change.extend(usages.iter().map(|(&file_id, references)| { |
327 | source_edit_from_references(sema, file_id, references, "self") | 352 | source_edit_from_references(sema, file_id, references, "self") |
328 | })); | 353 | })); |
329 | source_change.insert_source_edit( | 354 | source_change.insert_source_edit( |
330 | position.file_id, | 355 | file_id.original_file(sema.db), |
331 | TextEdit::replace(param_range, String::from(self_param)), | 356 | TextEdit::replace(first_param_range, String::from(self_param)), |
332 | ); | 357 | ); |
333 | 358 | ||
334 | Ok(RangeInfo::new(range, source_change)) | 359 | Ok(RangeInfo::new(TextRange::default(), source_change)) |
335 | } | 360 | } |
336 | 361 | ||
337 | fn text_edit_from_self_param( | 362 | fn 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> { | 363 | fn target_type_name(impl_def: &ast::Impl) -> Option<String> { |
343 | if let Some(ast::Type::PathType(p)) = impl_def.self_ty() { | 364 | if let Some(ast::Type::PathType(p)) = impl_def.self_ty() { |
344 | return Some(p.path()?.segment()?.name_ref()?.text().to_string()); | 365 | return Some(p.path()?.segment()?.name_ref()?.text().to_string()); |
@@ -346,7 +367,7 @@ fn text_edit_from_self_param( | |||
346 | None | 367 | None |
347 | } | 368 | } |
348 | 369 | ||
349 | let impl_def = find_node_at_offset::<ast::Impl>(syn, self_param.syntax().text_range().start())?; | 370 | let impl_def = self_param.syntax().ancestors().find_map(|it| ast::Impl::cast(it))?; |
350 | let type_name = target_type_name(&impl_def)?; | 371 | let type_name = target_type_name(&impl_def)?; |
351 | 372 | ||
352 | let mut replacement_text = String::from(new_name); | 373 | let mut replacement_text = String::from(new_name); |
@@ -363,94 +384,119 @@ fn text_edit_from_self_param( | |||
363 | 384 | ||
364 | fn rename_self_to_param( | 385 | fn rename_self_to_param( |
365 | sema: &Semantics<RootDatabase>, | 386 | sema: &Semantics<RootDatabase>, |
366 | position: FilePosition, | 387 | local: hir::Local, |
367 | new_name: &str, | 388 | new_name: &str, |
368 | ident_kind: IdentifierKind, | 389 | identifier_kind: IdentifierKind, |
369 | range: TextRange, | 390 | ) -> RenameResult<RangeInfo<SourceChange>> { |
370 | refs: ReferenceSearchResult, | 391 | let (file_id, self_param) = match local.source(sema.db) { |
371 | ) -> Result<RangeInfo<SourceChange>, RenameError> { | 392 | InFile { file_id, value: Either::Right(self_param) } => (file_id, self_param), |
372 | match ident_kind { | 393 | _ => { |
373 | IdentifierKind::Lifetime => bail!("Invalid name `{}`: not an identifier", new_name), | 394 | assert_never!(true, "rename_self_to_param invoked on a non-self local"); |
374 | IdentifierKind::ToSelf => { | 395 | 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 | } | 396 | } |
379 | _ => (), | 397 | }; |
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 | 398 | ||
407 | return Ok(RangeInfo::new(range, source_change)); | 399 | let def = Definition::Local(local); |
408 | } | 400 | let usages = def.usages(sema).all(); |
401 | let edit = text_edit_from_self_param(&self_param, new_name) | ||
402 | .ok_or_else(|| format_err!("No target type found"))?; | ||
403 | if usages.len() > 1 && identifier_kind == IdentifierKind::Underscore { | ||
404 | bail!("Cannot rename reference to `_` as it is being referenced multiple times"); | ||
409 | } | 405 | } |
410 | Err(format_err!("Method has no self param")) | 406 | let mut source_change = SourceChange::default(); |
407 | source_change.insert_source_edit(file_id.original_file(sema.db), edit); | ||
408 | source_change.extend(usages.iter().map(|(&file_id, references)| { | ||
409 | source_edit_from_references(sema, file_id, &references, new_name) | ||
410 | })); | ||
411 | Ok(RangeInfo::new(TextRange::default(), source_change)) | ||
411 | } | 412 | } |
412 | 413 | ||
413 | fn rename_reference( | 414 | fn rename_reference( |
414 | sema: &Semantics<RootDatabase>, | 415 | sema: &Semantics<RootDatabase>, |
415 | position: FilePosition, | 416 | def: Definition, |
416 | new_name: &str, | 417 | new_name: &str, |
417 | ) -> Result<RangeInfo<SourceChange>, RenameError> { | 418 | ) -> RenameResult<RangeInfo<SourceChange>> { |
418 | let ident_kind = check_identifier(new_name)?; | 419 | let ident_kind = check_identifier(new_name)?; |
419 | let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; | ||
420 | 420 | ||
421 | match (ident_kind, &refs.declaration.kind) { | 421 | let def_is_lbl_or_lt = matches!(def, |
422 | (IdentifierKind::ToSelf, ReferenceKind::Lifetime) | 422 | Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) |
423 | | (IdentifierKind::Underscore, ReferenceKind::Lifetime) | 423 | | Definition::Label(_) |
424 | | (IdentifierKind::Ident, ReferenceKind::Lifetime) => { | 424 | ); |
425 | match (ident_kind, def) { | ||
426 | (IdentifierKind::ToSelf, _) | ||
427 | | (IdentifierKind::Underscore, _) | ||
428 | | (IdentifierKind::Ident, _) | ||
429 | if def_is_lbl_or_lt => | ||
430 | { | ||
425 | mark::hit!(rename_not_a_lifetime_ident_ref); | 431 | mark::hit!(rename_not_a_lifetime_ident_ref); |
426 | bail!("Invalid name `{}`: not a lifetime identifier", new_name) | 432 | bail!("Invalid name `{}`: not a lifetime identifier", new_name) |
427 | } | 433 | } |
428 | (IdentifierKind::Lifetime, ReferenceKind::Lifetime) => mark::hit!(rename_lifetime), | 434 | (IdentifierKind::Lifetime, _) if def_is_lbl_or_lt => mark::hit!(rename_lifetime), |
429 | (IdentifierKind::Lifetime, _) => { | 435 | (IdentifierKind::Lifetime, _) => { |
430 | mark::hit!(rename_not_an_ident_ref); | 436 | mark::hit!(rename_not_an_ident_ref); |
431 | bail!("Invalid name `{}`: not an identifier", new_name) | 437 | bail!("Invalid name `{}`: not an identifier", new_name) |
432 | } | 438 | } |
433 | (_, ReferenceKind::SelfParam) => { | 439 | (IdentifierKind::ToSelf, Definition::Local(local)) if local.is_self(sema.db) => { |
440 | // no-op | ||
441 | mark::hit!(rename_self_to_self); | ||
442 | return Ok(RangeInfo::new(TextRange::default(), SourceChange::default())); | ||
443 | } | ||
444 | (ident_kind, Definition::Local(local)) if local.is_self(sema.db) => { | ||
434 | mark::hit!(rename_self_to_param); | 445 | mark::hit!(rename_self_to_param); |
435 | return rename_self_to_param(sema, position, new_name, ident_kind, range, refs); | 446 | return rename_self_to_param(sema, local, new_name, ident_kind); |
436 | } | 447 | } |
437 | (IdentifierKind::ToSelf, _) => { | 448 | (IdentifierKind::ToSelf, Definition::Local(local)) => { |
438 | mark::hit!(rename_to_self); | 449 | mark::hit!(rename_to_self); |
439 | return rename_to_self(sema, position); | 450 | 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 | } | 451 | } |
452 | (IdentifierKind::ToSelf, _) => bail!("Invalid name `{}`: not an identifier", new_name), | ||
445 | (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident), | 453 | (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident), |
446 | } | 454 | } |
447 | 455 | ||
456 | let usages = def.usages(sema).all(); | ||
457 | if !usages.is_empty() && ident_kind == IdentifierKind::Underscore { | ||
458 | mark::hit!(rename_underscore_multiple); | ||
459 | bail!("Cannot rename reference to `_` as it is being referenced multiple times"); | ||
460 | } | ||
448 | let mut source_change = SourceChange::default(); | 461 | let mut source_change = SourceChange::default(); |
449 | source_change.extend(refs.into_iter().map(|(file_id, references)| { | 462 | source_change.extend(usages.iter().map(|(&file_id, references)| { |
450 | source_edit_from_references(sema, file_id, &references, new_name) | 463 | source_edit_from_references(sema, file_id, &references, new_name) |
451 | })); | 464 | })); |
452 | 465 | ||
453 | Ok(RangeInfo::new(range, source_change)) | 466 | let (file_id, edit) = source_edit_from_def(sema, def, new_name)?; |
467 | source_change.insert_source_edit(file_id, edit); | ||
468 | Ok(RangeInfo::new(TextRange::default(), source_change)) | ||
469 | } | ||
470 | |||
471 | fn source_edit_from_def( | ||
472 | sema: &Semantics<RootDatabase>, | ||
473 | def: Definition, | ||
474 | new_name: &str, | ||
475 | ) -> RenameResult<(FileId, TextEdit)> { | ||
476 | let nav = def.try_to_nav(sema.db).unwrap(); | ||
477 | |||
478 | let mut replacement_text = String::new(); | ||
479 | let mut repl_range = nav.focus_or_full_range(); | ||
480 | if let Definition::Local(local) = def { | ||
481 | if let Either::Left(pat) = local.source(sema.db).value { | ||
482 | if matches!( | ||
483 | pat.syntax().parent().and_then(ast::RecordPatField::cast), | ||
484 | Some(pat_field) if pat_field.name_ref().is_none() | ||
485 | ) { | ||
486 | replacement_text.push_str(": "); | ||
487 | replacement_text.push_str(new_name); | ||
488 | repl_range = TextRange::new( | ||
489 | pat.syntax().text_range().end(), | ||
490 | pat.syntax().text_range().end(), | ||
491 | ); | ||
492 | } | ||
493 | } | ||
494 | } | ||
495 | if replacement_text.is_empty() { | ||
496 | replacement_text.push_str(new_name); | ||
497 | } | ||
498 | let edit = TextEdit::replace(repl_range, replacement_text); | ||
499 | Ok((nav.file_id, edit)) | ||
454 | } | 500 | } |
455 | 501 | ||
456 | #[cfg(test)] | 502 | #[cfg(test)] |
@@ -872,7 +918,7 @@ mod foo$0; | |||
872 | "#, | 918 | "#, |
873 | expect![[r#" | 919 | expect![[r#" |
874 | RangeInfo { | 920 | RangeInfo { |
875 | range: 4..7, | 921 | range: 0..0, |
876 | info: SourceChange { | 922 | info: SourceChange { |
877 | source_file_edits: { | 923 | source_file_edits: { |
878 | FileId( | 924 | FileId( |
@@ -924,7 +970,7 @@ use crate::foo$0::FooContent; | |||
924 | "#, | 970 | "#, |
925 | expect![[r#" | 971 | expect![[r#" |
926 | RangeInfo { | 972 | RangeInfo { |
927 | range: 11..14, | 973 | range: 0..0, |
928 | info: SourceChange { | 974 | info: SourceChange { |
929 | source_file_edits: { | 975 | source_file_edits: { |
930 | FileId( | 976 | FileId( |
@@ -980,7 +1026,7 @@ mod fo$0o; | |||
980 | "#, | 1026 | "#, |
981 | expect![[r#" | 1027 | expect![[r#" |
982 | RangeInfo { | 1028 | RangeInfo { |
983 | range: 4..7, | 1029 | range: 0..0, |
984 | info: SourceChange { | 1030 | info: SourceChange { |
985 | source_file_edits: { | 1031 | source_file_edits: { |
986 | FileId( | 1032 | FileId( |
@@ -1027,7 +1073,7 @@ mod outer { mod fo$0o; } | |||
1027 | "#, | 1073 | "#, |
1028 | expect![[r#" | 1074 | expect![[r#" |
1029 | RangeInfo { | 1075 | RangeInfo { |
1030 | range: 16..19, | 1076 | range: 0..0, |
1031 | info: SourceChange { | 1077 | info: SourceChange { |
1032 | source_file_edits: { | 1078 | source_file_edits: { |
1033 | FileId( | 1079 | FileId( |
@@ -1097,7 +1143,7 @@ pub mod foo$0; | |||
1097 | "#, | 1143 | "#, |
1098 | expect![[r#" | 1144 | expect![[r#" |
1099 | RangeInfo { | 1145 | RangeInfo { |
1100 | range: 8..11, | 1146 | range: 0..0, |
1101 | info: SourceChange { | 1147 | info: SourceChange { |
1102 | source_file_edits: { | 1148 | source_file_edits: { |
1103 | FileId( | 1149 | FileId( |