]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs
Rollup merge of #100228 - luqmana:suggestion-ice, r=estebank
[rust.git] / src / tools / rust-analyzer / crates / ide / src / goto_implementation.rs
1 use hir::{AsAssocItem, Impl, Semantics};
2 use ide_db::{
3     defs::{Definition, NameClass, NameRefClass},
4     helpers::pick_best_token,
5     RootDatabase,
6 };
7 use itertools::Itertools;
8 use syntax::{ast, AstNode, SyntaxKind::*, T};
9
10 use crate::{FilePosition, NavigationTarget, RangeInfo, TryToNav};
11
12 // Feature: Go to Implementation
13 //
14 // Navigates to the impl blocks of types.
15 //
16 // |===
17 // | Editor  | Shortcut
18 //
19 // | VS Code | kbd:[Ctrl+F12]
20 // |===
21 //
22 // image::https://user-images.githubusercontent.com/48062697/113065566-02f85480-91b1-11eb-9288-aaad8abd8841.gif[]
23 pub(crate) fn goto_implementation(
24     db: &RootDatabase,
25     position: FilePosition,
26 ) -> Option<RangeInfo<Vec<NavigationTarget>>> {
27     let sema = Semantics::new(db);
28     let source_file = sema.parse(position.file_id);
29     let syntax = source_file.syntax().clone();
30
31     let original_token =
32         pick_best_token(syntax.token_at_offset(position.offset), |kind| match kind {
33             IDENT | T![self] | INT_NUMBER => 1,
34             _ => 0,
35         })?;
36     let range = original_token.text_range();
37     let navs = sema
38         .descend_into_macros(original_token)
39         .into_iter()
40         .filter_map(|token| token.parent().and_then(ast::NameLike::cast))
41         .filter_map(|node| match &node {
42             ast::NameLike::Name(name) => {
43                 NameClass::classify(&sema, name).map(|class| match class {
44                     NameClass::Definition(it) | NameClass::ConstReference(it) => it,
45                     NameClass::PatFieldShorthand { local_def, field_ref: _ } => {
46                         Definition::Local(local_def)
47                     }
48                 })
49             }
50             ast::NameLike::NameRef(name_ref) => {
51                 NameRefClass::classify(&sema, name_ref).map(|class| match class {
52                     NameRefClass::Definition(def) => def,
53                     NameRefClass::FieldShorthand { local_ref, field_ref: _ } => {
54                         Definition::Local(local_ref)
55                     }
56                 })
57             }
58             ast::NameLike::Lifetime(_) => None,
59         })
60         .unique()
61         .filter_map(|def| {
62             let navs = match def {
63                 Definition::Trait(trait_) => impls_for_trait(&sema, trait_),
64                 Definition::Adt(adt) => impls_for_ty(&sema, adt.ty(sema.db)),
65                 Definition::TypeAlias(alias) => impls_for_ty(&sema, alias.ty(sema.db)),
66                 Definition::BuiltinType(builtin) => impls_for_ty(&sema, builtin.ty(sema.db)),
67                 Definition::Function(f) => {
68                     let assoc = f.as_assoc_item(sema.db)?;
69                     let name = assoc.name(sema.db)?;
70                     let trait_ = assoc.containing_trait_or_trait_impl(sema.db)?;
71                     impls_for_trait_item(&sema, trait_, name)
72                 }
73                 Definition::Const(c) => {
74                     let assoc = c.as_assoc_item(sema.db)?;
75                     let name = assoc.name(sema.db)?;
76                     let trait_ = assoc.containing_trait_or_trait_impl(sema.db)?;
77                     impls_for_trait_item(&sema, trait_, name)
78                 }
79                 _ => return None,
80             };
81             Some(navs)
82         })
83         .flatten()
84         .collect();
85
86     Some(RangeInfo { range, info: navs })
87 }
88
89 fn impls_for_ty(sema: &Semantics<'_, RootDatabase>, ty: hir::Type) -> Vec<NavigationTarget> {
90     Impl::all_for_type(sema.db, ty).into_iter().filter_map(|imp| imp.try_to_nav(sema.db)).collect()
91 }
92
93 fn impls_for_trait(
94     sema: &Semantics<'_, RootDatabase>,
95     trait_: hir::Trait,
96 ) -> Vec<NavigationTarget> {
97     Impl::all_for_trait(sema.db, trait_)
98         .into_iter()
99         .filter_map(|imp| imp.try_to_nav(sema.db))
100         .collect()
101 }
102
103 fn impls_for_trait_item(
104     sema: &Semantics<'_, RootDatabase>,
105     trait_: hir::Trait,
106     fun_name: hir::Name,
107 ) -> Vec<NavigationTarget> {
108     Impl::all_for_trait(sema.db, trait_)
109         .into_iter()
110         .filter_map(|imp| {
111             let item = imp.items(sema.db).iter().find_map(|itm| {
112                 let itm_name = itm.name(sema.db)?;
113                 (itm_name == fun_name).then(|| *itm)
114             })?;
115             item.try_to_nav(sema.db)
116         })
117         .collect()
118 }
119
120 #[cfg(test)]
121 mod tests {
122     use ide_db::base_db::FileRange;
123     use itertools::Itertools;
124
125     use crate::fixture;
126
127     fn check(ra_fixture: &str) {
128         let (analysis, position, expected) = fixture::annotations(ra_fixture);
129
130         let navs = analysis.goto_implementation(position).unwrap().unwrap().info;
131
132         let cmp = |frange: &FileRange| (frange.file_id, frange.range.start());
133
134         let actual = navs
135             .into_iter()
136             .map(|nav| FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
137             .sorted_by_key(cmp)
138             .collect::<Vec<_>>();
139         let expected =
140             expected.into_iter().map(|(range, _)| range).sorted_by_key(cmp).collect::<Vec<_>>();
141         assert_eq!(expected, actual);
142     }
143
144     #[test]
145     fn goto_implementation_works() {
146         check(
147             r#"
148 struct Foo$0;
149 impl Foo {}
150    //^^^
151 "#,
152         );
153     }
154
155     #[test]
156     fn goto_implementation_works_multiple_blocks() {
157         check(
158             r#"
159 struct Foo$0;
160 impl Foo {}
161    //^^^
162 impl Foo {}
163    //^^^
164 "#,
165         );
166     }
167
168     #[test]
169     fn goto_implementation_works_multiple_mods() {
170         check(
171             r#"
172 struct Foo$0;
173 mod a {
174     impl super::Foo {}
175        //^^^^^^^^^^
176 }
177 mod b {
178     impl super::Foo {}
179        //^^^^^^^^^^
180 }
181 "#,
182         );
183     }
184
185     #[test]
186     fn goto_implementation_works_multiple_files() {
187         check(
188             r#"
189 //- /lib.rs
190 struct Foo$0;
191 mod a;
192 mod b;
193 //- /a.rs
194 impl crate::Foo {}
195    //^^^^^^^^^^
196 //- /b.rs
197 impl crate::Foo {}
198    //^^^^^^^^^^
199 "#,
200         );
201     }
202
203     #[test]
204     fn goto_implementation_for_trait() {
205         check(
206             r#"
207 trait T$0 {}
208 struct Foo;
209 impl T for Foo {}
210          //^^^
211 "#,
212         );
213     }
214
215     #[test]
216     fn goto_implementation_for_trait_multiple_files() {
217         check(
218             r#"
219 //- /lib.rs
220 trait T$0 {};
221 struct Foo;
222 mod a;
223 mod b;
224 //- /a.rs
225 impl crate::T for crate::Foo {}
226                 //^^^^^^^^^^
227 //- /b.rs
228 impl crate::T for crate::Foo {}
229                 //^^^^^^^^^^
230             "#,
231         );
232     }
233
234     #[test]
235     fn goto_implementation_all_impls() {
236         check(
237             r#"
238 //- /lib.rs
239 trait T {}
240 struct Foo$0;
241 impl Foo {}
242    //^^^
243 impl T for Foo {}
244          //^^^
245 impl T for &Foo {}
246          //^^^^
247 "#,
248         );
249     }
250
251     #[test]
252     fn goto_implementation_to_builtin_derive() {
253         check(
254             r#"
255 //- minicore: copy, derive
256   #[derive(Copy)]
257 //^^^^^^^^^^^^^^^
258 struct Foo$0;
259 "#,
260         );
261     }
262
263     #[test]
264     fn goto_implementation_type_alias() {
265         check(
266             r#"
267 struct Foo;
268
269 type Bar$0 = Foo;
270
271 impl Foo {}
272    //^^^
273 impl Bar {}
274    //^^^
275 "#,
276         );
277     }
278
279     #[test]
280     fn goto_implementation_adt_generic() {
281         check(
282             r#"
283 struct Foo$0<T>;
284
285 impl<T> Foo<T> {}
286       //^^^^^^
287 impl Foo<str> {}
288    //^^^^^^^^
289 "#,
290         );
291     }
292
293     #[test]
294     fn goto_implementation_builtin() {
295         check(
296             r#"
297 //- /lib.rs crate:main deps:core
298 fn foo(_: bool$0) {{}}
299 //- /libcore.rs crate:core
300 #[lang = "bool"]
301 impl bool {}
302    //^^^^
303 "#,
304         );
305     }
306
307     #[test]
308     fn goto_implementation_trait_functions() {
309         check(
310             r#"
311 trait Tr {
312     fn f$0();
313 }
314
315 struct S;
316
317 impl Tr for S {
318     fn f() {
319      //^
320         println!("Hello, world!");
321     }
322 }
323 "#,
324         );
325     }
326
327     #[test]
328     fn goto_implementation_trait_assoc_const() {
329         check(
330             r#"
331 trait Tr {
332     const C$0: usize;
333 }
334
335 struct S;
336
337 impl Tr for S {
338     const C: usize = 4;
339         //^
340 }
341 "#,
342         );
343     }
344 }