ra_ap_ide/
goto_type_definition.rs

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