1 use ide_db::{base_db::Upcast, defs::Definition, helpers::pick_best_token, RootDatabase};
2 use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, T};
4 use crate::{FilePosition, NavigationTarget, RangeInfo, TryToNav};
6 // Feature: Go to Type Definition
8 // Navigates to the type of an identifier.
11 // | Editor | Action Name
13 // | VS Code | **Go to Type Definition*
16 // image::https://user-images.githubusercontent.com/48062697/113020657-b560f500-917a-11eb-9007-0f809733a338.gif[]
17 pub(crate) fn goto_type_definition(
19 position: FilePosition,
20 ) -> Option<RangeInfo<Vec<NavigationTarget>>> {
21 let sema = hir::Semantics::new(db);
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,
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) {
39 let range = token.text_range();
40 sema.descend_into_macros(token)
43 let ty = sema.token_ancestors_with_macros(token.clone()).find_map(|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
53 if let Some(record_field) = ast::RecordExprField::for_name_ref(&it) {
54 let (_, _, ty) = sema.resolve_record_field(&record_field)?;
57 let record_field = ast::RecordPatField::for_field_name_ref(&it)?;
58 sema.resolve_record_pat_field(&record_field)?.ty(db)
70 // collect from each `ty` into the `res` result vec
71 let ty = ty.strip_references();
73 if let Some(adt) = t.as_adt() {
75 } else if let Some(trait_) = t.as_dyn_trait() {
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) {
84 Some(RangeInfo::new(range, res))
89 use ide_db::base_db::FileRange;
90 use itertools::Itertools;
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);
99 let cmp = |&FileRange { file_id, range }: &_| (file_id, range.start());
102 .map(|nav| FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
104 .collect::<Vec<_>>();
105 let expected = expected
107 .map(|(FileRange { file_id, range }, _)| FileRange { file_id, range })
109 .collect::<Vec<_>>();
110 assert_eq!(expected, navs);
114 fn goto_type_definition_works_simple() {
127 fn goto_type_definition_record_expr_field() {
132 struct Foo { foo: Bar }
142 struct Foo { foo: Bar }
151 fn goto_type_definition_record_pat_field() {
156 struct Foo { foo: Bar }
166 struct Foo { foo: Bar }
168 let Foo { foo$0: bar };
175 fn goto_type_definition_works_simple_ref() {
188 fn goto_type_definition_works_through_macro() {
191 macro_rules! id { ($($tt:tt)*) => { $($tt)* } }
195 fn bar() { let f$0 = Foo {}; }
202 fn goto_type_definition_for_param() {
213 fn goto_type_definition_for_tuple_field() {
228 fn goto_def_for_self_param() {
241 fn goto_def_for_type_fallback() {
252 fn goto_def_for_struct_field() {
266 fn goto_def_for_enum_struct_field() {
282 fn goto_def_considers_generics() {
287 struct Bar<T, U>(T, U);
292 fn foo(x$0: Bar<Baz<Foo>, Baz<usize>) {}