diff options
author | Lukas Wirth <[email protected]> | 2020-12-16 20:35:15 +0000 |
---|---|---|
committer | Lukas Wirth <[email protected]> | 2020-12-16 21:21:01 +0000 |
commit | 55faa2daa3fc8bd213038a012b1c5e9ad5fd3736 (patch) | |
tree | 2d84a7fa9fe93aa7a6bdd4d0f9cda87e6bb4a5da | |
parent | 067067a6c11bb5afda98f5af14bfdec4744e7812 (diff) |
Lifetime reference search
-rw-r--r-- | crates/ide/src/display/navigation_target.rs | 20 | ||||
-rw-r--r-- | crates/ide/src/doc_links.rs | 5 | ||||
-rw-r--r-- | crates/ide/src/goto_definition.rs | 57 | ||||
-rw-r--r-- | crates/ide/src/hover.rs | 2 | ||||
-rw-r--r-- | crates/ide/src/lib.rs | 7 | ||||
-rw-r--r-- | crates/ide/src/references.rs | 91 | ||||
-rw-r--r-- | crates/ide/src/references/rename.rs | 104 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting.rs | 1 | ||||
-rw-r--r-- | crates/ide_db/src/defs.rs | 59 | ||||
-rw-r--r-- | crates/ide_db/src/search.rs | 63 | ||||
-rw-r--r-- | crates/ide_db/src/symbol_index.rs | 3 | ||||
-rw-r--r-- | crates/rust-analyzer/src/handlers.rs | 2 |
12 files changed, 373 insertions, 41 deletions
diff --git a/crates/ide/src/display/navigation_target.rs b/crates/ide/src/display/navigation_target.rs index 234f80a3a..ce0f4214c 100644 --- a/crates/ide/src/display/navigation_target.rs +++ b/crates/ide/src/display/navigation_target.rs | |||
@@ -9,7 +9,7 @@ use ide_db::{defs::Definition, RootDatabase}; | |||
9 | use syntax::{ | 9 | use syntax::{ |
10 | ast::{self, NameOwner}, | 10 | ast::{self, NameOwner}, |
11 | match_ast, AstNode, SmolStr, | 11 | match_ast, AstNode, SmolStr, |
12 | SyntaxKind::{self, IDENT_PAT, TYPE_PARAM}, | 12 | SyntaxKind::{self, IDENT_PAT, LIFETIME_PARAM, TYPE_PARAM}, |
13 | TextRange, | 13 | TextRange, |
14 | }; | 14 | }; |
15 | 15 | ||
@@ -182,6 +182,7 @@ impl TryToNav for Definition { | |||
182 | Definition::SelfType(it) => Some(it.to_nav(db)), | 182 | Definition::SelfType(it) => Some(it.to_nav(db)), |
183 | Definition::Local(it) => Some(it.to_nav(db)), | 183 | Definition::Local(it) => Some(it.to_nav(db)), |
184 | Definition::TypeParam(it) => Some(it.to_nav(db)), | 184 | Definition::TypeParam(it) => Some(it.to_nav(db)), |
185 | Definition::LifetimeParam(it) => Some(it.to_nav(db)), | ||
185 | } | 186 | } |
186 | } | 187 | } |
187 | } | 188 | } |
@@ -376,6 +377,23 @@ impl ToNav for hir::TypeParam { | |||
376 | } | 377 | } |
377 | } | 378 | } |
378 | 379 | ||
380 | impl ToNav for hir::LifetimeParam { | ||
381 | fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { | ||
382 | let src = self.source(db); | ||
383 | let full_range = src.value.syntax().text_range(); | ||
384 | NavigationTarget { | ||
385 | file_id: src.file_id.original_file(db), | ||
386 | name: self.name(db).to_string().into(), | ||
387 | kind: LIFETIME_PARAM, | ||
388 | full_range, | ||
389 | focus_range: Some(full_range), | ||
390 | container_name: None, | ||
391 | description: None, | ||
392 | docs: None, | ||
393 | } | ||
394 | } | ||
395 | } | ||
396 | |||
379 | pub(crate) fn docs_from_symbol(db: &RootDatabase, symbol: &FileSymbol) -> Option<Documentation> { | 397 | pub(crate) fn docs_from_symbol(db: &RootDatabase, symbol: &FileSymbol) -> Option<Documentation> { |
380 | let parse = db.parse(symbol.file_id); | 398 | let parse = db.parse(symbol.file_id); |
381 | let node = symbol.ptr.to_node(parse.tree().syntax()); | 399 | let node = symbol.ptr.to_node(parse.tree().syntax()); |
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs index 10263537a..2b5794a31 100644 --- a/crates/ide/src/doc_links.rs +++ b/crates/ide/src/doc_links.rs | |||
@@ -190,7 +190,10 @@ fn rewrite_intra_doc_link( | |||
190 | }, | 190 | }, |
191 | Definition::Macro(it) => it.resolve_doc_path(db, link, ns), | 191 | Definition::Macro(it) => it.resolve_doc_path(db, link, ns), |
192 | Definition::Field(it) => it.resolve_doc_path(db, link, ns), | 192 | Definition::Field(it) => it.resolve_doc_path(db, link, ns), |
193 | Definition::SelfType(_) | Definition::Local(_) | Definition::TypeParam(_) => return None, | 193 | Definition::SelfType(_) |
194 | | Definition::Local(_) | ||
195 | | Definition::TypeParam(_) | ||
196 | | Definition::LifetimeParam(_) => return None, | ||
194 | }?; | 197 | }?; |
195 | let krate = resolved.module(db)?.krate(); | 198 | let krate = resolved.module(db)?.krate(); |
196 | let canonical_path = resolved.canonical_path(db)?; | 199 | let canonical_path = resolved.canonical_path(db)?; |
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index b9810457f..173509b08 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs | |||
@@ -1,3 +1,4 @@ | |||
1 | use either::Either; | ||
1 | use hir::Semantics; | 2 | use hir::Semantics; |
2 | use ide_db::{ | 3 | use ide_db::{ |
3 | base_db::FileId, | 4 | base_db::FileId, |
@@ -33,7 +34,7 @@ pub(crate) fn goto_definition( | |||
33 | let nav_targets = match_ast! { | 34 | let nav_targets = match_ast! { |
34 | match parent { | 35 | match parent { |
35 | ast::NameRef(name_ref) => { | 36 | ast::NameRef(name_ref) => { |
36 | reference_definition(&sema, &name_ref).to_vec() | 37 | reference_definition(&sema, Either::Right(&name_ref)).to_vec() |
37 | }, | 38 | }, |
38 | ast::Name(name) => { | 39 | ast::Name(name) => { |
39 | let def = NameClass::classify(&sema, &name)?.referenced_or_defined(sema.db); | 40 | let def = NameClass::classify(&sema, &name)?.referenced_or_defined(sema.db); |
@@ -53,6 +54,13 @@ pub(crate) fn goto_definition( | |||
53 | let self_param = func.param_list()?.self_param()?; | 54 | let self_param = func.param_list()?.self_param()?; |
54 | vec![self_to_nav_target(self_param, position.file_id)?] | 55 | vec![self_to_nav_target(self_param, position.file_id)?] |
55 | }, | 56 | }, |
57 | ast::Lifetime(lt) => if let Some(name_class) = NameClass::classify_lifetime(&sema, <) { | ||
58 | let def = name_class.referenced_or_defined(sema.db); | ||
59 | let nav = def.try_to_nav(sema.db)?; | ||
60 | vec![nav] | ||
61 | } else { | ||
62 | reference_definition(&sema, Either::Left(<)).to_vec() | ||
63 | }, | ||
56 | _ => return None, | 64 | _ => return None, |
57 | } | 65 | } |
58 | }; | 66 | }; |
@@ -64,7 +72,7 @@ fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { | |||
64 | return tokens.max_by_key(priority); | 72 | return tokens.max_by_key(priority); |
65 | fn priority(n: &SyntaxToken) -> usize { | 73 | fn priority(n: &SyntaxToken) -> usize { |
66 | match n.kind() { | 74 | match n.kind() { |
67 | IDENT | INT_NUMBER | T![self] => 2, | 75 | IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] => 2, |
68 | kind if kind.is_trivia() => 0, | 76 | kind if kind.is_trivia() => 0, |
69 | _ => 1, | 77 | _ => 1, |
70 | } | 78 | } |
@@ -102,9 +110,12 @@ impl ReferenceResult { | |||
102 | 110 | ||
103 | pub(crate) fn reference_definition( | 111 | pub(crate) fn reference_definition( |
104 | sema: &Semantics<RootDatabase>, | 112 | sema: &Semantics<RootDatabase>, |
105 | name_ref: &ast::NameRef, | 113 | name_ref: Either<&ast::Lifetime, &ast::NameRef>, |
106 | ) -> ReferenceResult { | 114 | ) -> ReferenceResult { |
107 | let name_kind = NameRefClass::classify(sema, name_ref); | 115 | let name_kind = name_ref.either( |
116 | |lifetime| NameRefClass::classify_lifetime(sema, lifetime), | ||
117 | |name_ref| NameRefClass::classify(sema, name_ref), | ||
118 | ); | ||
108 | if let Some(def) = name_kind { | 119 | if let Some(def) = name_kind { |
109 | let def = def.referenced(sema.db); | 120 | let def = def.referenced(sema.db); |
110 | return match def.try_to_nav(sema.db) { | 121 | return match def.try_to_nav(sema.db) { |
@@ -114,10 +125,9 @@ pub(crate) fn reference_definition( | |||
114 | } | 125 | } |
115 | 126 | ||
116 | // Fallback index based approach: | 127 | // Fallback index based approach: |
117 | let navs = symbol_index::index_resolve(sema.db, name_ref) | 128 | let name = name_ref.either(ast::Lifetime::text, ast::NameRef::text); |
118 | .into_iter() | 129 | let navs = |
119 | .map(|s| s.to_nav(sema.db)) | 130 | symbol_index::index_resolve(sema.db, name).into_iter().map(|s| s.to_nav(sema.db)).collect(); |
120 | .collect(); | ||
121 | ReferenceResult::Approximate(navs) | 131 | ReferenceResult::Approximate(navs) |
122 | } | 132 | } |
123 | 133 | ||
@@ -1036,4 +1046,35 @@ impl Foo { | |||
1036 | }"#, | 1046 | }"#, |
1037 | ) | 1047 | ) |
1038 | } | 1048 | } |
1049 | |||
1050 | #[test] | ||
1051 | fn goto_lifetime_param_on_decl() { | ||
1052 | check( | ||
1053 | r#" | ||
1054 | fn foo<'foobar<|>>(_: &'foobar ()) { | ||
1055 | //^^^^^^^ | ||
1056 | }"#, | ||
1057 | ) | ||
1058 | } | ||
1059 | |||
1060 | #[test] | ||
1061 | fn goto_lifetime_param_decl() { | ||
1062 | check( | ||
1063 | r#" | ||
1064 | fn foo<'foobar>(_: &'foobar<|> ()) { | ||
1065 | //^^^^^^^ | ||
1066 | }"#, | ||
1067 | ) | ||
1068 | } | ||
1069 | |||
1070 | #[test] | ||
1071 | fn goto_lifetime_param_decl_nested() { | ||
1072 | check( | ||
1073 | r#" | ||
1074 | fn foo<'foobar>(_: &'foobar ()) { | ||
1075 | fn foo<'foobar>(_: &'foobar<|> ()) {} | ||
1076 | //^^^^^^^ | ||
1077 | }"#, | ||
1078 | ) | ||
1079 | } | ||
1039 | } | 1080 | } |
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index ab017d2ad..a01b0c894 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs | |||
@@ -364,7 +364,7 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> { | |||
364 | Adt::Enum(it) => from_def_source(db, it, mod_path), | 364 | Adt::Enum(it) => from_def_source(db, it, mod_path), |
365 | }) | 365 | }) |
366 | } | 366 | } |
367 | Definition::TypeParam(_) => { | 367 | Definition::TypeParam(_) | Definition::LifetimeParam(_) => { |
368 | // FIXME: Hover for generic param | 368 | // FIXME: Hover for generic param |
369 | None | 369 | None |
370 | } | 370 | } |
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 71068cac2..c5c652cda 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs | |||
@@ -528,6 +528,13 @@ impl Analysis { | |||
528 | self.with_db(|db| references::rename::rename(db, position, new_name)) | 528 | self.with_db(|db| references::rename::rename(db, position, new_name)) |
529 | } | 529 | } |
530 | 530 | ||
531 | pub fn prepare_rename( | ||
532 | &self, | ||
533 | position: FilePosition, | ||
534 | ) -> Cancelable<Result<RangeInfo<()>, RenameError>> { | ||
535 | self.with_db(|db| references::rename::prepare_rename(db, position)) | ||
536 | } | ||
537 | |||
531 | pub fn structural_search_replace( | 538 | pub fn structural_search_replace( |
532 | &self, | 539 | &self, |
533 | query: &str, | 540 | query: &str, |
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index 675957fff..98190a86b 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs | |||
@@ -130,6 +130,8 @@ pub(crate) fn find_all_refs( | |||
130 | kind = ReferenceKind::FieldShorthandForLocal; | 130 | kind = ReferenceKind::FieldShorthandForLocal; |
131 | } | 131 | } |
132 | } | 132 | } |
133 | } else if let Definition::LifetimeParam(_) = def { | ||
134 | kind = ReferenceKind::Lifetime; | ||
133 | }; | 135 | }; |
134 | 136 | ||
135 | let declaration = Declaration { nav, kind, access: decl_access(&def, &syntax, decl_range) }; | 137 | let declaration = Declaration { nav, kind, access: decl_access(&def, &syntax, decl_range) }; |
@@ -148,11 +150,29 @@ fn find_name( | |||
148 | let range = name.syntax().text_range(); | 150 | let range = name.syntax().text_range(); |
149 | return Some(RangeInfo::new(range, def)); | 151 | return Some(RangeInfo::new(range, def)); |
150 | } | 152 | } |
151 | let name_ref = | 153 | |
152 | sema.find_node_at_offset_with_descend::<ast::NameRef>(&syntax, position.offset)?; | 154 | let (text_range, def) = if let Some(lifetime) = |
153 | let def = NameRefClass::classify(sema, &name_ref)?.referenced(sema.db); | 155 | sema.find_node_at_offset_with_descend::<ast::Lifetime>(&syntax, position.offset) |
154 | let range = name_ref.syntax().text_range(); | 156 | { |
155 | Some(RangeInfo::new(range, def)) | 157 | if let Some(def) = NameRefClass::classify_lifetime(sema, &lifetime) |
158 | .map(|class| NameRefClass::referenced(class, sema.db)) | ||
159 | { | ||
160 | (lifetime.syntax().text_range(), def) | ||
161 | } else { | ||
162 | ( | ||
163 | lifetime.syntax().text_range(), | ||
164 | NameClass::classify_lifetime(sema, &lifetime)?.referenced_or_defined(sema.db), | ||
165 | ) | ||
166 | } | ||
167 | } else { | ||
168 | let name_ref = | ||
169 | sema.find_node_at_offset_with_descend::<ast::NameRef>(&syntax, position.offset)?; | ||
170 | ( | ||
171 | name_ref.syntax().text_range(), | ||
172 | NameRefClass::classify(sema, &name_ref)?.referenced(sema.db), | ||
173 | ) | ||
174 | }; | ||
175 | Some(RangeInfo::new(text_range, def)) | ||
156 | } | 176 | } |
157 | 177 | ||
158 | fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Option<ReferenceAccess> { | 178 | fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Option<ReferenceAccess> { |
@@ -1005,4 +1025,65 @@ impl Foo { | |||
1005 | } | 1025 | } |
1006 | expect.assert_eq(&actual) | 1026 | expect.assert_eq(&actual) |
1007 | } | 1027 | } |
1028 | |||
1029 | #[test] | ||
1030 | fn test_find_lifetimes_function() { | ||
1031 | check( | ||
1032 | r#" | ||
1033 | trait Foo<'a> {} | ||
1034 | impl<'a> Foo<'a> for &'a () {} | ||
1035 | fn foo<'a, 'b: 'a>(x: &'a<|> ()) -> &'a () where &'a (): Foo<'a> { | ||
1036 | fn bar<'a>(_: &'a ()) {} | ||
1037 | x | ||
1038 | } | ||
1039 | "#, | ||
1040 | expect![[r#" | ||
1041 | 'a LIFETIME_PARAM FileId(0) 55..57 55..57 Lifetime | ||
1042 | |||
1043 | FileId(0) 63..65 Lifetime | ||
1044 | FileId(0) 71..73 Lifetime | ||
1045 | FileId(0) 82..84 Lifetime | ||
1046 | FileId(0) 95..97 Lifetime | ||
1047 | FileId(0) 106..108 Lifetime | ||
1048 | "#]], | ||
1049 | ); | ||
1050 | } | ||
1051 | |||
1052 | #[test] | ||
1053 | fn test_find_lifetimes_type_alias() { | ||
1054 | check( | ||
1055 | r#" | ||
1056 | type Foo<'a, T> where T: 'a<|> = &'a T; | ||
1057 | "#, | ||
1058 | expect![[r#" | ||
1059 | 'a LIFETIME_PARAM FileId(0) 9..11 9..11 Lifetime | ||
1060 | |||
1061 | FileId(0) 25..27 Lifetime | ||
1062 | FileId(0) 31..33 Lifetime | ||
1063 | "#]], | ||
1064 | ); | ||
1065 | } | ||
1066 | |||
1067 | #[test] | ||
1068 | fn test_find_lifetimes_trait_impl() { | ||
1069 | check( | ||
1070 | r#" | ||
1071 | trait Foo<'a> { | ||
1072 | fn foo() -> &'a (); | ||
1073 | } | ||
1074 | impl<'a> Foo<'a> for &'a () { | ||
1075 | fn foo() -> &'a<|> () { | ||
1076 | unimplemented!() | ||
1077 | } | ||
1078 | } | ||
1079 | "#, | ||
1080 | expect![[r#" | ||
1081 | 'a LIFETIME_PARAM FileId(0) 47..49 47..49 Lifetime | ||
1082 | |||
1083 | FileId(0) 55..57 Lifetime | ||
1084 | FileId(0) 64..66 Lifetime | ||
1085 | FileId(0) 89..91 Lifetime | ||
1086 | "#]], | ||
1087 | ); | ||
1088 | } | ||
1008 | } | 1089 | } |
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs index 44081f210..56e923841 100644 --- a/crates/ide/src/references/rename.rs +++ b/crates/ide/src/references/rename.rs | |||
@@ -35,6 +35,29 @@ impl fmt::Display for RenameError { | |||
35 | 35 | ||
36 | impl Error for RenameError {} | 36 | impl Error for RenameError {} |
37 | 37 | ||
38 | pub(crate) fn prepare_rename( | ||
39 | db: &RootDatabase, | ||
40 | position: FilePosition, | ||
41 | ) -> Result<RangeInfo<()>, RenameError> { | ||
42 | let sema = Semantics::new(db); | ||
43 | let source_file = sema.parse(position.file_id); | ||
44 | let syntax = source_file.syntax(); | ||
45 | if let Some(module) = find_module_at_offset(&sema, position, syntax) { | ||
46 | rename_mod(&sema, position, module, "dummy") | ||
47 | } else if let Some(self_token) = | ||
48 | syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW) | ||
49 | { | ||
50 | rename_self_to_param(&sema, position, self_token, "dummy") | ||
51 | } else { | ||
52 | let range = match find_all_refs(&sema, position, None) { | ||
53 | Some(RangeInfo { range, .. }) => range, | ||
54 | None => return Err(RenameError("No references found at position".to_string())), | ||
55 | }; | ||
56 | Ok(RangeInfo::new(range, SourceChange::from(vec![]))) | ||
57 | } | ||
58 | .map(|info| RangeInfo::new(info.range, ())) | ||
59 | } | ||
60 | |||
38 | pub(crate) fn rename( | 61 | pub(crate) fn rename( |
39 | db: &RootDatabase, | 62 | db: &RootDatabase, |
40 | position: FilePosition, | 63 | position: FilePosition, |
@@ -49,11 +72,18 @@ pub(crate) fn rename_with_semantics( | |||
49 | position: FilePosition, | 72 | position: FilePosition, |
50 | new_name: &str, | 73 | new_name: &str, |
51 | ) -> Result<RangeInfo<SourceChange>, RenameError> { | 74 | ) -> Result<RangeInfo<SourceChange>, RenameError> { |
52 | match lex_single_syntax_kind(new_name) { | 75 | let is_lifetime_name = match lex_single_syntax_kind(new_name) { |
53 | Some(res) => match res { | 76 | Some(res) => match res { |
54 | (SyntaxKind::IDENT, _) => (), | 77 | (SyntaxKind::IDENT, _) => false, |
55 | (SyntaxKind::UNDERSCORE, _) => (), | 78 | (SyntaxKind::UNDERSCORE, _) => false, |
56 | (SyntaxKind::SELF_KW, _) => return rename_to_self(&sema, position), | 79 | (SyntaxKind::SELF_KW, _) => return rename_to_self(&sema, position), |
80 | (SyntaxKind::LIFETIME_IDENT, _) if new_name != "'static" && new_name != "'_" => true, | ||
81 | (SyntaxKind::LIFETIME_IDENT, _) => { | ||
82 | return Err(RenameError(format!( | ||
83 | "Invalid name `{0}`: Cannot rename lifetime to {0}", | ||
84 | new_name | ||
85 | ))) | ||
86 | } | ||
57 | (_, Some(syntax_error)) => { | 87 | (_, Some(syntax_error)) => { |
58 | return Err(RenameError(format!("Invalid name `{}`: {}", new_name, syntax_error))) | 88 | return Err(RenameError(format!("Invalid name `{}`: {}", new_name, syntax_error))) |
59 | } | 89 | } |
@@ -62,18 +92,21 @@ pub(crate) fn rename_with_semantics( | |||
62 | } | 92 | } |
63 | }, | 93 | }, |
64 | None => return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))), | 94 | None => return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))), |
65 | } | 95 | }; |
66 | 96 | ||
67 | let source_file = sema.parse(position.file_id); | 97 | let source_file = sema.parse(position.file_id); |
68 | let syntax = source_file.syntax(); | 98 | let syntax = source_file.syntax(); |
69 | if let Some(module) = find_module_at_offset(&sema, position, syntax) { | 99 | // this is here to prevent lifetime renames from happening on modules and self |
100 | if is_lifetime_name { | ||
101 | rename_reference(&sema, position, new_name, is_lifetime_name) | ||
102 | } else if let Some(module) = find_module_at_offset(&sema, position, syntax) { | ||
70 | rename_mod(&sema, position, module, new_name) | 103 | rename_mod(&sema, position, module, new_name) |
71 | } else if let Some(self_token) = | 104 | } else if let Some(self_token) = |
72 | syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW) | 105 | syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW) |
73 | { | 106 | { |
74 | rename_self_to_param(&sema, position, self_token, new_name) | 107 | rename_self_to_param(&sema, position, self_token, new_name) |
75 | } else { | 108 | } else { |
76 | rename_reference(&sema, position, new_name) | 109 | rename_reference(&sema, position, new_name, is_lifetime_name) |
77 | } | 110 | } |
78 | } | 111 | } |
79 | 112 | ||
@@ -355,12 +388,26 @@ fn rename_reference( | |||
355 | sema: &Semantics<RootDatabase>, | 388 | sema: &Semantics<RootDatabase>, |
356 | position: FilePosition, | 389 | position: FilePosition, |
357 | new_name: &str, | 390 | new_name: &str, |
391 | is_lifetime_name: bool, | ||
358 | ) -> Result<RangeInfo<SourceChange>, RenameError> { | 392 | ) -> Result<RangeInfo<SourceChange>, RenameError> { |
359 | let RangeInfo { range, info: refs } = match find_all_refs(sema, position, None) { | 393 | let RangeInfo { range, info: refs } = match find_all_refs(sema, position, None) { |
360 | Some(range_info) => range_info, | 394 | Some(range_info) => range_info, |
361 | None => return Err(RenameError("No references found at position".to_string())), | 395 | None => return Err(RenameError("No references found at position".to_string())), |
362 | }; | 396 | }; |
363 | 397 | ||
398 | match (refs.declaration.kind == ReferenceKind::Lifetime, is_lifetime_name) { | ||
399 | (true, false) => { | ||
400 | return Err(RenameError(format!( | ||
401 | "Invalid name `{}`: not a lifetime identifier", | ||
402 | new_name | ||
403 | ))) | ||
404 | } | ||
405 | (false, true) => { | ||
406 | return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))) | ||
407 | } | ||
408 | _ => (), | ||
409 | } | ||
410 | |||
364 | let edit = refs | 411 | let edit = refs |
365 | .into_iter() | 412 | .into_iter() |
366 | .map(|reference| source_edit_from_reference(sema, reference, new_name)) | 413 | .map(|reference| source_edit_from_reference(sema, reference, new_name)) |
@@ -465,6 +512,24 @@ mod tests { | |||
465 | } | 512 | } |
466 | 513 | ||
467 | #[test] | 514 | #[test] |
515 | fn test_rename_to_invalid_identifier_lifetime() { | ||
516 | check( | ||
517 | "'foo", | ||
518 | r#"fn main() { let i<|> = 1; }"#, | ||
519 | "error: Invalid name `'foo`: not an identifier", | ||
520 | ); | ||
521 | } | ||
522 | |||
523 | #[test] | ||
524 | fn test_rename_to_invalid_identifier_lifetime2() { | ||
525 | check( | ||
526 | "foo", | ||
527 | r#"fn main<'a>(_: &'a<|> ()) {}"#, | ||
528 | "error: Invalid name `foo`: not a lifetime identifier", | ||
529 | ); | ||
530 | } | ||
531 | |||
532 | #[test] | ||
468 | fn test_rename_for_local() { | 533 | fn test_rename_for_local() { |
469 | check( | 534 | check( |
470 | "k", | 535 | "k", |
@@ -1396,4 +1461,31 @@ fn foo(Foo { i: bar }: foo) -> i32 { | |||
1396 | "#, | 1461 | "#, |
1397 | ) | 1462 | ) |
1398 | } | 1463 | } |
1464 | |||
1465 | #[test] | ||
1466 | fn test_rename_lifetimes() { | ||
1467 | check( | ||
1468 | "'yeeee", | ||
1469 | r#" | ||
1470 | trait Foo<'a> { | ||
1471 | fn foo() -> &'a (); | ||
1472 | } | ||
1473 | impl<'a> Foo<'a> for &'a () { | ||
1474 | fn foo() -> &'a<|> () { | ||
1475 | unimplemented!() | ||
1476 | } | ||
1477 | } | ||
1478 | "#, | ||
1479 | r#" | ||
1480 | trait Foo<'a> { | ||
1481 | fn foo() -> &'a (); | ||
1482 | } | ||
1483 | impl<'yeeee> Foo<'yeeee> for &'yeeee () { | ||
1484 | fn foo() -> &'yeeee () { | ||
1485 | unimplemented!() | ||
1486 | } | ||
1487 | } | ||
1488 | "#, | ||
1489 | ) | ||
1490 | } | ||
1399 | } | 1491 | } |
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs index 990b0f7d9..488969f1a 100644 --- a/crates/ide/src/syntax_highlighting.rs +++ b/crates/ide/src/syntax_highlighting.rs | |||
@@ -806,6 +806,7 @@ fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight { | |||
806 | } | 806 | } |
807 | return h; | 807 | return h; |
808 | } | 808 | } |
809 | Definition::LifetimeParam(_) => HighlightTag::Lifetime, | ||
809 | } | 810 | } |
810 | .into() | 811 | .into() |
811 | } | 812 | } |
diff --git a/crates/ide_db/src/defs.rs b/crates/ide_db/src/defs.rs index d4a774261..f2d1e4c39 100644 --- a/crates/ide_db/src/defs.rs +++ b/crates/ide_db/src/defs.rs | |||
@@ -6,12 +6,12 @@ | |||
6 | // FIXME: this badly needs rename/rewrite (matklad, 2020-02-06). | 6 | // FIXME: this badly needs rename/rewrite (matklad, 2020-02-06). |
7 | 7 | ||
8 | use hir::{ | 8 | use hir::{ |
9 | db::HirDatabase, Crate, Field, HasVisibility, ImplDef, Local, MacroDef, Module, ModuleDef, | 9 | db::HirDatabase, Crate, Field, HasVisibility, ImplDef, LifetimeParam, Local, MacroDef, Module, |
10 | Name, PathResolution, Semantics, TypeParam, Visibility, | 10 | ModuleDef, Name, PathResolution, Semantics, TypeParam, Visibility, |
11 | }; | 11 | }; |
12 | use syntax::{ | 12 | use syntax::{ |
13 | ast::{self, AstNode}, | 13 | ast::{self, AstNode}, |
14 | match_ast, SyntaxNode, | 14 | match_ast, SyntaxKind, SyntaxNode, |
15 | }; | 15 | }; |
16 | 16 | ||
17 | use crate::RootDatabase; | 17 | use crate::RootDatabase; |
@@ -25,6 +25,8 @@ pub enum Definition { | |||
25 | SelfType(ImplDef), | 25 | SelfType(ImplDef), |
26 | Local(Local), | 26 | Local(Local), |
27 | TypeParam(TypeParam), | 27 | TypeParam(TypeParam), |
28 | LifetimeParam(LifetimeParam), | ||
29 | // FIXME: Label | ||
28 | } | 30 | } |
29 | 31 | ||
30 | impl Definition { | 32 | impl Definition { |
@@ -36,6 +38,7 @@ impl Definition { | |||
36 | Definition::SelfType(it) => Some(it.module(db)), | 38 | Definition::SelfType(it) => Some(it.module(db)), |
37 | Definition::Local(it) => Some(it.module(db)), | 39 | Definition::Local(it) => Some(it.module(db)), |
38 | Definition::TypeParam(it) => Some(it.module(db)), | 40 | Definition::TypeParam(it) => Some(it.module(db)), |
41 | Definition::LifetimeParam(it) => Some(it.module(db)), | ||
39 | } | 42 | } |
40 | } | 43 | } |
41 | 44 | ||
@@ -47,6 +50,7 @@ impl Definition { | |||
47 | Definition::SelfType(_) => None, | 50 | Definition::SelfType(_) => None, |
48 | Definition::Local(_) => None, | 51 | Definition::Local(_) => None, |
49 | Definition::TypeParam(_) => None, | 52 | Definition::TypeParam(_) => None, |
53 | Definition::LifetimeParam(_) => None, | ||
50 | } | 54 | } |
51 | } | 55 | } |
52 | 56 | ||
@@ -72,6 +76,7 @@ impl Definition { | |||
72 | Definition::SelfType(_) => return None, | 76 | Definition::SelfType(_) => return None, |
73 | Definition::Local(it) => it.name(db)?, | 77 | Definition::Local(it) => it.name(db)?, |
74 | Definition::TypeParam(it) => it.name(db), | 78 | Definition::TypeParam(it) => it.name(db), |
79 | Definition::LifetimeParam(it) => it.name(db), | ||
75 | }; | 80 | }; |
76 | Some(name) | 81 | Some(name) |
77 | } | 82 | } |
@@ -229,6 +234,25 @@ impl NameClass { | |||
229 | } | 234 | } |
230 | } | 235 | } |
231 | } | 236 | } |
237 | |||
238 | pub fn classify_lifetime( | ||
239 | sema: &Semantics<RootDatabase>, | ||
240 | lifetime: &ast::Lifetime, | ||
241 | ) -> Option<NameClass> { | ||
242 | let _p = profile::span("classify_lifetime").detail(|| lifetime.to_string()); | ||
243 | let parent = lifetime.syntax().parent()?; | ||
244 | |||
245 | match_ast! { | ||
246 | match parent { | ||
247 | ast::LifetimeParam(it) => { | ||
248 | let def = sema.to_def(&it)?; | ||
249 | Some(NameClass::Definition(Definition::LifetimeParam(def))) | ||
250 | }, | ||
251 | ast::Label(_it) => None, | ||
252 | _ => None, | ||
253 | } | ||
254 | } | ||
255 | } | ||
232 | } | 256 | } |
233 | 257 | ||
234 | #[derive(Debug)] | 258 | #[derive(Debug)] |
@@ -338,6 +362,35 @@ impl NameRefClass { | |||
338 | let resolved = sema.resolve_extern_crate(&extern_crate)?; | 362 | let resolved = sema.resolve_extern_crate(&extern_crate)?; |
339 | Some(NameRefClass::ExternCrate(resolved)) | 363 | Some(NameRefClass::ExternCrate(resolved)) |
340 | } | 364 | } |
365 | |||
366 | pub fn classify_lifetime( | ||
367 | sema: &Semantics<RootDatabase>, | ||
368 | lifetime: &ast::Lifetime, | ||
369 | ) -> Option<NameRefClass> { | ||
370 | let _p = profile::span("classify_lifetime_ref").detail(|| lifetime.to_string()); | ||
371 | let parent = lifetime.syntax().parent()?; | ||
372 | match parent.kind() { | ||
373 | SyntaxKind::LIFETIME_ARG | ||
374 | | SyntaxKind::SELF_PARAM | ||
375 | | SyntaxKind::TYPE_BOUND | ||
376 | | SyntaxKind::WHERE_PRED | ||
377 | | SyntaxKind::REF_TYPE => sema | ||
378 | .resolve_lifetime_param(lifetime) | ||
379 | .map(Definition::LifetimeParam) | ||
380 | .map(NameRefClass::Definition), | ||
381 | // lifetime bounds, as in the 'b in 'a: 'b aren't wrapped in TypeBound nodes so we gotta check | ||
382 | // if our lifetime is in a LifetimeParam without being the constrained lifetime | ||
383 | _ if ast::LifetimeParam::cast(parent).and_then(|param| param.lifetime()).as_ref() | ||
384 | != Some(lifetime) => | ||
385 | { | ||
386 | sema.resolve_lifetime_param(lifetime) | ||
387 | .map(Definition::LifetimeParam) | ||
388 | .map(NameRefClass::Definition) | ||
389 | } | ||
390 | SyntaxKind::BREAK_EXPR | SyntaxKind::CONTINUE_EXPR => None, | ||
391 | _ => None, | ||
392 | } | ||
393 | } | ||
341 | } | 394 | } |
342 | 395 | ||
343 | impl From<PathResolution> for Definition { | 396 | impl From<PathResolution> for Definition { |
diff --git a/crates/ide_db/src/search.rs b/crates/ide_db/src/search.rs index 3936c7390..5b3997bcf 100644 --- a/crates/ide_db/src/search.rs +++ b/crates/ide_db/src/search.rs | |||
@@ -33,6 +33,7 @@ pub enum ReferenceKind { | |||
33 | RecordFieldExprOrPat, | 33 | RecordFieldExprOrPat, |
34 | SelfKw, | 34 | SelfKw, |
35 | EnumLiteral, | 35 | EnumLiteral, |
36 | Lifetime, | ||
36 | Other, | 37 | Other, |
37 | } | 38 | } |
38 | 39 | ||
@@ -129,6 +130,25 @@ impl Definition { | |||
129 | return SearchScope::new(res); | 130 | return SearchScope::new(res); |
130 | } | 131 | } |
131 | 132 | ||
133 | if let Definition::LifetimeParam(param) = self { | ||
134 | let range = match param.parent(db) { | ||
135 | hir::GenericDef::Function(it) => it.source(db).value.syntax().text_range(), | ||
136 | hir::GenericDef::Adt(it) => match it { | ||
137 | hir::Adt::Struct(it) => it.source(db).value.syntax().text_range(), | ||
138 | hir::Adt::Union(it) => it.source(db).value.syntax().text_range(), | ||
139 | hir::Adt::Enum(it) => it.source(db).value.syntax().text_range(), | ||
140 | }, | ||
141 | hir::GenericDef::Trait(it) => it.source(db).value.syntax().text_range(), | ||
142 | hir::GenericDef::TypeAlias(it) => it.source(db).value.syntax().text_range(), | ||
143 | hir::GenericDef::ImplDef(it) => it.source(db).value.syntax().text_range(), | ||
144 | hir::GenericDef::EnumVariant(it) => it.source(db).value.syntax().text_range(), | ||
145 | hir::GenericDef::Const(it) => it.source(db).value.syntax().text_range(), | ||
146 | }; | ||
147 | let mut res = FxHashMap::default(); | ||
148 | res.insert(file_id, Some(range)); | ||
149 | return SearchScope::new(res); | ||
150 | } | ||
151 | |||
132 | let vis = self.visibility(db); | 152 | let vis = self.visibility(db); |
133 | 153 | ||
134 | if let Some(Visibility::Module(module)) = vis.and_then(|it| it.into()) { | 154 | if let Some(Visibility::Module(module)) = vis.and_then(|it| it.into()) { |
@@ -255,25 +275,42 @@ impl<'a> FindUsages<'a> { | |||
255 | continue; | 275 | continue; |
256 | } | 276 | } |
257 | 277 | ||
258 | match sema.find_node_at_offset_with_descend(&tree, offset) { | 278 | if let Some(name_ref) = sema.find_node_at_offset_with_descend(&tree, offset) { |
259 | Some(name_ref) => { | 279 | if self.found_name_ref(&name_ref, sink) { |
260 | if self.found_name_ref(&name_ref, sink) { | 280 | return; |
261 | return; | 281 | } |
262 | } | 282 | } else if let Some(name) = sema.find_node_at_offset_with_descend(&tree, offset) { |
283 | if self.found_name(&name, sink) { | ||
284 | return; | ||
285 | } | ||
286 | } else if let Some(lifetime) = sema.find_node_at_offset_with_descend(&tree, offset) | ||
287 | { | ||
288 | if self.found_lifetime(&lifetime, sink) { | ||
289 | return; | ||
263 | } | 290 | } |
264 | None => match sema.find_node_at_offset_with_descend(&tree, offset) { | ||
265 | Some(name) => { | ||
266 | if self.found_name(&name, sink) { | ||
267 | return; | ||
268 | } | ||
269 | } | ||
270 | None => {} | ||
271 | }, | ||
272 | } | 291 | } |
273 | } | 292 | } |
274 | } | 293 | } |
275 | } | 294 | } |
276 | 295 | ||
296 | fn found_lifetime( | ||
297 | &self, | ||
298 | lifetime: &ast::Lifetime, | ||
299 | sink: &mut dyn FnMut(Reference) -> bool, | ||
300 | ) -> bool { | ||
301 | match NameRefClass::classify_lifetime(self.sema, lifetime) { | ||
302 | Some(NameRefClass::Definition(def)) if &def == self.def => { | ||
303 | let reference = Reference { | ||
304 | file_range: self.sema.original_range(lifetime.syntax()), | ||
305 | kind: ReferenceKind::Lifetime, | ||
306 | access: None, | ||
307 | }; | ||
308 | sink(reference) | ||
309 | } | ||
310 | _ => false, // not a usage | ||
311 | } | ||
312 | } | ||
313 | |||
277 | fn found_name_ref( | 314 | fn found_name_ref( |
278 | &self, | 315 | &self, |
279 | name_ref: &ast::NameRef, | 316 | name_ref: &ast::NameRef, |
diff --git a/crates/ide_db/src/symbol_index.rs b/crates/ide_db/src/symbol_index.rs index 121063aea..ca455fa03 100644 --- a/crates/ide_db/src/symbol_index.rs +++ b/crates/ide_db/src/symbol_index.rs | |||
@@ -209,8 +209,7 @@ pub fn crate_symbols(db: &RootDatabase, krate: CrateId, query: Query) -> Vec<Fil | |||
209 | query.search(&buf) | 209 | query.search(&buf) |
210 | } | 210 | } |
211 | 211 | ||
212 | pub fn index_resolve(db: &RootDatabase, name_ref: &ast::NameRef) -> Vec<FileSymbol> { | 212 | pub fn index_resolve(db: &RootDatabase, name: &SmolStr) -> Vec<FileSymbol> { |
213 | let name = name_ref.text(); | ||
214 | let mut query = Query::new(name.to_string()); | 213 | let mut query = Query::new(name.to_string()); |
215 | query.exact(); | 214 | query.exact(); |
216 | query.limit(4); | 215 | query.limit(4); |
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 94e2bfa1b..af226c109 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs | |||
@@ -733,7 +733,7 @@ pub(crate) fn handle_prepare_rename( | |||
733 | let _p = profile::span("handle_prepare_rename"); | 733 | let _p = profile::span("handle_prepare_rename"); |
734 | let position = from_proto::file_position(&snap, params)?; | 734 | let position = from_proto::file_position(&snap, params)?; |
735 | 735 | ||
736 | let change = snap.analysis.rename(position, "dummy")??; | 736 | let change = snap.analysis.prepare_rename(position)??; |
737 | let line_index = snap.analysis.file_line_index(position.file_id)?; | 737 | let line_index = snap.analysis.file_line_index(position.file_id)?; |
738 | let range = to_proto::range(&line_index, change.range); | 738 | let range = to_proto::range(&line_index, change.range); |
739 | Ok(Some(PrepareRenameResponse::Range(range))) | 739 | Ok(Some(PrepareRenameResponse::Range(range))) |