ide/
goto_type_definition.rs

1use hir::GenericParam;
2use ide_db::{RootDatabase, defs::Definition, helpers::pick_best_token};
3use syntax::{AstNode, SyntaxKind::*, SyntaxToken, T, ast, match_ast};
4
5use crate::{FilePosition, NavigationTarget, RangeInfo, TryToNav};
6
7// Feature: Go to Type Definition
8//
9// Navigates to the type of an identifier.
10//
11// | Editor  | Action Name |
12// |---------|-------------|
13// | VS Code | **Go to Type Definition** |
14//
15// ![Go to Type Definition](https://user-images.githubusercontent.com/48062697/113020657-b560f500-917a-11eb-9007-0f809733a338.gif)
16pub(crate) fn goto_type_definition(
17    db: &RootDatabase,
18    FilePosition { file_id, offset }: FilePosition,
19) -> Option<RangeInfo<Vec<NavigationTarget>>> {
20    let sema = hir::Semantics::new(db);
21
22    let file: ast::SourceFile = sema.parse_guess_edition(file_id);
23    let token: SyntaxToken =
24        pick_best_token(file.syntax().token_at_offset(offset), |kind| match kind {
25            IDENT | INT_NUMBER | T![self] => 3,
26            kind if kind.is_trivia() => 0,
27            T![;] => 1,
28            _ => 2,
29        })?;
30
31    let mut res = Vec::new();
32    let mut push = |def: Definition| {
33        if let Some(navs) = def.try_to_nav(&sema) {
34            for nav in navs {
35                if !res.contains(&nav) {
36                    res.push(nav);
37                }
38            }
39        }
40    };
41    let mut process_ty = |ty: hir::Type<'_>| {
42        // collect from each `ty` into the `res` result vec
43        let ty = ty.strip_references();
44        ty.walk(db, |t| {
45            if let Some(adt) = t.as_adt() {
46                push(adt.into());
47            } else if let Some(trait_) = t.as_dyn_trait() {
48                push(trait_.into());
49            } else if let Some(traits) = t.as_impl_traits(db) {
50                traits.for_each(|it| push(it.into()));
51            } else if let Some(trait_) = t.as_associated_type_parent_trait(db) {
52                push(trait_.into());
53            }
54        });
55    };
56    if let Some((range, _, _, resolution)) =
57        sema.check_for_format_args_template(token.clone(), offset)
58    {
59        if let Some(ty) = resolution.and_then(|res| match Definition::from(res) {
60            Definition::Const(it) => Some(it.ty(db)),
61            Definition::Static(it) => Some(it.ty(db)),
62            Definition::GenericParam(GenericParam::ConstParam(it)) => Some(it.ty(db)),
63            Definition::Local(it) => Some(it.ty(db)),
64            Definition::Adt(hir::Adt::Struct(it)) => Some(it.ty(db)),
65            _ => None,
66        }) {
67            process_ty(ty);
68        }
69        return Some(RangeInfo::new(range, res));
70    }
71
72    let range = token.text_range();
73    sema.descend_into_macros_no_opaque(token, false)
74        .into_iter()
75        .filter_map(|token| {
76            sema.token_ancestors_with_macros(token.value)
77                // When `token` is within a macro call, we can't determine its type. Don't continue
78                // this traversal because otherwise we'll end up returning the type of *that* macro
79                // call, which is not what we want in general.
80                //
81                // Macro calls always wrap `TokenTree`s, so it's sufficient and efficient to test
82                // if the current node is a `TokenTree`.
83                .take_while(|node| !ast::TokenTree::can_cast(node.kind()))
84                .find_map(|node| {
85                    let ty = match_ast! {
86                        match node {
87                            ast::Expr(it) => sema.type_of_expr(&it)?.original,
88                            ast::Pat(it) => sema.type_of_pat(&it)?.original,
89                            ast::SelfParam(it) => sema.type_of_self(&it)?,
90                            ast::Type(it) => sema.resolve_type(&it)?,
91                            ast::RecordField(it) => sema.to_def(&it)?.ty(db).to_type(db),
92                            // can't match on RecordExprField directly as `ast::Expr` will match an iteration too early otherwise
93                            ast::NameRef(it) => {
94                                if let Some(record_field) = ast::RecordExprField::for_name_ref(&it) {
95                                    let (_, _, ty) = sema.resolve_record_field(&record_field)?;
96                                    ty
97                                } else {
98                                    let record_field = ast::RecordPatField::for_field_name_ref(&it)?;
99                                    sema.resolve_record_pat_field(&record_field)?.1
100                                }
101                            },
102                            _ => return None,
103                        }
104                    };
105                    Some(ty)
106                })
107        })
108        .for_each(process_ty);
109    Some(RangeInfo::new(range, res))
110}
111
112#[cfg(test)]
113mod tests {
114    use ide_db::FileRange;
115    use itertools::Itertools;
116
117    use crate::fixture;
118
119    fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
120        let (analysis, position, expected) = fixture::annotations(ra_fixture);
121        let navs = analysis.goto_type_definition(position).unwrap().unwrap().info;
122        assert!(!navs.is_empty(), "navigation is empty");
123
124        let cmp = |&FileRange { file_id, range }: &_| (file_id, range.start());
125        let navs = navs
126            .into_iter()
127            .map(|nav| FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
128            .sorted_by_key(cmp)
129            .collect::<Vec<_>>();
130        let expected = expected
131            .into_iter()
132            .map(|(file_range, _)| file_range)
133            .sorted_by_key(cmp)
134            .collect::<Vec<_>>();
135        assert_eq!(expected, navs);
136    }
137
138    #[test]
139    fn goto_type_definition_works_simple() {
140        check(
141            r#"
142struct Foo;
143     //^^^
144fn foo() {
145    let f: Foo; f$0
146}
147"#,
148        );
149    }
150
151    #[test]
152    fn goto_type_definition_record_expr_field() {
153        check(
154            r#"
155struct Bar;
156    // ^^^
157struct Foo { foo: Bar }
158fn foo() {
159    Foo { foo$0 }
160}
161"#,
162        );
163        check(
164            r#"
165struct Bar;
166    // ^^^
167struct Foo { foo: Bar }
168fn foo() {
169    Foo { foo$0: Bar }
170}
171"#,
172        );
173    }
174
175    #[test]
176    fn goto_type_definition_record_pat_field() {
177        check(
178            r#"
179struct Bar;
180    // ^^^
181struct Foo { foo: Bar }
182fn foo() {
183    let Foo { foo$0 };
184}
185"#,
186        );
187        check(
188            r#"
189struct Bar;
190    // ^^^
191struct Foo { foo: Bar }
192fn foo() {
193    let Foo { foo$0: bar };
194}
195"#,
196        );
197    }
198
199    #[test]
200    fn goto_type_definition_works_simple_ref() {
201        check(
202            r#"
203struct Foo;
204     //^^^
205fn foo() {
206    let f: &Foo; f$0
207}
208"#,
209        );
210    }
211
212    #[test]
213    fn goto_type_definition_works_through_macro() {
214        check(
215            r#"
216macro_rules! id { ($($tt:tt)*) => { $($tt)* } }
217struct Foo {}
218     //^^^
219id! {
220    fn bar() { let f$0 = Foo {}; }
221}
222"#,
223        );
224    }
225
226    #[test]
227    fn dont_collect_type_from_token_in_macro_call() {
228        check(
229            r#"
230struct DontCollectMe;
231struct S;
232     //^
233
234macro_rules! inner {
235    ($t:tt) => { DontCollectMe }
236}
237macro_rules! m {
238    ($t:ident) => {
239        match $t {
240            _ => inner!($t);
241        }
242    }
243}
244
245fn test() {
246    m!($0S);
247}
248"#,
249        );
250    }
251
252    #[test]
253    fn goto_type_definition_for_param() {
254        check(
255            r#"
256struct Foo;
257     //^^^
258fn foo($0f: Foo) {}
259"#,
260        );
261    }
262
263    #[test]
264    fn goto_type_definition_for_tuple_field() {
265        check(
266            r#"
267struct Foo;
268     //^^^
269struct Bar(Foo);
270fn foo() {
271    let bar = Bar(Foo);
272    bar.$00;
273}
274"#,
275        );
276    }
277
278    #[test]
279    fn goto_def_for_self_param() {
280        check(
281            r#"
282struct Foo;
283     //^^^
284impl Foo {
285    fn f(&self$0) {}
286}
287"#,
288        )
289    }
290
291    #[test]
292    fn goto_def_for_type_fallback() {
293        check(
294            r#"
295struct Foo;
296     //^^^
297impl Foo$0 {}
298"#,
299        )
300    }
301
302    #[test]
303    fn goto_def_for_struct_field() {
304        check(
305            r#"
306struct Bar;
307     //^^^
308
309struct Foo {
310    bar$0: Bar,
311}
312"#,
313        );
314    }
315
316    #[test]
317    fn goto_def_for_enum_struct_field() {
318        check(
319            r#"
320struct Bar;
321     //^^^
322
323enum Foo {
324    Bar {
325        bar$0: Bar
326    },
327}
328"#,
329        );
330    }
331
332    #[test]
333    fn goto_def_considers_generics() {
334        check(
335            r#"
336struct Foo;
337     //^^^
338struct Bar<T, U>(T, U);
339     //^^^
340struct Baz<T>(T);
341     //^^^
342
343fn foo(x$0: Bar<Baz<Foo>, Baz<usize>) {}
344"#,
345        );
346    }
347
348    #[test]
349    fn implicit_format_args() {
350        check(
351            r#"
352//- minicore: fmt
353struct Bar;
354    // ^^^
355    fn test() {
356    let a = Bar;
357    format_args!("hello {a$0}");
358}
359"#,
360        );
361        check(
362            r#"
363//- minicore: fmt
364struct Bar;
365    // ^^^
366    fn test() {
367    format_args!("hello {Bar$0}");
368}
369"#,
370        );
371        check(
372            r#"
373//- minicore: fmt
374struct Bar;
375    // ^^^
376const BAR: Bar = Bar;
377fn test() {
378    format_args!("hello {BAR$0}");
379}
380"#,
381        );
382    }
383}