ide/
goto_declaration.rs

1use hir::{AsAssocItem, Semantics};
2use ide_db::{
3    RootDatabase,
4    defs::{Definition, NameClass, NameRefClass},
5};
6use syntax::{AstNode, SyntaxKind::*, T, ast, match_ast};
7
8use crate::{
9    FilePosition, GotoDefinitionConfig, NavigationTarget, RangeInfo,
10    goto_definition::goto_definition, navigation_target::TryToNav,
11};
12
13// Feature: Go to Declaration
14//
15// Navigates to the declaration of an identifier.
16//
17// This is the same as `Go to Definition` with the following exceptions:
18// - outline modules will navigate to the `mod name;` item declaration
19// - trait assoc items will navigate to the assoc item of the trait declaration as opposed to the trait impl
20// - fields in patterns will navigate to the field declaration of the struct, union or variant
21pub(crate) fn goto_declaration(
22    db: &RootDatabase,
23    position @ FilePosition { file_id, offset }: FilePosition,
24    config: &GotoDefinitionConfig<'_>,
25) -> Option<RangeInfo<Vec<NavigationTarget>>> {
26    let sema = Semantics::new(db);
27    let file = sema.parse_guess_edition(file_id).syntax().clone();
28    let original_token = file
29        .token_at_offset(offset)
30        .find(|it| matches!(it.kind(), IDENT | T![self] | T![super] | T![crate] | T![Self]))?;
31    let range = original_token.text_range();
32    let info: Vec<NavigationTarget> = sema
33        .descend_into_macros_no_opaque(original_token, false)
34        .iter()
35        .filter_map(|token| {
36            let parent = token.value.parent()?;
37            let def = match_ast! {
38                match parent {
39                    ast::NameRef(name_ref) => match NameRefClass::classify(&sema, &name_ref)? {
40                        NameRefClass::Definition(it, _) => Some(it),
41                        NameRefClass::FieldShorthand { field_ref, .. } =>
42                            return field_ref.try_to_nav(&sema),
43                        NameRefClass::ExternCrateShorthand { decl, .. } =>
44                            return decl.try_to_nav(&sema),
45                    },
46                    ast::Name(name) => match NameClass::classify(&sema, &name)? {
47                        NameClass::Definition(it) | NameClass::ConstReference(it) => Some(it),
48                        NameClass::PatFieldShorthand { field_ref, .. } =>
49                            return field_ref.try_to_nav(&sema),
50                    },
51                    _ => None
52                }
53            };
54            let assoc = match def? {
55                Definition::Module(module) => {
56                    return Some(NavigationTarget::from_module_to_decl(db, module));
57                }
58                Definition::Const(c) => c.as_assoc_item(db),
59                Definition::TypeAlias(ta) => ta.as_assoc_item(db),
60                Definition::Function(f) => f.as_assoc_item(db),
61                Definition::ExternCrateDecl(it) => return it.try_to_nav(&sema),
62                _ => None,
63            }?;
64
65            let trait_ = assoc.implemented_trait(db)?;
66            let name = Some(assoc.name(db)?);
67            let item = trait_.items(db).into_iter().find(|it| it.name(db) == name)?;
68            item.try_to_nav(&sema)
69        })
70        .flatten()
71        .collect();
72
73    if info.is_empty() {
74        goto_definition(db, position, config)
75    } else {
76        Some(RangeInfo::new(range, info))
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use ide_db::{FileRange, MiniCore};
83    use itertools::Itertools;
84
85    use crate::{GotoDefinitionConfig, fixture};
86
87    const TEST_CONFIG: GotoDefinitionConfig<'_> =
88        GotoDefinitionConfig { minicore: MiniCore::default() };
89
90    fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
91        let (analysis, position, expected) = fixture::annotations(ra_fixture);
92        let navs = analysis
93            .goto_declaration(position, &TEST_CONFIG)
94            .unwrap()
95            .expect("no declaration or definition found")
96            .info;
97        if navs.is_empty() {
98            panic!("unresolved reference")
99        }
100
101        let cmp = |&FileRange { file_id, range }: &_| (file_id, range.start());
102        let navs = navs
103            .into_iter()
104            .map(|nav| FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
105            .sorted_by_key(cmp)
106            .collect::<Vec<_>>();
107        let expected = expected
108            .into_iter()
109            .map(|(FileRange { file_id, range }, _)| FileRange { file_id, range })
110            .sorted_by_key(cmp)
111            .collect::<Vec<_>>();
112        assert_eq!(expected, navs);
113    }
114
115    #[test]
116    fn goto_decl_module_outline() {
117        check(
118            r#"
119//- /main.rs
120mod foo;
121 // ^^^
122//- /foo.rs
123use self$0;
124"#,
125        )
126    }
127
128    #[test]
129    fn goto_decl_module_inline() {
130        check(
131            r#"
132mod foo {
133 // ^^^
134    use self$0;
135}
136"#,
137        )
138    }
139
140    #[test]
141    fn goto_decl_goto_def_fallback() {
142        check(
143            r#"
144struct Foo;
145    // ^^^
146impl Foo$0 {}
147"#,
148        );
149    }
150
151    #[test]
152    fn goto_decl_assoc_item_no_impl_item() {
153        check(
154            r#"
155trait Trait {
156    const C: () = ();
157       // ^
158}
159impl Trait for () {}
160
161fn main() {
162    <()>::C$0;
163}
164"#,
165        );
166    }
167
168    #[test]
169    fn goto_decl_assoc_item() {
170        check(
171            r#"
172trait Trait {
173    const C: () = ();
174       // ^
175}
176impl Trait for () {
177    const C: () = ();
178}
179
180fn main() {
181    <()>::C$0;
182}
183"#,
184        );
185        check(
186            r#"
187trait Trait {
188    const C: () = ();
189       // ^
190}
191impl Trait for () {
192    const C$0: () = ();
193}
194"#,
195        );
196    }
197
198    #[test]
199    fn goto_decl_field_pat_shorthand() {
200        check(
201            r#"
202struct Foo { field: u32 }
203           //^^^^^
204fn main() {
205    let Foo { field$0 };
206}
207"#,
208        );
209    }
210
211    #[test]
212    fn goto_decl_constructor_shorthand() {
213        check(
214            r#"
215struct Foo { field: u32 }
216           //^^^^^
217fn main() {
218    let field = 0;
219    Foo { field$0 };
220}
221"#,
222        );
223    }
224
225    #[test]
226    fn goto_decl_for_extern_crate() {
227        check(
228            r#"
229//- /main.rs crate:main deps:std
230extern crate std$0;
231         /// ^^^
232//- /std/lib.rs crate:std
233// empty
234"#,
235        )
236    }
237
238    #[test]
239    fn goto_decl_for_renamed_extern_crate() {
240        check(
241            r#"
242//- /main.rs crate:main deps:std
243extern crate std as abc$0;
244                /// ^^^
245//- /std/lib.rs crate:std
246// empty
247"#,
248        )
249    }
250}