aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/goto_type_definition.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src/goto_type_definition.rs')
-rw-r--r--crates/ide/src/goto_type_definition.rs151
1 files changed, 151 insertions, 0 deletions
diff --git a/crates/ide/src/goto_type_definition.rs b/crates/ide/src/goto_type_definition.rs
new file mode 100644
index 000000000..4a151b150
--- /dev/null
+++ b/crates/ide/src/goto_type_definition.rs
@@ -0,0 +1,151 @@
1use ide_db::RootDatabase;
2use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T};
3
4use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo};
5
6// Feature: Go to Type Definition
7//
8// Navigates to the type of an identifier.
9//
10// |===
11// | Editor | Action Name
12//
13// | VS Code | **Go to Type Definition*
14// |===
15pub(crate) fn goto_type_definition(
16 db: &RootDatabase,
17 position: FilePosition,
18) -> Option<RangeInfo<Vec<NavigationTarget>>> {
19 let sema = hir::Semantics::new(db);
20
21 let file: ast::SourceFile = sema.parse(position.file_id);
22 let token: SyntaxToken = pick_best(file.syntax().token_at_offset(position.offset))?;
23 let token: SyntaxToken = sema.descend_into_macros(token);
24
25 let (ty, node) = sema.ancestors_with_macros(token.parent()).find_map(|node| {
26 let ty = match_ast! {
27 match node {
28 ast::Expr(it) => sema.type_of_expr(&it)?,
29 ast::Pat(it) => sema.type_of_pat(&it)?,
30 ast::SelfParam(it) => sema.type_of_self(&it)?,
31 _ => return None,
32 }
33 };
34
35 Some((ty, node))
36 })?;
37
38 let adt_def = ty.autoderef(db).filter_map(|ty| ty.as_adt()).last()?;
39
40 let nav = adt_def.to_nav(db);
41 Some(RangeInfo::new(node.text_range(), vec![nav]))
42}
43
44fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
45 return tokens.max_by_key(priority);
46 fn priority(n: &SyntaxToken) -> usize {
47 match n.kind() {
48 IDENT | INT_NUMBER | T![self] => 2,
49 kind if kind.is_trivia() => 0,
50 _ => 1,
51 }
52 }
53}
54
55#[cfg(test)]
56mod tests {
57 use base_db::FileRange;
58
59 use crate::mock_analysis::MockAnalysis;
60
61 fn check(ra_fixture: &str) {
62 let (mock, position) = MockAnalysis::with_files_and_position(ra_fixture);
63 let (expected, data) = mock.annotation();
64 assert!(data.is_empty());
65 let analysis = mock.analysis();
66
67 let mut navs = analysis.goto_type_definition(position).unwrap().unwrap().info;
68 assert_eq!(navs.len(), 1);
69 let nav = navs.pop().unwrap();
70 assert_eq!(expected, FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() });
71 }
72
73 #[test]
74 fn goto_type_definition_works_simple() {
75 check(
76 r#"
77struct Foo;
78 //^^^
79fn foo() {
80 let f: Foo; f<|>
81}
82"#,
83 );
84 }
85
86 #[test]
87 fn goto_type_definition_works_simple_ref() {
88 check(
89 r#"
90struct Foo;
91 //^^^
92fn foo() {
93 let f: &Foo; f<|>
94}
95"#,
96 );
97 }
98
99 #[test]
100 fn goto_type_definition_works_through_macro() {
101 check(
102 r#"
103macro_rules! id { ($($tt:tt)*) => { $($tt)* } }
104struct Foo {}
105 //^^^
106id! {
107 fn bar() { let f<|> = Foo {}; }
108}
109"#,
110 );
111 }
112
113 #[test]
114 fn goto_type_definition_for_param() {
115 check(
116 r#"
117struct Foo;
118 //^^^
119fn foo(<|>f: Foo) {}
120"#,
121 );
122 }
123
124 #[test]
125 fn goto_type_definition_for_tuple_field() {
126 check(
127 r#"
128struct Foo;
129 //^^^
130struct Bar(Foo);
131fn foo() {
132 let bar = Bar(Foo);
133 bar.<|>0;
134}
135"#,
136 );
137 }
138
139 #[test]
140 fn goto_def_for_self_param() {
141 check(
142 r#"
143struct Foo;
144 //^^^
145impl Foo {
146 fn f(&self<|>) {}
147}
148"#,
149 )
150 }
151}