]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs
Rollup merge of #100228 - luqmana:suggestion-ice, r=estebank
[rust.git] / src / tools / rust-analyzer / crates / ide / src / goto_type_definition.rs
1 use ide_db::{base_db::Upcast, defs::Definition, helpers::pick_best_token, RootDatabase};
2 use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, T};
3
4 use crate::{FilePosition, NavigationTarget, RangeInfo, TryToNav};
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 // |===
15 //
16 // image::https://user-images.githubusercontent.com/48062697/113020657-b560f500-917a-11eb-9007-0f809733a338.gif[]
17 pub(crate) fn goto_type_definition(
18     db: &RootDatabase,
19     position: FilePosition,
20 ) -> Option<RangeInfo<Vec<NavigationTarget>>> {
21     let sema = hir::Semantics::new(db);
22
23     let file: ast::SourceFile = sema.parse(position.file_id);
24     let token: SyntaxToken =
25         pick_best_token(file.syntax().token_at_offset(position.offset), |kind| match kind {
26             IDENT | INT_NUMBER | T![self] => 2,
27             kind if kind.is_trivia() => 0,
28             _ => 1,
29         })?;
30
31     let mut res = Vec::new();
32     let mut push = |def: Definition| {
33         if let Some(nav) = def.try_to_nav(db) {
34             if !res.contains(&nav) {
35                 res.push(nav);
36             }
37         }
38     };
39     let range = token.text_range();
40     sema.descend_into_macros(token)
41         .iter()
42         .filter_map(|token| {
43             let ty = sema.token_ancestors_with_macros(token.clone()).find_map(|node| {
44                 let ty = match_ast! {
45                     match node {
46                         ast::Expr(it) => sema.type_of_expr(&it)?.original,
47                         ast::Pat(it) => sema.type_of_pat(&it)?.original,
48                         ast::SelfParam(it) => sema.type_of_self(&it)?,
49                         ast::Type(it) => sema.resolve_type(&it)?,
50                         ast::RecordField(it) => sema.to_def(&it).map(|d| d.ty(db.upcast()))?,
51                         // can't match on RecordExprField directly as `ast::Expr` will match an iteration too early otherwise
52                         ast::NameRef(it) => {
53                             if let Some(record_field) = ast::RecordExprField::for_name_ref(&it) {
54                                 let (_, _, ty) = sema.resolve_record_field(&record_field)?;
55                                 ty
56                             } else {
57                                 let record_field = ast::RecordPatField::for_field_name_ref(&it)?;
58                                 sema.resolve_record_pat_field(&record_field)?.ty(db)
59                             }
60                         },
61                         _ => return None,
62                     }
63                 };
64
65                 Some(ty)
66             });
67             ty
68         })
69         .for_each(|ty| {
70             // collect from each `ty` into the `res` result vec
71             let ty = ty.strip_references();
72             ty.walk(db, |t| {
73                 if let Some(adt) = t.as_adt() {
74                     push(adt.into());
75                 } else if let Some(trait_) = t.as_dyn_trait() {
76                     push(trait_.into());
77                 } else if let Some(traits) = t.as_impl_traits(db) {
78                     traits.for_each(|it| push(it.into()));
79                 } else if let Some(trait_) = t.as_associated_type_parent_trait(db) {
80                     push(trait_.into());
81                 }
82             });
83         });
84     Some(RangeInfo::new(range, res))
85 }
86
87 #[cfg(test)]
88 mod tests {
89     use ide_db::base_db::FileRange;
90     use itertools::Itertools;
91
92     use crate::fixture;
93
94     fn check(ra_fixture: &str) {
95         let (analysis, position, expected) = fixture::annotations(ra_fixture);
96         let navs = analysis.goto_type_definition(position).unwrap().unwrap().info;
97         assert_ne!(navs.len(), 0);
98
99         let cmp = |&FileRange { file_id, range }: &_| (file_id, range.start());
100         let navs = navs
101             .into_iter()
102             .map(|nav| FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
103             .sorted_by_key(cmp)
104             .collect::<Vec<_>>();
105         let expected = expected
106             .into_iter()
107             .map(|(FileRange { file_id, range }, _)| FileRange { file_id, range })
108             .sorted_by_key(cmp)
109             .collect::<Vec<_>>();
110         assert_eq!(expected, navs);
111     }
112
113     #[test]
114     fn goto_type_definition_works_simple() {
115         check(
116             r#"
117 struct Foo;
118      //^^^
119 fn foo() {
120     let f: Foo; f$0
121 }
122 "#,
123         );
124     }
125
126     #[test]
127     fn goto_type_definition_record_expr_field() {
128         check(
129             r#"
130 struct Bar;
131     // ^^^
132 struct Foo { foo: Bar }
133 fn foo() {
134     Foo { foo$0 }
135 }
136 "#,
137         );
138         check(
139             r#"
140 struct Bar;
141     // ^^^
142 struct Foo { foo: Bar }
143 fn foo() {
144     Foo { foo$0: Bar }
145 }
146 "#,
147         );
148     }
149
150     #[test]
151     fn goto_type_definition_record_pat_field() {
152         check(
153             r#"
154 struct Bar;
155     // ^^^
156 struct Foo { foo: Bar }
157 fn foo() {
158     let Foo { foo$0 };
159 }
160 "#,
161         );
162         check(
163             r#"
164 struct Bar;
165     // ^^^
166 struct Foo { foo: Bar }
167 fn foo() {
168     let Foo { foo$0: bar };
169 }
170 "#,
171         );
172     }
173
174     #[test]
175     fn goto_type_definition_works_simple_ref() {
176         check(
177             r#"
178 struct Foo;
179      //^^^
180 fn foo() {
181     let f: &Foo; f$0
182 }
183 "#,
184         );
185     }
186
187     #[test]
188     fn goto_type_definition_works_through_macro() {
189         check(
190             r#"
191 macro_rules! id { ($($tt:tt)*) => { $($tt)* } }
192 struct Foo {}
193      //^^^
194 id! {
195     fn bar() { let f$0 = Foo {}; }
196 }
197 "#,
198         );
199     }
200
201     #[test]
202     fn goto_type_definition_for_param() {
203         check(
204             r#"
205 struct Foo;
206      //^^^
207 fn foo($0f: Foo) {}
208 "#,
209         );
210     }
211
212     #[test]
213     fn goto_type_definition_for_tuple_field() {
214         check(
215             r#"
216 struct Foo;
217      //^^^
218 struct Bar(Foo);
219 fn foo() {
220     let bar = Bar(Foo);
221     bar.$00;
222 }
223 "#,
224         );
225     }
226
227     #[test]
228     fn goto_def_for_self_param() {
229         check(
230             r#"
231 struct Foo;
232      //^^^
233 impl Foo {
234     fn f(&self$0) {}
235 }
236 "#,
237         )
238     }
239
240     #[test]
241     fn goto_def_for_type_fallback() {
242         check(
243             r#"
244 struct Foo;
245      //^^^
246 impl Foo$0 {}
247 "#,
248         )
249     }
250
251     #[test]
252     fn goto_def_for_struct_field() {
253         check(
254             r#"
255 struct Bar;
256      //^^^
257
258 struct Foo {
259     bar$0: Bar,
260 }
261 "#,
262         );
263     }
264
265     #[test]
266     fn goto_def_for_enum_struct_field() {
267         check(
268             r#"
269 struct Bar;
270      //^^^
271
272 enum Foo {
273     Bar {
274         bar$0: Bar
275     },
276 }
277 "#,
278         );
279     }
280
281     #[test]
282     fn goto_def_considers_generics() {
283         check(
284             r#"
285 struct Foo;
286      //^^^
287 struct Bar<T, U>(T, U);
288      //^^^
289 struct Baz<T>(T);
290      //^^^
291
292 fn foo(x$0: Bar<Baz<Foo>, Baz<usize>) {}
293 "#,
294         );
295     }
296 }