summaryrefslogtreecommitdiffstats
path: root/src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs')
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs296
1 files changed, 296 insertions, 0 deletions
diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs b/src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs
new file mode 100644
index 000000000..55cdb3200
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs
@@ -0,0 +1,296 @@
+use ide_db::{base_db::Upcast, defs::Definition, helpers::pick_best_token, RootDatabase};
+use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, T};
+
+use crate::{FilePosition, NavigationTarget, RangeInfo, TryToNav};
+
+// Feature: Go to Type Definition
+//
+// Navigates to the type of an identifier.
+//
+// |===
+// | Editor | Action Name
+//
+// | VS Code | **Go to Type Definition*
+// |===
+//
+// image::https://user-images.githubusercontent.com/48062697/113020657-b560f500-917a-11eb-9007-0f809733a338.gif[]
+pub(crate) fn goto_type_definition(
+ db: &RootDatabase,
+ position: FilePosition,
+) -> Option<RangeInfo<Vec<NavigationTarget>>> {
+ let sema = hir::Semantics::new(db);
+
+ let file: ast::SourceFile = sema.parse(position.file_id);
+ let token: SyntaxToken =
+ pick_best_token(file.syntax().token_at_offset(position.offset), |kind| match kind {
+ IDENT | INT_NUMBER | T![self] => 2,
+ kind if kind.is_trivia() => 0,
+ _ => 1,
+ })?;
+
+ let mut res = Vec::new();
+ let mut push = |def: Definition| {
+ if let Some(nav) = def.try_to_nav(db) {
+ if !res.contains(&nav) {
+ res.push(nav);
+ }
+ }
+ };
+ let range = token.text_range();
+ sema.descend_into_macros(token)
+ .iter()
+ .filter_map(|token| {
+ let ty = sema.token_ancestors_with_macros(token.clone()).find_map(|node| {
+ let ty = match_ast! {
+ match node {
+ ast::Expr(it) => sema.type_of_expr(&it)?.original,
+ ast::Pat(it) => sema.type_of_pat(&it)?.original,
+ ast::SelfParam(it) => sema.type_of_self(&it)?,
+ ast::Type(it) => sema.resolve_type(&it)?,
+ ast::RecordField(it) => sema.to_def(&it).map(|d| d.ty(db.upcast()))?,
+ // can't match on RecordExprField directly as `ast::Expr` will match an iteration too early otherwise
+ ast::NameRef(it) => {
+ if let Some(record_field) = ast::RecordExprField::for_name_ref(&it) {
+ let (_, _, ty) = sema.resolve_record_field(&record_field)?;
+ ty
+ } else {
+ let record_field = ast::RecordPatField::for_field_name_ref(&it)?;
+ sema.resolve_record_pat_field(&record_field)?.ty(db)
+ }
+ },
+ _ => return None,
+ }
+ };
+
+ Some(ty)
+ });
+ ty
+ })
+ .for_each(|ty| {
+ // collect from each `ty` into the `res` result vec
+ let ty = ty.strip_references();
+ ty.walk(db, |t| {
+ if let Some(adt) = t.as_adt() {
+ push(adt.into());
+ } else if let Some(trait_) = t.as_dyn_trait() {
+ push(trait_.into());
+ } else if let Some(traits) = t.as_impl_traits(db) {
+ traits.for_each(|it| push(it.into()));
+ } else if let Some(trait_) = t.as_associated_type_parent_trait(db) {
+ push(trait_.into());
+ }
+ });
+ });
+ Some(RangeInfo::new(range, res))
+}
+
+#[cfg(test)]
+mod tests {
+ use ide_db::base_db::FileRange;
+ use itertools::Itertools;
+
+ use crate::fixture;
+
+ fn check(ra_fixture: &str) {
+ let (analysis, position, expected) = fixture::annotations(ra_fixture);
+ let navs = analysis.goto_type_definition(position).unwrap().unwrap().info;
+ assert_ne!(navs.len(), 0);
+
+ let cmp = |&FileRange { file_id, range }: &_| (file_id, range.start());
+ let navs = navs
+ .into_iter()
+ .map(|nav| FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
+ .sorted_by_key(cmp)
+ .collect::<Vec<_>>();
+ let expected = expected
+ .into_iter()
+ .map(|(FileRange { file_id, range }, _)| FileRange { file_id, range })
+ .sorted_by_key(cmp)
+ .collect::<Vec<_>>();
+ assert_eq!(expected, navs);
+ }
+
+ #[test]
+ fn goto_type_definition_works_simple() {
+ check(
+ r#"
+struct Foo;
+ //^^^
+fn foo() {
+ let f: Foo; f$0
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_type_definition_record_expr_field() {
+ check(
+ r#"
+struct Bar;
+ // ^^^
+struct Foo { foo: Bar }
+fn foo() {
+ Foo { foo$0 }
+}
+"#,
+ );
+ check(
+ r#"
+struct Bar;
+ // ^^^
+struct Foo { foo: Bar }
+fn foo() {
+ Foo { foo$0: Bar }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_type_definition_record_pat_field() {
+ check(
+ r#"
+struct Bar;
+ // ^^^
+struct Foo { foo: Bar }
+fn foo() {
+ let Foo { foo$0 };
+}
+"#,
+ );
+ check(
+ r#"
+struct Bar;
+ // ^^^
+struct Foo { foo: Bar }
+fn foo() {
+ let Foo { foo$0: bar };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_type_definition_works_simple_ref() {
+ check(
+ r#"
+struct Foo;
+ //^^^
+fn foo() {
+ let f: &Foo; f$0
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_type_definition_works_through_macro() {
+ check(
+ r#"
+macro_rules! id { ($($tt:tt)*) => { $($tt)* } }
+struct Foo {}
+ //^^^
+id! {
+ fn bar() { let f$0 = Foo {}; }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_type_definition_for_param() {
+ check(
+ r#"
+struct Foo;
+ //^^^
+fn foo($0f: Foo) {}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_type_definition_for_tuple_field() {
+ check(
+ r#"
+struct Foo;
+ //^^^
+struct Bar(Foo);
+fn foo() {
+ let bar = Bar(Foo);
+ bar.$00;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_self_param() {
+ check(
+ r#"
+struct Foo;
+ //^^^
+impl Foo {
+ fn f(&self$0) {}
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn goto_def_for_type_fallback() {
+ check(
+ r#"
+struct Foo;
+ //^^^
+impl Foo$0 {}
+"#,
+ )
+ }
+
+ #[test]
+ fn goto_def_for_struct_field() {
+ check(
+ r#"
+struct Bar;
+ //^^^
+
+struct Foo {
+ bar$0: Bar,
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_enum_struct_field() {
+ check(
+ r#"
+struct Bar;
+ //^^^
+
+enum Foo {
+ Bar {
+ bar$0: Bar
+ },
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_considers_generics() {
+ check(
+ r#"
+struct Foo;
+ //^^^
+struct Bar<T, U>(T, U);
+ //^^^
+struct Baz<T>(T);
+ //^^^
+
+fn foo(x$0: Bar<Baz<Foo>, Baz<usize>) {}
+"#,
+ );
+ }
+}