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
13pub(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}