use std::convert::TryInto; use crate::{ display::TryToNav, doc_links::token_as_doc_comment, FilePosition, NavigationTarget, RangeInfo, }; use hir::{AsAssocItem, Semantics}; use ide_db::{ base_db::{AnchoredPath, FileId, FileLoader}, defs::Definition, helpers::pick_best_token, RootDatabase, }; use itertools::Itertools; use syntax::{ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TextRange, T}; // Feature: Go to Definition // // Navigates to the definition of an identifier. // // |=== // | Editor | Shortcut // // | VS Code | kbd:[F12] // |=== // // image::https://user-images.githubusercontent.com/48062697/113065563-025fbe00-91b1-11eb-83e4-a5a703610b23.gif[] pub(crate) fn goto_definition( db: &RootDatabase, position: FilePosition, ) -> Option>> { let sema = &Semantics::new(db); let file = sema.parse(position.file_id).syntax().clone(); let original_token = pick_best_token(file.token_at_offset(position.offset), |kind| match kind { IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] | COMMENT => 2, kind if kind.is_trivia() => 0, _ => 1, })?; if let Some(doc_comment) = token_as_doc_comment(&original_token) { return doc_comment.get_definition_with_descend_at(sema, position.offset, |def, _, _| { let nav = def.try_to_nav(db)?; Some(RangeInfo::new(original_token.text_range(), vec![nav])) }); } let navs = sema .descend_into_macros(original_token.clone()) .into_iter() .filter_map(|token| { let parent = token.parent()?; if let Some(tt) = ast::TokenTree::cast(parent) { if let x @ Some(_) = try_lookup_include_path(sema, tt, token.clone(), position.file_id) { return x; } } Some( Definition::from_token(sema, &token) .into_iter() .flat_map(|def| { try_find_trait_item_definition(sema.db, &def) .unwrap_or_else(|| def_to_nav(sema.db, def)) }) .collect::>(), ) }) .flatten() .unique() .collect::>(); Some(RangeInfo::new(original_token.text_range(), navs)) } fn try_lookup_include_path( sema: &Semantics, tt: ast::TokenTree, token: SyntaxToken, file_id: FileId, ) -> Option> { let token = ast::String::cast(token)?; let path = token.value()?.into_owned(); let macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?; let name = macro_call.path()?.segment()?.name_ref()?; if !matches!(&*name.text(), "include" | "include_str" | "include_bytes") { return None; } let file_id = sema.db.resolve_path(AnchoredPath { anchor: file_id, path: &path })?; let size = sema.db.file_text(file_id).len().try_into().ok()?; Some(vec![NavigationTarget { file_id, full_range: TextRange::new(0.into(), size), name: path.into(), focus_range: None, kind: None, container_name: None, description: None, docs: None, }]) } /// finds the trait definition of an impl'd item /// e.g. /// ```rust /// trait A { fn a(); } /// struct S; /// impl A for S { fn a(); } // <-- on this function, will get the location of a() in the trait /// ``` fn try_find_trait_item_definition( db: &RootDatabase, def: &Definition, ) -> Option> { let name = def.name(db)?; let assoc = def.as_assoc_item(db)?; let imp = match assoc.container(db) { hir::AssocItemContainer::Impl(imp) => imp, _ => return None, }; let trait_ = imp.trait_(db)?; trait_ .items(db) .iter() .find_map(|itm| (itm.name(db)? == name).then(|| itm.try_to_nav(db)).flatten()) .map(|it| vec![it]) } fn def_to_nav(db: &RootDatabase, def: Definition) -> Vec { def.try_to_nav(db).map(|it| vec![it]).unwrap_or_default() } #[cfg(test)] mod tests { use ide_db::base_db::FileRange; use itertools::Itertools; use crate::fixture; #[track_caller] fn check(ra_fixture: &str) { let (analysis, position, expected) = fixture::annotations(ra_fixture); let navs = analysis.goto_definition(position).unwrap().expect("no definition found").info; if navs.is_empty() { panic!("unresolved reference") } 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::>(); let expected = expected .into_iter() .map(|(FileRange { file_id, range }, _)| FileRange { file_id, range }) .sorted_by_key(cmp) .collect::>(); assert_eq!(expected, navs); } fn check_unresolved(ra_fixture: &str) { let (analysis, position) = fixture::position(ra_fixture); let navs = analysis.goto_definition(position).unwrap().expect("no definition found").info; assert!(navs.is_empty(), "didn't expect this to resolve anywhere: {:?}", navs) } #[test] fn goto_def_in_mac_call_in_attr_invoc() { check( r#" //- proc_macros: identity pub struct Struct { // ^^^^^^ field: i32, } macro_rules! identity { ($($tt:tt)*) => {$($tt)*}; } #[proc_macros::identity] fn function() { identity!(Struct$0 { field: 0 }); } "#, ) } #[test] fn goto_def_for_extern_crate() { check( r#" //- /main.rs crate:main deps:std extern crate std$0; //- /std/lib.rs crate:std // empty //^file "#, ) } #[test] fn goto_def_for_renamed_extern_crate() { check( r#" //- /main.rs crate:main deps:std extern crate std as abc$0; //- /std/lib.rs crate:std // empty //^file "#, ) } #[test] fn goto_def_in_items() { check( r#" struct Foo; //^^^ enum E { X(Foo$0) } "#, ); } #[test] fn goto_def_at_start_of_item() { check( r#" struct Foo; //^^^ enum E { X($0Foo) } "#, ); } #[test] fn goto_definition_resolves_correct_name() { check( r#" //- /lib.rs use a::Foo; mod a; mod b; enum E { X(Foo$0) } //- /a.rs struct Foo; //^^^ //- /b.rs struct Foo; "#, ); } #[test] fn goto_def_for_module_declaration() { check( r#" //- /lib.rs mod $0foo; //- /foo.rs // empty //^file "#, ); check( r#" //- /lib.rs mod $0foo; //- /foo/mod.rs // empty //^file "#, ); } #[test] fn goto_def_for_macros() { check( r#" macro_rules! foo { () => { () } } //^^^ fn bar() { $0foo!(); } "#, ); } #[test] fn goto_def_for_macros_from_other_crates() { check( r#" //- /lib.rs crate:main deps:foo use foo::foo; fn bar() { $0foo!(); } //- /foo/lib.rs crate:foo #[macro_export] macro_rules! foo { () => { () } } //^^^ "#, ); } #[test] fn goto_def_for_macros_in_use_tree() { check( r#" //- /lib.rs crate:main deps:foo use foo::foo$0; //- /foo/lib.rs crate:foo #[macro_export] macro_rules! foo { () => { () } } //^^^ "#, ); } #[test] fn goto_def_for_macro_defined_fn_with_arg() { check( r#" //- /lib.rs macro_rules! define_fn { ($name:ident) => (fn $name() {}) } define_fn!(foo); //^^^ fn bar() { $0foo(); } "#, ); } #[test] fn goto_def_for_macro_defined_fn_no_arg() { check( r#" //- /lib.rs macro_rules! define_fn { () => (fn foo() {}) } define_fn!(); //^^^^^^^^^^^^^ fn bar() { $0foo(); } "#, ); } #[test] fn goto_definition_works_for_macro_inside_pattern() { check( r#" //- /lib.rs macro_rules! foo {() => {0}} //^^^ fn bar() { match (0,1) { ($0foo!(), _) => {} } } "#, ); } #[test] fn goto_definition_works_for_macro_inside_match_arm_lhs() { check( r#" //- /lib.rs macro_rules! foo {() => {0}} //^^^ fn bar() { match 0 { $0foo!() => {} } } "#, ); } #[test] fn goto_def_for_use_alias() { check( r#" //- /lib.rs crate:main deps:foo use foo as bar$0; //- /foo/lib.rs crate:foo // empty //^file "#, ); } #[test] fn goto_def_for_use_alias_foo_macro() { check( r#" //- /lib.rs crate:main deps:foo use foo::foo as bar$0; //- /foo/lib.rs crate:foo #[macro_export] macro_rules! foo { () => { () } } //^^^ "#, ); } #[test] fn goto_def_for_methods() { check( r#" struct Foo; impl Foo { fn frobnicate(&self) { } //^^^^^^^^^^ } fn bar(foo: &Foo) { foo.frobnicate$0(); } "#, ); } #[test] fn goto_def_for_fields() { check( r#" struct Foo { spam: u32, } //^^^^ fn bar(foo: &Foo) { foo.spam$0; } "#, ); } #[test] fn goto_def_for_record_fields() { check( r#" //- /lib.rs struct Foo { spam: u32, } //^^^^ fn bar() -> Foo { Foo { spam$0: 0, } } "#, ); } #[test] fn goto_def_for_record_pat_fields() { check( r#" //- /lib.rs struct Foo { spam: u32, } //^^^^ fn bar(foo: Foo) -> Foo { let Foo { spam$0: _, } = foo } "#, ); } #[test] fn goto_def_for_record_fields_macros() { check( r" macro_rules! m { () => { 92 };} struct Foo { spam: u32 } //^^^^ fn bar() -> Foo { Foo { spam$0: m!() } } ", ); } #[test] fn goto_for_tuple_fields() { check( r#" struct Foo(u32); //^^^ fn bar() { let foo = Foo(0); foo.$00; } "#, ); } #[test] fn goto_def_for_ufcs_inherent_methods() { check( r#" struct Foo; impl Foo { fn frobnicate() { } } //^^^^^^^^^^ fn bar(foo: &Foo) { Foo::frobnicate$0(); } "#, ); } #[test] fn goto_def_for_ufcs_trait_methods_through_traits() { check( r#" trait Foo { fn frobnicate(); } //^^^^^^^^^^ fn bar() { Foo::frobnicate$0(); } "#, ); } #[test] fn goto_def_for_ufcs_trait_methods_through_self() { check( r#" struct Foo; trait Trait { fn frobnicate(); } //^^^^^^^^^^ impl Trait for Foo {} fn bar() { Foo::frobnicate$0(); } "#, ); } #[test] fn goto_definition_on_self() { check( r#" struct Foo; impl Foo { //^^^ pub fn new() -> Self { Self$0 {} } } "#, ); check( r#" struct Foo; impl Foo { //^^^ pub fn new() -> Self$0 { Self {} } } "#, ); check( r#" enum Foo { A } impl Foo { //^^^ pub fn new() -> Self$0 { Foo::A } } "#, ); check( r#" enum Foo { A } impl Foo { //^^^ pub fn thing(a: &Self$0) { } } "#, ); } #[test] fn goto_definition_on_self_in_trait_impl() { check( r#" struct Foo; trait Make { fn new() -> Self; } impl Make for Foo { //^^^ fn new() -> Self { Self$0 {} } } "#, ); check( r#" struct Foo; trait Make { fn new() -> Self; } impl Make for Foo { //^^^ fn new() -> Self$0 { Self {} } } "#, ); } #[test] fn goto_def_when_used_on_definition_name_itself() { check( r#" struct Foo$0 { value: u32 } //^^^ "#, ); check( r#" struct Foo { field$0: string, } //^^^^^ "#, ); check( r#" fn foo_test$0() { } //^^^^^^^^ "#, ); check( r#" enum Foo$0 { Variant } //^^^ "#, ); check( r#" enum Foo { Variant1, Variant2$0, //^^^^^^^^ Variant3, } "#, ); check( r#" static INNER$0: &str = ""; //^^^^^ "#, ); check( r#" const INNER$0: &str = ""; //^^^^^ "#, ); check( r#" type Thing$0 = Option<()>; //^^^^^ "#, ); check( r#" trait Foo$0 { } //^^^ "#, ); check( r#" mod bar$0 { } //^^^ "#, ); } #[test] fn goto_from_macro() { check( r#" macro_rules! id { ($($tt:tt)*) => { $($tt)* } } fn foo() {} //^^^ id! { fn bar() { fo$0o(); } } mod confuse_index { fn foo(); } "#, ); } #[test] fn goto_through_format() { check( r#" #[macro_export] macro_rules! format { ($($arg:tt)*) => ($crate::fmt::format($crate::__export::format_args!($($arg)*))) } #[rustc_builtin_macro] #[macro_export] macro_rules! format_args { ($fmt:expr) => ({ /* compiler built-in */ }); ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) } pub mod __export { pub use crate::format_args; fn foo() {} // for index confusion } fn foo() -> i8 {} //^^^ fn test() { format!("{}", fo$0o()) } "#, ); } #[test] fn goto_through_included_file() { check( r#" //- /main.rs #[rustc_builtin_macro] macro_rules! include {} include!("foo.rs"); //^^^^^^^^^^^^^^^^^^^ fn f() { foo$0(); } mod confuse_index { pub fn foo() {} } //- /foo.rs fn foo() {} "#, ); } #[test] fn goto_for_type_param() { check( r#" struct Foo { t: $0T } //^ "#, ); } #[test] fn goto_within_macro() { check( r#" macro_rules! id { ($($tt:tt)*) => ($($tt)*) } fn foo() { let x = 1; //^ id!({ let y = $0x; let z = y; }); } "#, ); check( r#" macro_rules! id { ($($tt:tt)*) => ($($tt)*) } fn foo() { let x = 1; id!({ let y = x; //^ let z = $0y; }); } "#, ); } #[test] fn goto_def_in_local_fn() { check( r#" fn main() { fn foo() { let x = 92; //^ $0x; } } "#, ); } #[test] fn goto_def_in_local_macro() { check( r#" fn bar() { macro_rules! foo { () => { () } } //^^^ $0foo!(); } "#, ); } #[test] fn goto_def_for_field_init_shorthand() { check( r#" struct Foo { x: i32 } //^ fn main() { let x = 92; //^ Foo { x$0 }; } "#, ) } #[test] fn goto_def_for_enum_variant_field() { check( r#" enum Foo { Bar { x: i32 } //^ } fn baz(foo: Foo) { match foo { Foo::Bar { x$0 } => x //^ }; } "#, ); } #[test] fn goto_def_for_enum_variant_self_pattern_const() { check( r#" enum Foo { Bar } //^^^ impl Foo { fn baz(self) { match self { Self::Bar$0 => {} } } } "#, ); } #[test] fn goto_def_for_enum_variant_self_pattern_record() { check( r#" enum Foo { Bar { val: i32 } } //^^^ impl Foo { fn baz(self) -> i32 { match self { Self::Bar$0 { val } => {} } } } "#, ); } #[test] fn goto_def_for_enum_variant_self_expr_const() { check( r#" enum Foo { Bar } //^^^ impl Foo { fn baz(self) { Self::Bar$0; } } "#, ); } #[test] fn goto_def_for_enum_variant_self_expr_record() { check( r#" enum Foo { Bar { val: i32 } } //^^^ impl Foo { fn baz(self) { Self::Bar$0 {val: 4}; } } "#, ); } #[test] fn goto_def_for_type_alias_generic_parameter() { check( r#" type Alias = T$0; //^ "#, ) } #[test] fn goto_def_for_macro_container() { check( r#" //- /lib.rs crate:main deps:foo foo::module$0::mac!(); //- /foo/lib.rs crate:foo pub mod module { //^^^^^^ #[macro_export] macro_rules! _mac { () => { () } } pub use crate::_mac as mac; } "#, ); } #[test] fn goto_def_for_assoc_ty_in_path() { check( r#" trait Iterator { type Item; //^^^^ } fn f() -> impl Iterator {} "#, ); } #[test] fn unknown_assoc_ty() { check_unresolved( r#" trait Iterator { type Item; } fn f() -> impl Iterator {} "#, ) } #[test] fn goto_def_for_assoc_ty_in_path_multiple() { check( r#" trait Iterator { type A; //^ type B; } fn f() -> impl Iterator {} "#, ); check( r#" trait Iterator { type A; type B; //^ } fn f() -> impl Iterator {} "#, ); } #[test] fn goto_def_for_assoc_ty_ufcs() { check( r#" trait Iterator { type Item; //^^^^ } fn g() -> <() as Iterator>::Item {} "#, ); } #[test] fn goto_def_for_assoc_ty_ufcs_multiple() { check( r#" trait Iterator { type A; //^ type B; } fn g() -> <() as Iterator>::B {} "#, ); check( r#" trait Iterator { type A; type B; //^ } fn g() -> <() as Iterator>::A {} "#, ); } #[test] fn goto_self_param_ty_specified() { check( r#" struct Foo {} impl Foo { fn bar(self: &Foo) { //^^^^ let foo = sel$0f; } }"#, ) } #[test] fn goto_self_param_on_decl() { check( r#" struct Foo {} impl Foo { fn bar(&self$0) { //^^^^ } }"#, ) } #[test] fn goto_lifetime_param_on_decl() { check( r#" fn foo<'foobar$0>(_: &'foobar ()) { //^^^^^^^ }"#, ) } #[test] fn goto_lifetime_param_decl() { check( r#" fn foo<'foobar>(_: &'foobar$0 ()) { //^^^^^^^ }"#, ) } #[test] fn goto_lifetime_param_decl_nested() { check( r#" fn foo<'foobar>(_: &'foobar ()) { fn foo<'foobar>(_: &'foobar$0 ()) {} //^^^^^^^ }"#, ) } #[test] fn goto_lifetime_hrtb() { // FIXME: requires the HIR to somehow track these hrtb lifetimes check_unresolved( r#" trait Foo {} fn foo() where for<'a> T: Foo<&'a$0 (u8, u16)>, {} //^^ "#, ); check_unresolved( r#" trait Foo {} fn foo() where for<'a$0> T: Foo<&'a (u8, u16)>, {} //^^ "#, ); } #[test] fn goto_lifetime_hrtb_for_type() { // FIXME: requires ForTypes to be implemented check_unresolved( r#"trait Foo {} fn foo() where T: for<'a> Foo<&'a$0 (u8, u16)>, {} //^^ "#, ); } #[test] fn goto_label() { check( r#" fn foo<'foo>(_: &'foo ()) { 'foo: { //^^^^ 'bar: loop { break 'foo$0; } } }"#, ) } #[test] fn goto_def_for_intra_doc_link_same_file() { check( r#" /// Blah, [`bar`](bar) .. [`foo`](foo$0) has [`bar`](bar) pub fn bar() { } /// You might want to see [`std::fs::read()`] too. pub fn foo() { } //^^^ }"#, ) } #[test] fn goto_def_for_intra_doc_link_inner() { check( r#" //- /main.rs mod m; struct S; //^ //- /m.rs //! [`super::S$0`] "#, ) } #[test] fn goto_incomplete_field() { check( r#" struct A { a: u32 } //^ fn foo() { A { a$0: }; } "#, ) } #[test] fn goto_proc_macro() { check( r#" //- /main.rs crate:main deps:mac use mac::fn_macro; fn_macro$0!(); //- /mac.rs crate:mac #[proc_macro] fn fn_macro() {} //^^^^^^^^ "#, ) } #[test] fn goto_intra_doc_links() { check( r#" pub mod theitem { /// This is the item. Cool! pub struct TheItem; //^^^^^^^ } /// Gives you a [`TheItem$0`]. /// /// [`TheItem`]: theitem::TheItem pub fn gimme() -> theitem::TheItem { theitem::TheItem } "#, ); } #[test] fn goto_ident_from_pat_macro() { check( r#" macro_rules! pat { ($name:ident) => { Enum::Variant1($name) } } enum Enum { Variant1(u8), Variant2, } fn f(e: Enum) { match e { pat!(bind) => { //^^^^ bind$0 } Enum::Variant2 => {} } } "#, ); } #[test] fn goto_include() { check( r#" //- /main.rs fn main() { let str = include_str!("foo.txt$0"); } //- /foo.txt // empty //^file "#, ); } #[test] fn goto_def_of_trait_impl_fn() { check( r#" trait Twait { fn a(); // ^ } struct Stwuct; impl Twait for Stwuct { fn a$0(); } "#, ); } #[test] fn goto_def_of_trait_impl_const() { check( r#" trait Twait { const NOMS: bool; // ^^^^ } struct Stwuct; impl Twait for Stwuct { const NOMS$0: bool = true; } "#, ); } #[test] fn goto_def_of_trait_impl_type_alias() { check( r#" trait Twait { type IsBad; // ^^^^^ } struct Stwuct; impl Twait for Stwuct { type IsBad$0 = !; } "#, ); } #[test] fn goto_def_derive_input() { check( r#" //- minicore:derive #[rustc_builtin_macro] pub macro Copy {} // ^^^^ #[derive(Copy$0)] struct Foo; "#, ); check( r#" //- minicore:derive mod foo { #[rustc_builtin_macro] pub macro Copy {} // ^^^^ } #[derive(foo::Copy$0)] struct Foo; "#, ); } #[test] fn goto_def_in_macro_multi() { check( r#" struct Foo { foo: () //^^^ } macro_rules! foo { ($ident:ident) => { fn $ident(Foo { $ident }: Foo) {} } } foo!(foo$0); //^^^ //^^^ "#, ); check( r#" fn bar() {} //^^^ struct bar; //^^^ macro_rules! foo { ($ident:ident) => { fn foo() { let _: $ident = $ident; } } } foo!(bar$0); "#, ); } }