diff options
author | Aleksey Kladov <[email protected]> | 2021-06-14 16:46:27 +0100 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2021-06-14 16:46:54 +0100 |
commit | 26c978f258ed2af45a6979eefea9860c1eaeacda (patch) | |
tree | 6b96b1d2f5ba48148dcfd6442ab9c742696e7847 /crates/ide | |
parent | a91071b57be6e64ad2fd277998ada0ae6206457b (diff) |
internal: adapt diagnostics to the new rename API
Diffstat (limited to 'crates/ide')
-rw-r--r-- | crates/ide/src/references/rename.rs | 435 |
1 files changed, 12 insertions, 423 deletions
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs index 6b3d02bf4..88b6b1260 100644 --- a/crates/ide/src/references/rename.rs +++ b/crates/ide/src/references/rename.rs | |||
@@ -2,44 +2,23 @@ | |||
2 | //! | 2 | //! |
3 | //! All reference and file rename requests go through here where the corresponding [`SourceChange`]s | 3 | //! All reference and file rename requests go through here where the corresponding [`SourceChange`]s |
4 | //! will be calculated. | 4 | //! will be calculated. |
5 | use std::fmt::{self, Display}; | 5 | use hir::{AsAssocItem, InFile, Semantics}; |
6 | |||
7 | use either::Either; | ||
8 | use hir::{AsAssocItem, FieldSource, HasSource, InFile, ModuleSource, Semantics}; | ||
9 | use ide_db::{ | 6 | use ide_db::{ |
10 | base_db::{AnchoredPathBuf, FileId, FileRange}, | 7 | base_db::FileId, |
11 | defs::{Definition, NameClass, NameRefClass}, | 8 | defs::{Definition, NameClass, NameRefClass}, |
12 | search::FileReference, | 9 | rename::{bail, format_err, source_edit_from_references, IdentifierKind}, |
13 | RootDatabase, | 10 | RootDatabase, |
14 | }; | 11 | }; |
15 | use stdx::never; | 12 | use stdx::never; |
16 | use syntax::{ | 13 | use syntax::{ast, AstNode, SyntaxNode}; |
17 | ast::{self, NameOwner}, | ||
18 | lex_single_syntax_kind, AstNode, SyntaxKind, SyntaxNode, T, | ||
19 | }; | ||
20 | 14 | ||
21 | use text_edit::TextEdit; | 15 | use text_edit::TextEdit; |
22 | 16 | ||
23 | use crate::{FilePosition, FileSystemEdit, RangeInfo, SourceChange, TextRange}; | 17 | use crate::{FilePosition, RangeInfo, SourceChange}; |
24 | |||
25 | type RenameResult<T> = Result<T, RenameError>; | ||
26 | #[derive(Debug)] | ||
27 | pub struct RenameError(String); | ||
28 | |||
29 | impl fmt::Display for RenameError { | ||
30 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
31 | Display::fmt(&self.0, f) | ||
32 | } | ||
33 | } | ||
34 | 18 | ||
35 | macro_rules! format_err { | 19 | pub use ide_db::rename::RenameError; |
36 | ($fmt:expr) => {RenameError(format!($fmt))}; | ||
37 | ($fmt:expr, $($arg:tt)+) => {RenameError(format!($fmt, $($arg)+))} | ||
38 | } | ||
39 | 20 | ||
40 | macro_rules! bail { | 21 | type RenameResult<T> = Result<T, RenameError>; |
41 | ($($tokens:tt)*) => {return Err(format_err!($($tokens)*))} | ||
42 | } | ||
43 | 22 | ||
44 | /// Prepares a rename. The sole job of this function is to return the TextRange of the thing that is | 23 | /// Prepares a rename. The sole job of this function is to return the TextRange of the thing that is |
45 | /// being targeted for a rename. | 24 | /// being targeted for a rename. |
@@ -52,8 +31,8 @@ pub(crate) fn prepare_rename( | |||
52 | let syntax = source_file.syntax(); | 31 | let syntax = source_file.syntax(); |
53 | 32 | ||
54 | let def = find_definition(&sema, syntax, position)?; | 33 | let def = find_definition(&sema, syntax, position)?; |
55 | let frange = def_name_range(&&sema, def) | 34 | let frange = |
56 | .ok_or_else(|| format_err!("No references found at position"))?; | 35 | def.rename_range(&sema).ok_or_else(|| format_err!("No references found at position"))?; |
57 | Ok(RangeInfo::new(frange.range, ())) | 36 | Ok(RangeInfo::new(frange.range, ())) |
58 | } | 37 | } |
59 | 38 | ||
@@ -98,14 +77,7 @@ pub(crate) fn rename_with_semantics( | |||
98 | } | 77 | } |
99 | } | 78 | } |
100 | 79 | ||
101 | match def { | 80 | def.rename(sema, new_name) |
102 | Definition::ModuleDef(hir::ModuleDef::Module(module)) => rename_mod(sema, module, new_name), | ||
103 | Definition::SelfType(_) => bail!("Cannot rename `Self`"), | ||
104 | Definition::ModuleDef(hir::ModuleDef::BuiltinType(_)) => { | ||
105 | bail!("Cannot rename builtin type") | ||
106 | } | ||
107 | def => rename_reference(sema, def, new_name), | ||
108 | } | ||
109 | } | 81 | } |
110 | 82 | ||
111 | /// Called by the client when it is about to rename a file. | 83 | /// Called by the client when it is about to rename a file. |
@@ -116,38 +88,12 @@ pub(crate) fn will_rename_file( | |||
116 | ) -> Option<SourceChange> { | 88 | ) -> Option<SourceChange> { |
117 | let sema = Semantics::new(db); | 89 | let sema = Semantics::new(db); |
118 | let module = sema.to_module_def(file_id)?; | 90 | let module = sema.to_module_def(file_id)?; |
119 | let mut change = rename_mod(&sema, module, new_name_stem).ok()?; | 91 | let def = Definition::ModuleDef(module.into()); |
92 | let mut change = def.rename(&sema, new_name_stem).ok()?; | ||
120 | change.file_system_edits.clear(); | 93 | change.file_system_edits.clear(); |
121 | Some(change) | 94 | Some(change) |
122 | } | 95 | } |
123 | 96 | ||
124 | #[derive(Copy, Clone, Debug, PartialEq)] | ||
125 | enum IdentifierKind { | ||
126 | Ident, | ||
127 | Lifetime, | ||
128 | Underscore, | ||
129 | } | ||
130 | |||
131 | impl IdentifierKind { | ||
132 | fn classify(new_name: &str) -> RenameResult<IdentifierKind> { | ||
133 | match lex_single_syntax_kind(new_name) { | ||
134 | Some(res) => match res { | ||
135 | (SyntaxKind::IDENT, _) => Ok(IdentifierKind::Ident), | ||
136 | (T![_], _) => Ok(IdentifierKind::Underscore), | ||
137 | (SyntaxKind::LIFETIME_IDENT, _) if new_name != "'static" && new_name != "'_" => { | ||
138 | Ok(IdentifierKind::Lifetime) | ||
139 | } | ||
140 | (SyntaxKind::LIFETIME_IDENT, _) => { | ||
141 | bail!("Invalid name `{}`: not a lifetime identifier", new_name) | ||
142 | } | ||
143 | (_, Some(syntax_error)) => bail!("Invalid name `{}`: {}", new_name, syntax_error), | ||
144 | (_, None) => bail!("Invalid name `{}`: not an identifier", new_name), | ||
145 | }, | ||
146 | None => bail!("Invalid name `{}`: not an identifier", new_name), | ||
147 | } | ||
148 | } | ||
149 | } | ||
150 | |||
151 | fn find_definition( | 97 | fn find_definition( |
152 | sema: &Semantics<RootDatabase>, | 98 | sema: &Semantics<RootDatabase>, |
153 | syntax: &SyntaxNode, | 99 | syntax: &SyntaxNode, |
@@ -189,126 +135,6 @@ fn find_definition( | |||
189 | .ok_or_else(|| format_err!("No references found at position")) | 135 | .ok_or_else(|| format_err!("No references found at position")) |
190 | } | 136 | } |
191 | 137 | ||
192 | fn rename_mod( | ||
193 | sema: &Semantics<RootDatabase>, | ||
194 | module: hir::Module, | ||
195 | new_name: &str, | ||
196 | ) -> RenameResult<SourceChange> { | ||
197 | if IdentifierKind::classify(new_name)? != IdentifierKind::Ident { | ||
198 | bail!("Invalid name `{0}`: cannot rename module to {0}", new_name); | ||
199 | } | ||
200 | |||
201 | let mut source_change = SourceChange::default(); | ||
202 | |||
203 | let InFile { file_id, value: def_source } = module.definition_source(sema.db); | ||
204 | let file_id = file_id.original_file(sema.db); | ||
205 | if let ModuleSource::SourceFile(..) = def_source { | ||
206 | // mod is defined in path/to/dir/mod.rs | ||
207 | let path = if module.is_mod_rs(sema.db) { | ||
208 | format!("../{}/mod.rs", new_name) | ||
209 | } else { | ||
210 | format!("{}.rs", new_name) | ||
211 | }; | ||
212 | let dst = AnchoredPathBuf { anchor: file_id, path }; | ||
213 | let move_file = FileSystemEdit::MoveFile { src: file_id, dst }; | ||
214 | source_change.push_file_system_edit(move_file); | ||
215 | } | ||
216 | |||
217 | if let Some(InFile { file_id, value: decl_source }) = module.declaration_source(sema.db) { | ||
218 | let file_id = file_id.original_file(sema.db); | ||
219 | match decl_source.name() { | ||
220 | Some(name) => source_change.insert_source_edit( | ||
221 | file_id, | ||
222 | TextEdit::replace(name.syntax().text_range(), new_name.to_string()), | ||
223 | ), | ||
224 | _ => never!("Module source node is missing a name"), | ||
225 | } | ||
226 | } | ||
227 | let def = Definition::ModuleDef(hir::ModuleDef::Module(module)); | ||
228 | let usages = def.usages(sema).all(); | ||
229 | let ref_edits = usages.iter().map(|(&file_id, references)| { | ||
230 | (file_id, source_edit_from_references(references, def, new_name)) | ||
231 | }); | ||
232 | source_change.extend(ref_edits); | ||
233 | |||
234 | Ok(source_change) | ||
235 | } | ||
236 | |||
237 | fn rename_reference( | ||
238 | sema: &Semantics<RootDatabase>, | ||
239 | mut def: Definition, | ||
240 | new_name: &str, | ||
241 | ) -> RenameResult<SourceChange> { | ||
242 | let ident_kind = IdentifierKind::classify(new_name)?; | ||
243 | |||
244 | if matches!( | ||
245 | def, // is target a lifetime? | ||
246 | Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_) | ||
247 | ) { | ||
248 | match ident_kind { | ||
249 | IdentifierKind::Ident | IdentifierKind::Underscore => { | ||
250 | cov_mark::hit!(rename_not_a_lifetime_ident_ref); | ||
251 | bail!("Invalid name `{}`: not a lifetime identifier", new_name); | ||
252 | } | ||
253 | IdentifierKind::Lifetime => cov_mark::hit!(rename_lifetime), | ||
254 | } | ||
255 | } else { | ||
256 | match (ident_kind, def) { | ||
257 | (IdentifierKind::Lifetime, _) => { | ||
258 | cov_mark::hit!(rename_not_an_ident_ref); | ||
259 | bail!("Invalid name `{}`: not an identifier", new_name); | ||
260 | } | ||
261 | (IdentifierKind::Ident, _) => cov_mark::hit!(rename_non_local), | ||
262 | (IdentifierKind::Underscore, _) => (), | ||
263 | } | ||
264 | } | ||
265 | |||
266 | def = match def { | ||
267 | // HACK: resolve trait impl items to the item def of the trait definition | ||
268 | // so that we properly resolve all trait item references | ||
269 | Definition::ModuleDef(mod_def) => mod_def | ||
270 | .as_assoc_item(sema.db) | ||
271 | .and_then(|it| it.containing_trait_impl(sema.db)) | ||
272 | .and_then(|it| { | ||
273 | it.items(sema.db).into_iter().find_map(|it| match (it, mod_def) { | ||
274 | (hir::AssocItem::Function(trait_func), hir::ModuleDef::Function(func)) | ||
275 | if trait_func.name(sema.db) == func.name(sema.db) => | ||
276 | { | ||
277 | Some(Definition::ModuleDef(hir::ModuleDef::Function(trait_func))) | ||
278 | } | ||
279 | (hir::AssocItem::Const(trait_konst), hir::ModuleDef::Const(konst)) | ||
280 | if trait_konst.name(sema.db) == konst.name(sema.db) => | ||
281 | { | ||
282 | Some(Definition::ModuleDef(hir::ModuleDef::Const(trait_konst))) | ||
283 | } | ||
284 | ( | ||
285 | hir::AssocItem::TypeAlias(trait_type_alias), | ||
286 | hir::ModuleDef::TypeAlias(type_alias), | ||
287 | ) if trait_type_alias.name(sema.db) == type_alias.name(sema.db) => { | ||
288 | Some(Definition::ModuleDef(hir::ModuleDef::TypeAlias(trait_type_alias))) | ||
289 | } | ||
290 | _ => None, | ||
291 | }) | ||
292 | }) | ||
293 | .unwrap_or(def), | ||
294 | _ => def, | ||
295 | }; | ||
296 | let usages = def.usages(sema).all(); | ||
297 | |||
298 | if !usages.is_empty() && ident_kind == IdentifierKind::Underscore { | ||
299 | cov_mark::hit!(rename_underscore_multiple); | ||
300 | bail!("Cannot rename reference to `_` as it is being referenced multiple times"); | ||
301 | } | ||
302 | let mut source_change = SourceChange::default(); | ||
303 | source_change.extend(usages.iter().map(|(&file_id, references)| { | ||
304 | (file_id, source_edit_from_references(references, def, new_name)) | ||
305 | })); | ||
306 | |||
307 | let (file_id, edit) = source_edit_from_def(sema, def, new_name)?; | ||
308 | source_change.insert_source_edit(file_id, edit); | ||
309 | Ok(source_change) | ||
310 | } | ||
311 | |||
312 | fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameResult<SourceChange> { | 138 | fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameResult<SourceChange> { |
313 | if never!(local.is_self(sema.db)) { | 139 | if never!(local.is_self(sema.db)) { |
314 | bail!("rename_to_self invoked on self"); | 140 | bail!("rename_to_self invoked on self"); |
@@ -426,243 +252,6 @@ fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Opt | |||
426 | Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text)) | 252 | Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text)) |
427 | } | 253 | } |
428 | 254 | ||
429 | fn source_edit_from_references( | ||
430 | references: &[FileReference], | ||
431 | def: Definition, | ||
432 | new_name: &str, | ||
433 | ) -> TextEdit { | ||
434 | let mut edit = TextEdit::builder(); | ||
435 | for reference in references { | ||
436 | let (range, replacement) = match &reference.name { | ||
437 | // if the ranges differ then the node is inside a macro call, we can't really attempt | ||
438 | // to make special rewrites like shorthand syntax and such, so just rename the node in | ||
439 | // the macro input | ||
440 | ast::NameLike::NameRef(name_ref) | ||
441 | if name_ref.syntax().text_range() == reference.range => | ||
442 | { | ||
443 | source_edit_from_name_ref(name_ref, new_name, def) | ||
444 | } | ||
445 | ast::NameLike::Name(name) if name.syntax().text_range() == reference.range => { | ||
446 | source_edit_from_name(name, new_name) | ||
447 | } | ||
448 | _ => None, | ||
449 | } | ||
450 | .unwrap_or_else(|| (reference.range, new_name.to_string())); | ||
451 | edit.replace(range, replacement); | ||
452 | } | ||
453 | edit.finish() | ||
454 | } | ||
455 | |||
456 | fn source_edit_from_name(name: &ast::Name, new_name: &str) -> Option<(TextRange, String)> { | ||
457 | if let Some(_) = ast::RecordPatField::for_field_name(name) { | ||
458 | if let Some(ident_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) { | ||
459 | return Some(( | ||
460 | TextRange::empty(ident_pat.syntax().text_range().start()), | ||
461 | [new_name, ": "].concat(), | ||
462 | )); | ||
463 | } | ||
464 | } | ||
465 | None | ||
466 | } | ||
467 | |||
468 | fn source_edit_from_name_ref( | ||
469 | name_ref: &ast::NameRef, | ||
470 | new_name: &str, | ||
471 | def: Definition, | ||
472 | ) -> Option<(TextRange, String)> { | ||
473 | if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) { | ||
474 | let rcf_name_ref = record_field.name_ref(); | ||
475 | let rcf_expr = record_field.expr(); | ||
476 | match (rcf_name_ref, rcf_expr.and_then(|it| it.name_ref())) { | ||
477 | // field: init-expr, check if we can use a field init shorthand | ||
478 | (Some(field_name), Some(init)) => { | ||
479 | if field_name == *name_ref { | ||
480 | if init.text() == new_name { | ||
481 | cov_mark::hit!(test_rename_field_put_init_shorthand); | ||
482 | // same names, we can use a shorthand here instead. | ||
483 | // we do not want to erase attributes hence this range start | ||
484 | let s = field_name.syntax().text_range().start(); | ||
485 | let e = record_field.syntax().text_range().end(); | ||
486 | return Some((TextRange::new(s, e), new_name.to_owned())); | ||
487 | } | ||
488 | } else if init == *name_ref { | ||
489 | if field_name.text() == new_name { | ||
490 | cov_mark::hit!(test_rename_local_put_init_shorthand); | ||
491 | // same names, we can use a shorthand here instead. | ||
492 | // we do not want to erase attributes hence this range start | ||
493 | let s = field_name.syntax().text_range().start(); | ||
494 | let e = record_field.syntax().text_range().end(); | ||
495 | return Some((TextRange::new(s, e), new_name.to_owned())); | ||
496 | } | ||
497 | } | ||
498 | None | ||
499 | } | ||
500 | // init shorthand | ||
501 | // FIXME: instead of splitting the shorthand, recursively trigger a rename of the | ||
502 | // other name https://github.com/rust-analyzer/rust-analyzer/issues/6547 | ||
503 | (None, Some(_)) if matches!(def, Definition::Field(_)) => { | ||
504 | cov_mark::hit!(test_rename_field_in_field_shorthand); | ||
505 | let s = name_ref.syntax().text_range().start(); | ||
506 | Some((TextRange::empty(s), format!("{}: ", new_name))) | ||
507 | } | ||
508 | (None, Some(_)) if matches!(def, Definition::Local(_)) => { | ||
509 | cov_mark::hit!(test_rename_local_in_field_shorthand); | ||
510 | let s = name_ref.syntax().text_range().end(); | ||
511 | Some((TextRange::empty(s), format!(": {}", new_name))) | ||
512 | } | ||
513 | _ => None, | ||
514 | } | ||
515 | } else if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) { | ||
516 | let rcf_name_ref = record_field.name_ref(); | ||
517 | let rcf_pat = record_field.pat(); | ||
518 | match (rcf_name_ref, rcf_pat) { | ||
519 | // field: rename | ||
520 | (Some(field_name), Some(ast::Pat::IdentPat(pat))) if field_name == *name_ref => { | ||
521 | // field name is being renamed | ||
522 | if pat.name().map_or(false, |it| it.text() == new_name) { | ||
523 | cov_mark::hit!(test_rename_field_put_init_shorthand_pat); | ||
524 | // same names, we can use a shorthand here instead/ | ||
525 | // we do not want to erase attributes hence this range start | ||
526 | let s = field_name.syntax().text_range().start(); | ||
527 | let e = record_field.syntax().text_range().end(); | ||
528 | Some((TextRange::new(s, e), pat.to_string())) | ||
529 | } else { | ||
530 | None | ||
531 | } | ||
532 | } | ||
533 | _ => None, | ||
534 | } | ||
535 | } else { | ||
536 | None | ||
537 | } | ||
538 | } | ||
539 | |||
540 | fn source_edit_from_def( | ||
541 | sema: &Semantics<RootDatabase>, | ||
542 | def: Definition, | ||
543 | new_name: &str, | ||
544 | ) -> RenameResult<(FileId, TextEdit)> { | ||
545 | let frange: FileRange = def_name_range(sema, def) | ||
546 | .ok_or_else(|| format_err!("No identifier available to rename"))?; | ||
547 | |||
548 | let mut replacement_text = String::new(); | ||
549 | let mut repl_range = frange.range; | ||
550 | if let Definition::Local(local) = def { | ||
551 | if let Either::Left(pat) = local.source(sema.db).value { | ||
552 | if matches!( | ||
553 | pat.syntax().parent().and_then(ast::RecordPatField::cast), | ||
554 | Some(pat_field) if pat_field.name_ref().is_none() | ||
555 | ) { | ||
556 | replacement_text.push_str(": "); | ||
557 | replacement_text.push_str(new_name); | ||
558 | repl_range = TextRange::new( | ||
559 | pat.syntax().text_range().end(), | ||
560 | pat.syntax().text_range().end(), | ||
561 | ); | ||
562 | } | ||
563 | } | ||
564 | } | ||
565 | if replacement_text.is_empty() { | ||
566 | replacement_text.push_str(new_name); | ||
567 | } | ||
568 | let edit = TextEdit::replace(repl_range, replacement_text); | ||
569 | Ok((frange.file_id, edit)) | ||
570 | } | ||
571 | |||
572 | fn def_name_range(sema: &Semantics<RootDatabase>, def: Definition) -> Option<FileRange> { | ||
573 | // FIXME: the `original_file_range` calls here are wrong -- they never fail, | ||
574 | // and _fall back_ to the entirety of the macro call. Such fall back is | ||
575 | // incorrect for renames. The safe behavior would be to return an error for | ||
576 | // such cases. The correct behavior would be to return an auxiliary list of | ||
577 | // "can't rename these occurrences in macros" items, and then show some kind | ||
578 | // of a dialog to the user. | ||
579 | |||
580 | let res = match def { | ||
581 | Definition::Macro(mac) => { | ||
582 | let src = mac.source(sema.db)?; | ||
583 | let name = match &src.value { | ||
584 | Either::Left(it) => it.name()?, | ||
585 | Either::Right(it) => it.name()?, | ||
586 | }; | ||
587 | src.with_value(name.syntax()).original_file_range(sema.db) | ||
588 | } | ||
589 | Definition::Field(field) => { | ||
590 | let src = field.source(sema.db)?; | ||
591 | |||
592 | match &src.value { | ||
593 | FieldSource::Named(record_field) => { | ||
594 | let name = record_field.name()?; | ||
595 | src.with_value(name.syntax()).original_file_range(sema.db) | ||
596 | } | ||
597 | FieldSource::Pos(_) => { | ||
598 | return None; | ||
599 | } | ||
600 | } | ||
601 | } | ||
602 | Definition::ModuleDef(module_def) => match module_def { | ||
603 | hir::ModuleDef::Module(module) => { | ||
604 | let src = module.declaration_source(sema.db)?; | ||
605 | let name = src.value.name()?; | ||
606 | src.with_value(name.syntax()).original_file_range(sema.db) | ||
607 | } | ||
608 | hir::ModuleDef::Function(it) => name_range(it, sema)?, | ||
609 | hir::ModuleDef::Adt(adt) => match adt { | ||
610 | hir::Adt::Struct(it) => name_range(it, sema)?, | ||
611 | hir::Adt::Union(it) => name_range(it, sema)?, | ||
612 | hir::Adt::Enum(it) => name_range(it, sema)?, | ||
613 | }, | ||
614 | hir::ModuleDef::Variant(it) => name_range(it, sema)?, | ||
615 | hir::ModuleDef::Const(it) => name_range(it, sema)?, | ||
616 | hir::ModuleDef::Static(it) => name_range(it, sema)?, | ||
617 | hir::ModuleDef::Trait(it) => name_range(it, sema)?, | ||
618 | hir::ModuleDef::TypeAlias(it) => name_range(it, sema)?, | ||
619 | hir::ModuleDef::BuiltinType(_) => return None, | ||
620 | }, | ||
621 | Definition::SelfType(_) => return None, | ||
622 | Definition::Local(local) => { | ||
623 | let src = local.source(sema.db); | ||
624 | let name = match &src.value { | ||
625 | Either::Left(bind_pat) => bind_pat.name()?, | ||
626 | Either::Right(_) => return None, | ||
627 | }; | ||
628 | src.with_value(name.syntax()).original_file_range(sema.db) | ||
629 | } | ||
630 | Definition::GenericParam(generic_param) => match generic_param { | ||
631 | hir::GenericParam::TypeParam(type_param) => { | ||
632 | let src = type_param.source(sema.db)?; | ||
633 | let name = match &src.value { | ||
634 | Either::Left(_) => return None, | ||
635 | Either::Right(type_param) => type_param.name()?, | ||
636 | }; | ||
637 | src.with_value(name.syntax()).original_file_range(sema.db) | ||
638 | } | ||
639 | hir::GenericParam::LifetimeParam(lifetime_param) => { | ||
640 | let src = lifetime_param.source(sema.db)?; | ||
641 | let lifetime = src.value.lifetime()?; | ||
642 | src.with_value(lifetime.syntax()).original_file_range(sema.db) | ||
643 | } | ||
644 | hir::GenericParam::ConstParam(it) => name_range(it, sema)?, | ||
645 | }, | ||
646 | Definition::Label(label) => { | ||
647 | let src = label.source(sema.db); | ||
648 | let lifetime = src.value.lifetime()?; | ||
649 | src.with_value(lifetime.syntax()).original_file_range(sema.db) | ||
650 | } | ||
651 | }; | ||
652 | return Some(res); | ||
653 | |||
654 | fn name_range<D>(def: D, sema: &Semantics<RootDatabase>) -> Option<FileRange> | ||
655 | where | ||
656 | D: HasSource, | ||
657 | D::Ast: ast::NameOwner, | ||
658 | { | ||
659 | let src = def.source(sema.db)?; | ||
660 | let name = src.value.name()?; | ||
661 | let res = src.with_value(name.syntax()).original_file_range(sema.db); | ||
662 | Some(res) | ||
663 | } | ||
664 | } | ||
665 | |||
666 | #[cfg(test)] | 255 | #[cfg(test)] |
667 | mod tests { | 256 | mod tests { |
668 | use expect_test::{expect, Expect}; | 257 | use expect_test::{expect, Expect}; |