aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src')
-rw-r--r--crates/ide/src/display/navigation_target.rs20
-rw-r--r--crates/ide/src/doc_links.rs5
-rw-r--r--crates/ide/src/goto_definition.rs57
-rw-r--r--crates/ide/src/hover.rs2
-rw-r--r--crates/ide/src/lib.rs7
-rw-r--r--crates/ide/src/references.rs91
-rw-r--r--crates/ide/src/references/rename.rs104
-rw-r--r--crates/ide/src/syntax_highlighting.rs1
8 files changed, 265 insertions, 22 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};
9use syntax::{ 9use 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
380impl 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
379pub(crate) fn docs_from_symbol(db: &RootDatabase, symbol: &FileSymbol) -> Option<Documentation> { 397pub(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 @@
1use either::Either;
1use hir::Semantics; 2use hir::Semantics;
2use ide_db::{ 3use 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, &lt) {
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(&lt)).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
103pub(crate) fn reference_definition( 111pub(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#"
1054fn foo<'foobar<|>>(_: &'foobar ()) {
1055 //^^^^^^^
1056}"#,
1057 )
1058 }
1059
1060 #[test]
1061 fn goto_lifetime_param_decl() {
1062 check(
1063 r#"
1064fn foo<'foobar>(_: &'foobar<|> ()) {
1065 //^^^^^^^
1066}"#,
1067 )
1068 }
1069
1070 #[test]
1071 fn goto_lifetime_param_decl_nested() {
1072 check(
1073 r#"
1074fn 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
158fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Option<ReferenceAccess> { 178fn 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#"
1033trait Foo<'a> {}
1034impl<'a> Foo<'a> for &'a () {}
1035fn 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#"
1056type 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#"
1071trait Foo<'a> {
1072 fn foo() -> &'a ();
1073}
1074impl<'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
36impl Error for RenameError {} 36impl Error for RenameError {}
37 37
38pub(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
38pub(crate) fn rename( 61pub(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#"
1470trait Foo<'a> {
1471 fn foo() -> &'a ();
1472}
1473impl<'a> Foo<'a> for &'a () {
1474 fn foo() -> &'a<|> () {
1475 unimplemented!()
1476 }
1477}
1478"#,
1479 r#"
1480trait Foo<'a> {
1481 fn foo() -> &'a ();
1482}
1483impl<'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}