use hir::Semantics; use ra_ide_db::{ defs::{classify_name, classify_name_ref, NameClass}, symbol_index, RootDatabase, }; use ra_syntax::{ ast::{self}, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, }; use crate::{ display::{ToNav, TryToNav}, FilePosition, NavigationTarget, RangeInfo, }; // Feature: Go to Definition // // Navigates to the definition of an identifier. // // |=== // | Editor | Shortcut // // | VS Code | kbd:[F12] // |=== 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(file.token_at_offset(position.offset))?; let token = sema.descend_into_macros(original_token.clone()); let nav_targets = match_ast! { match (token.parent()) { ast::NameRef(name_ref) => { reference_definition(&sema, &name_ref).to_vec() }, ast::Name(name) => { let def = match classify_name(&sema, &name)? { NameClass::Definition(def) | NameClass::ConstReference(def) => def, NameClass::FieldShorthand { local: _, field } => field, }; let nav = def.try_to_nav(sema.db)?; vec![nav] }, _ => return None, } }; Some(RangeInfo::new(original_token.text_range(), nav_targets)) } fn pick_best(tokens: TokenAtOffset) -> Option { return tokens.max_by_key(priority); fn priority(n: &SyntaxToken) -> usize { match n.kind() { IDENT | INT_NUMBER => 2, kind if kind.is_trivia() => 0, _ => 1, } } } #[derive(Debug)] pub(crate) enum ReferenceResult { Exact(NavigationTarget), Approximate(Vec), } impl ReferenceResult { fn to_vec(self) -> Vec { match self { ReferenceResult::Exact(target) => vec![target], ReferenceResult::Approximate(vec) => vec, } } } pub(crate) fn reference_definition( sema: &Semantics, name_ref: &ast::NameRef, ) -> ReferenceResult { let name_kind = classify_name_ref(sema, name_ref); if let Some(def) = name_kind { let def = def.definition(); return match def.try_to_nav(sema.db) { Some(nav) => ReferenceResult::Exact(nav), None => ReferenceResult::Approximate(Vec::new()), }; } // Fallback index based approach: let navs = symbol_index::index_resolve(sema.db, name_ref) .into_iter() .map(|s| s.to_nav(sema.db)) .collect(); ReferenceResult::Approximate(navs) } #[cfg(test)] mod tests { use expect::{expect, Expect}; use test_utils::assert_eq_text; use crate::mock_analysis::analysis_and_position; fn check_goto(ra_fixture: &str, expected: &str, expected_range: &str) { let (analysis, pos) = analysis_and_position(ra_fixture); let mut navs = analysis.goto_definition(pos).unwrap().unwrap().info; if navs.len() == 0 { panic!("unresolved reference") } assert_eq!(navs.len(), 1); let nav = navs.pop().unwrap(); let file_text = analysis.file_text(nav.file_id()).unwrap(); let mut actual = file_text[nav.full_range()].to_string(); if let Some(focus) = nav.focus_range() { actual += "|"; actual += &file_text[focus]; } if !expected_range.contains("...") { test_utils::assert_eq_text!(&actual, expected_range); } else { let mut parts = expected_range.split("..."); let prefix = parts.next().unwrap(); let suffix = parts.next().unwrap(); assert!( actual.starts_with(prefix) && actual.ends_with(suffix), "\nExpected: {}\n Actual: {}\n", expected_range, actual ); } nav.assert_match(expected); } fn check(ra_fixture: &str, expect: Expect) { let (analysis, pos) = analysis_and_position(ra_fixture); let mut navs = analysis.goto_definition(pos).unwrap().unwrap().info; if navs.len() == 0 { panic!("unresolved reference") } assert_eq!(navs.len(), 1); let nav = navs.pop().unwrap(); let file_text = analysis.file_text(nav.file_id()).unwrap(); let mut actual = nav.debug_render(); actual += "\n"; actual += &file_text[nav.full_range()].to_string(); if let Some(focus) = nav.focus_range() { actual += "|"; actual += &file_text[focus]; actual += "\n"; } expect.assert_eq(&actual); } #[test] fn goto_def_in_items() { check( r#" struct Foo; enum E { X(Foo<|>) } "#, expect![[r#" Foo STRUCT_DEF FileId(1) 0..11 7..10 struct Foo;|Foo "#]], ); } #[test] fn goto_def_at_start_of_item() { check_goto( " //- /lib.rs struct Foo; enum E { X(<|>Foo) } ", "Foo STRUCT_DEF FileId(1) 0..11 7..10", "struct Foo;|Foo", ); } #[test] fn goto_definition_resolves_correct_name() { check_goto( " //- /lib.rs use a::Foo; mod a; mod b; enum E { X(Foo<|>) } //- /a.rs struct Foo; //- /b.rs struct Foo; ", "Foo STRUCT_DEF FileId(2) 0..11 7..10", "struct Foo;|Foo", ); } #[test] fn goto_def_for_module_declaration() { check_goto( r#" //- /lib.rs mod <|>foo; //- /foo.rs // empty "#, "foo SOURCE_FILE FileId(2) 0..9", "// empty\n", ); check_goto( r#" //- /lib.rs mod <|>foo; //- /foo/mod.rs // empty "#, "foo SOURCE_FILE FileId(2) 0..9", "// empty\n", ); } #[test] fn goto_def_for_macros() { check_goto( " //- /lib.rs macro_rules! foo { () => { () } } fn bar() { <|>foo!(); } ", "foo MACRO_CALL FileId(1) 0..33 13..16", "macro_rules! foo { () => { () } }|foo", ); } #[test] fn goto_def_for_macros_from_other_crates() { check_goto( " //- /lib.rs use foo::foo; fn bar() { <|>foo!(); } //- /foo/lib.rs #[macro_export] macro_rules! foo { () => { () } } ", "foo MACRO_CALL FileId(2) 0..49 29..32", "#[macro_export]\nmacro_rules! foo { () => { () } }|foo", ); } #[test] fn goto_def_for_use_alias() { check_goto( r#" //- /lib.rs use foo as bar<|>; //- /foo/lib.rs #[macro_export] macro_rules! foo { () => { () } } "#, "SOURCE_FILE FileId(2) 0..50", "#[macro_export]\nmacro_rules! foo { () => { () } }\n", ); } #[test] fn goto_def_for_use_alias_foo_macro() { check_goto( " //- /lib.rs use foo::foo as bar<|>; //- /foo/lib.rs #[macro_export] macro_rules! foo { () => { () } } ", "foo MACRO_CALL FileId(2) 0..49 29..32", "#[macro_export]\nmacro_rules! foo { () => { () } }|foo", ); } #[test] fn goto_def_for_macros_in_use_tree() { check_goto( " //- /lib.rs use foo::foo<|>; //- /foo/lib.rs #[macro_export] macro_rules! foo { () => { () } } ", "foo MACRO_CALL FileId(2) 0..49 29..32", "#[macro_export]\nmacro_rules! foo { () => { () } }|foo", ); } #[test] fn goto_def_for_macro_defined_fn_with_arg() { check_goto( r#" //- /lib.rs macro_rules! define_fn { ($name:ident) => (fn $name() {}) } define_fn!(foo); fn bar() { <|>foo(); } "#, "foo FN_DEF FileId(1) 65..81 76..79", "define_fn!(foo);|foo", ); } #[test] fn goto_def_for_macro_defined_fn_no_arg() { check_goto( r#" //- /lib.rs macro_rules! define_fn { () => (fn foo() {}) } define_fn!(); fn bar() { <|>foo(); } "#, "foo FN_DEF FileId(1) 52..65 52..65", "define_fn!();|define_fn!();", ); } #[test] fn goto_definition_works_for_macro_inside_pattern() { check_goto( " //- /lib.rs macro_rules! foo {() => {0}} fn bar() { match (0,1) { (<|>foo!(), _) => {} } } ", "foo MACRO_CALL FileId(1) 0..28 13..16", "macro_rules! foo {() => {0}}|foo", ); } #[test] fn goto_definition_works_for_macro_inside_match_arm_lhs() { check_goto( " //- /lib.rs macro_rules! foo {() => {0}} fn bar() { match 0 { <|>foo!() => {} } } ", "foo MACRO_CALL FileId(1) 0..28 13..16", "macro_rules! foo {() => {0}}|foo", ); } #[test] fn goto_def_for_methods() { check_goto( " //- /lib.rs struct Foo; impl Foo { fn frobnicate(&self) { } } fn bar(foo: &Foo) { foo.frobnicate<|>(); } ", "frobnicate FN_DEF FileId(1) 27..51 30..40", "fn frobnicate(&self) { }|frobnicate", ); } #[test] fn goto_def_for_fields() { check_goto( r" //- /lib.rs struct Foo { spam: u32, } fn bar(foo: &Foo) { foo.spam<|>; } ", "spam RECORD_FIELD_DEF FileId(1) 17..26 17..21", "spam: u32|spam", ); } #[test] fn goto_def_for_record_fields() { check_goto( r" //- /lib.rs struct Foo { spam: u32, } fn bar() -> Foo { Foo { spam<|>: 0, } } ", "spam RECORD_FIELD_DEF FileId(1) 17..26 17..21", "spam: u32|spam", ); } #[test] fn goto_def_for_record_pat_fields() { check_goto( r" //- /lib.rs struct Foo { spam: u32, } fn bar(foo: Foo) -> Foo { let Foo { spam<|>: _, } = foo } ", "spam RECORD_FIELD_DEF FileId(1) 17..26 17..21", "spam: u32|spam", ); } #[test] fn goto_def_for_record_fields_macros() { check_goto( r" //- /lib.rs macro_rules! m { () => { 92 };} struct Foo { spam: u32 } fn bar() -> Foo { Foo { spam<|>: m!() } } ", "spam RECORD_FIELD_DEF FileId(1) 45..54 45..49", "spam: u32|spam", ); } #[test] fn goto_for_tuple_fields() { check_goto( " //- /lib.rs struct Foo(u32); fn bar() { let foo = Foo(0); foo.<|>0; } ", "TUPLE_FIELD_DEF FileId(1) 11..14", "u32", ); } #[test] fn goto_def_for_ufcs_inherent_methods() { check_goto( " //- /lib.rs struct Foo; impl Foo { fn frobnicate() { } } fn bar(foo: &Foo) { Foo::frobnicate<|>(); } ", "frobnicate FN_DEF FileId(1) 27..46 30..40", "fn frobnicate() { }|frobnicate", ); } #[test] fn goto_def_for_ufcs_trait_methods_through_traits() { check_goto( " //- /lib.rs trait Foo { fn frobnicate(); } fn bar() { Foo::frobnicate<|>(); } ", "frobnicate FN_DEF FileId(1) 16..32 19..29", "fn frobnicate();|frobnicate", ); } #[test] fn goto_def_for_ufcs_trait_methods_through_self() { check_goto( " //- /lib.rs struct Foo; trait Trait { fn frobnicate(); } impl Trait for Foo {} fn bar() { Foo::frobnicate<|>(); } ", "frobnicate FN_DEF FileId(1) 30..46 33..43", "fn frobnicate();|frobnicate", ); } #[test] fn goto_definition_on_self() { check_goto( " //- /lib.rs struct Foo; impl Foo { pub fn new() -> Self { Self<|> {} } } ", "impl IMPL_DEF FileId(1) 12..73", "impl Foo {...}", ); check_goto( " //- /lib.rs struct Foo; impl Foo { pub fn new() -> Self<|> { Self {} } } ", "impl IMPL_DEF FileId(1) 12..73", "impl Foo {...}", ); check_goto( " //- /lib.rs enum Foo { A } impl Foo { pub fn new() -> Self<|> { Foo::A } } ", "impl IMPL_DEF FileId(1) 15..75", "impl Foo {...}", ); check_goto( " //- /lib.rs enum Foo { A } impl Foo { pub fn thing(a: &Self<|>) { } } ", "impl IMPL_DEF FileId(1) 15..62", "impl Foo {...}", ); } #[test] fn goto_definition_on_self_in_trait_impl() { check_goto( " //- /lib.rs struct Foo; trait Make { fn new() -> Self; } impl Make for Foo { fn new() -> Self { Self<|> {} } } ", "impl IMPL_DEF FileId(1) 49..115", "impl Make for Foo {...}", ); check_goto( " //- /lib.rs struct Foo; trait Make { fn new() -> Self; } impl Make for Foo { fn new() -> Self<|> { Self {} } } ", "impl IMPL_DEF FileId(1) 49..115", "impl Make for Foo {...}", ); } #[test] fn goto_def_when_used_on_definition_name_itself() { check_goto( " //- /lib.rs struct Foo<|> { value: u32 } ", "Foo STRUCT_DEF FileId(1) 0..25 7..10", "struct Foo { value: u32 }|Foo", ); check_goto( r#" //- /lib.rs struct Foo { field<|>: string, } "#, "field RECORD_FIELD_DEF FileId(1) 17..30 17..22", "field: string|field", ); check_goto( " //- /lib.rs fn foo_test<|>() { } ", "foo_test FN_DEF FileId(1) 0..17 3..11", "fn foo_test() { }|foo_test", ); check_goto( " //- /lib.rs enum Foo<|> { Variant, } ", "Foo ENUM_DEF FileId(1) 0..25 5..8", "enum Foo {...}|Foo", ); check_goto( " //- /lib.rs enum Foo { Variant1, Variant2<|>, Variant3, } ", "Variant2 ENUM_VARIANT FileId(1) 29..37 29..37", "Variant2|Variant2", ); check_goto( r#" //- /lib.rs static INNER<|>: &str = ""; "#, "INNER STATIC_DEF FileId(1) 0..24 7..12", "static INNER: &str = \"\";|INNER", ); check_goto( r#" //- /lib.rs const INNER<|>: &str = ""; "#, "INNER CONST_DEF FileId(1) 0..23 6..11", "const INNER: &str = \"\";|INNER", ); check_goto( r#" //- /lib.rs type Thing<|> = Option<()>; "#, "Thing TYPE_ALIAS_DEF FileId(1) 0..24 5..10", "type Thing = Option<()>;|Thing", ); check_goto( r#" //- /lib.rs trait Foo<|> { } "#, "Foo TRAIT_DEF FileId(1) 0..13 6..9", "trait Foo { }|Foo", ); check_goto( r#" //- /lib.rs mod bar<|> { } "#, "bar MODULE FileId(1) 0..11 4..7", "mod bar { }|bar", ); } #[test] fn goto_from_macro() { check_goto( " //- /lib.rs macro_rules! id { ($($tt:tt)*) => { $($tt)* } } fn foo() {} id! { fn bar() { fo<|>o(); } } mod confuse_index { fn foo(); } ", "foo FN_DEF FileId(1) 52..63 55..58", "fn foo() {}|foo", ); } #[test] fn goto_through_format() { check_goto( " //- /lib.rs #[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<|>o()) } ", "foo FN_DEF FileId(1) 398..415 401..404", "fn foo() -> i8 {}|foo", ); } #[test] fn goto_for_type_param() { check_goto( r#" //- /lib.rs struct Foo { t: <|>T, } "#, "T TYPE_PARAM FileId(1) 11..19 11..12", "T: Clone|T", ); } #[test] fn goto_within_macro() { check_goto( r#" //- /lib.rs macro_rules! id { ($($tt:tt)*) => ($($tt)*) } fn foo() { let x = 1; id!({ let y = <|>x; let z = y; }); } "#, "x BIND_PAT FileId(1) 70..71", "x", ); check_goto( r#" //- /lib.rs macro_rules! id { ($($tt:tt)*) => ($($tt)*) } fn foo() { let x = 1; id!({ let y = x; let z = <|>y; }); } "#, "y BIND_PAT FileId(1) 99..100", "y", ); } #[test] fn goto_def_in_local_fn() { check_goto( " //- /lib.rs fn main() { fn foo() { let x = 92; <|>x; } } ", "x BIND_PAT FileId(1) 39..40", "x", ); } #[test] fn goto_def_in_local_macro() { check_goto( r" //- /lib.rs fn bar() { macro_rules! foo { () => { () } } <|>foo!(); } ", "foo MACRO_CALL FileId(1) 15..48 28..31", "macro_rules! foo { () => { () } }|foo", ); } #[test] fn goto_def_for_field_init_shorthand() { check_goto( " //- /lib.rs struct Foo { x: i32 } fn main() { let x = 92; Foo { x<|> }; } ", "x BIND_PAT FileId(1) 42..43", "x", ) } #[test] fn goto_def_for_enum_variant_field() { check_goto( " //- /lib.rs enum Foo { Bar { x: i32 } } fn baz(foo: Foo) { match foo { Foo::Bar { x<|> } => x }; } ", "x RECORD_FIELD_DEF FileId(1) 21..27 21..22", "x: i32|x", ); } #[test] fn goto_def_for_enum_variant_self_pattern_const() { check_goto( " //- /lib.rs enum Foo { Bar, } impl Foo { fn baz(self) { match self { Self::Bar<|> => {} } } } ", "Bar ENUM_VARIANT FileId(1) 15..18 15..18", "Bar|Bar", ); } #[test] fn goto_def_for_enum_variant_self_pattern_record() { check_goto( " //- /lib.rs enum Foo { Bar { val: i32 }, } impl Foo { fn baz(self) -> i32 { match self { Self::Bar<|> { val } => {} } } } ", "Bar ENUM_VARIANT FileId(1) 15..31 15..18", "Bar { val: i32 }|Bar", ); } #[test] fn goto_def_for_enum_variant_self_expr_const() { check_goto( " //- /lib.rs enum Foo { Bar, } impl Foo { fn baz(self) { Self::Bar<|>; } } ", "Bar ENUM_VARIANT FileId(1) 15..18 15..18", "Bar|Bar", ); } #[test] fn goto_def_for_enum_variant_self_expr_record() { check_goto( " //- /lib.rs enum Foo { Bar { val: i32 }, } impl Foo { fn baz(self) { Self::Bar<|> {val: 4}; } } ", "Bar ENUM_VARIANT FileId(1) 15..31 15..18", "Bar { val: i32 }|Bar", ); } }