ide/
goto_implementation.rs

1use hir::{AsAssocItem, Impl, Semantics};
2use ide_db::{
3    RootDatabase,
4    defs::{Definition, NameClass, NameRefClass},
5    helpers::pick_best_token,
6};
7use syntax::{AstNode, SyntaxKind::*, T, ast};
8
9use crate::{FilePosition, NavigationTarget, RangeInfo, TryToNav};
10
11pub struct GotoImplementationConfig {
12    pub filter_adjacent_derive_implementations: bool,
13}
14
15// Feature: Go to Implementation
16//
17// Navigates to the impl items of types.
18//
19// | Editor  | Shortcut |
20// |---------|----------|
21// | VS Code | <kbd>Ctrl+F12</kbd>
22//
23// ![Go to Implementation](https://user-images.githubusercontent.com/48062697/113065566-02f85480-91b1-11eb-9288-aaad8abd8841.gif)
24pub(crate) fn goto_implementation(
25    db: &RootDatabase,
26    config: &GotoImplementationConfig,
27    FilePosition { file_id, offset }: FilePosition,
28) -> Option<RangeInfo<Vec<NavigationTarget>>> {
29    let sema = Semantics::new(db);
30    let source_file = sema.parse_guess_edition(file_id);
31    let syntax = source_file.syntax().clone();
32
33    let original_token = pick_best_token(syntax.token_at_offset(offset), |kind| match kind {
34        IDENT | T![self] | INT_NUMBER => 1,
35        _ => 0,
36    })?;
37    let range = original_token.text_range();
38    let navs = sema
39        .descend_into_macros_exact(original_token)
40        .iter()
41        .filter_map(|token| {
42            token
43                .parent()
44                .and_then(ast::NameLike::cast)
45                .and_then(|node| match &node {
46                    ast::NameLike::Name(name) => {
47                        NameClass::classify(&sema, name).and_then(|class| match class {
48                            NameClass::Definition(it) | NameClass::ConstReference(it) => Some(it),
49                            NameClass::PatFieldShorthand { .. } => None,
50                        })
51                    }
52                    ast::NameLike::NameRef(name_ref) => NameRefClass::classify(&sema, name_ref)
53                        .and_then(|class| match class {
54                            NameRefClass::Definition(def, _) => Some(def),
55                            NameRefClass::FieldShorthand { .. }
56                            | NameRefClass::ExternCrateShorthand { .. } => None,
57                        }),
58                    ast::NameLike::Lifetime(_) => None,
59                })
60                .and_then(|def| {
61                    let navs = match def {
62                        Definition::Trait(trait_) => impls_for_trait(&sema, trait_),
63                        Definition::Adt(adt) => {
64                            let mut impls = Impl::all_for_type(db, adt.ty(sema.db));
65                            if config.filter_adjacent_derive_implementations {
66                                impls.retain(|impl_| {
67                                    sema.impl_generated_from_derive(*impl_) != Some(adt)
68                                });
69                            }
70                            impls
71                                .into_iter()
72                                .filter_map(|imp| imp.try_to_nav(&sema))
73                                .flatten()
74                                .collect()
75                        }
76                        Definition::TypeAlias(alias) => impls_for_ty(&sema, alias.ty(sema.db)),
77                        Definition::BuiltinType(builtin) => {
78                            impls_for_ty(&sema, builtin.ty(sema.db))
79                        }
80                        Definition::Function(f) => {
81                            let assoc = f.as_assoc_item(sema.db)?;
82                            let name = assoc.name(sema.db)?;
83                            let trait_ = assoc.container_or_implemented_trait(sema.db)?;
84                            impls_for_trait_item(&sema, trait_, name)
85                        }
86                        Definition::Const(c) => {
87                            let assoc = c.as_assoc_item(sema.db)?;
88                            let name = assoc.name(sema.db)?;
89                            let trait_ = assoc.container_or_implemented_trait(sema.db)?;
90                            impls_for_trait_item(&sema, trait_, name)
91                        }
92                        _ => return None,
93                    };
94                    Some(navs)
95                })
96        })
97        .flatten()
98        .collect();
99
100    Some(RangeInfo { range, info: navs })
101}
102
103fn impls_for_ty(sema: &Semantics<'_, RootDatabase>, ty: hir::Type<'_>) -> Vec<NavigationTarget> {
104    Impl::all_for_type(sema.db, ty)
105        .into_iter()
106        .filter_map(|imp| imp.try_to_nav(sema))
107        .flatten()
108        .collect()
109}
110
111fn impls_for_trait(
112    sema: &Semantics<'_, RootDatabase>,
113    trait_: hir::Trait,
114) -> Vec<NavigationTarget> {
115    Impl::all_for_trait(sema.db, trait_)
116        .into_iter()
117        .filter_map(|imp| imp.try_to_nav(sema))
118        .flatten()
119        .collect()
120}
121
122fn impls_for_trait_item(
123    sema: &Semantics<'_, RootDatabase>,
124    trait_: hir::Trait,
125    fun_name: hir::Name,
126) -> Vec<NavigationTarget> {
127    Impl::all_for_trait(sema.db, trait_)
128        .into_iter()
129        .filter_map(|imp| {
130            let item = imp.items(sema.db).iter().find_map(|itm| {
131                let itm_name = itm.name(sema.db)?;
132                (itm_name == fun_name).then_some(*itm)
133            })?;
134            item.try_to_nav(sema)
135        })
136        .flatten()
137        .collect()
138}
139
140#[cfg(test)]
141mod tests {
142    use ide_db::FileRange;
143    use itertools::Itertools;
144
145    use crate::{GotoImplementationConfig, fixture};
146
147    const TEST_CONFIG: &GotoImplementationConfig =
148        &GotoImplementationConfig { filter_adjacent_derive_implementations: false };
149
150    #[track_caller]
151    fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
152        check_with_config(TEST_CONFIG, ra_fixture);
153    }
154
155    #[track_caller]
156    fn check_with_config(
157        config: &GotoImplementationConfig,
158        #[rust_analyzer::rust_fixture] ra_fixture: &str,
159    ) {
160        let (analysis, position, expected) = fixture::annotations(ra_fixture);
161
162        let navs = analysis.goto_implementation(config, position).unwrap().unwrap().info;
163
164        let cmp = |frange: &FileRange| (frange.file_id, frange.range.start());
165
166        let actual = navs
167            .into_iter()
168            .map(|nav| FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
169            .sorted_by_key(cmp)
170            .collect::<Vec<_>>();
171        let expected =
172            expected.into_iter().map(|(range, _)| range).sorted_by_key(cmp).collect::<Vec<_>>();
173        assert_eq!(expected, actual);
174    }
175
176    #[test]
177    fn goto_implementation_works() {
178        check(
179            r#"
180struct Foo$0;
181impl Foo {}
182   //^^^
183"#,
184        );
185    }
186
187    #[test]
188    fn goto_implementation_works_multiple_blocks() {
189        check(
190            r#"
191struct Foo$0;
192impl Foo {}
193   //^^^
194impl Foo {}
195   //^^^
196"#,
197        );
198    }
199
200    #[test]
201    fn goto_implementation_works_multiple_mods() {
202        check(
203            r#"
204struct Foo$0;
205mod a {
206    impl super::Foo {}
207       //^^^^^^^^^^
208}
209mod b {
210    impl super::Foo {}
211       //^^^^^^^^^^
212}
213"#,
214        );
215    }
216
217    #[test]
218    fn goto_implementation_works_multiple_files() {
219        check(
220            r#"
221//- /lib.rs
222struct Foo$0;
223mod a;
224mod b;
225//- /a.rs
226impl crate::Foo {}
227   //^^^^^^^^^^
228//- /b.rs
229impl crate::Foo {}
230   //^^^^^^^^^^
231"#,
232        );
233    }
234
235    #[test]
236    fn goto_implementation_for_trait() {
237        check(
238            r#"
239trait T$0 {}
240struct Foo;
241impl T for Foo {}
242         //^^^
243"#,
244        );
245    }
246
247    #[test]
248    fn goto_implementation_for_trait_multiple_files() {
249        check(
250            r#"
251//- /lib.rs
252trait T$0 {};
253struct Foo;
254mod a;
255mod b;
256//- /a.rs
257impl crate::T for crate::Foo {}
258                //^^^^^^^^^^
259//- /b.rs
260impl crate::T for crate::Foo {}
261                //^^^^^^^^^^
262            "#,
263        );
264    }
265
266    // FIXME(next-solver): it would be nice to be able to also point to `&Foo`
267    #[test]
268    fn goto_implementation_all_impls() {
269        check(
270            r#"
271//- /lib.rs
272trait T {}
273struct Foo$0;
274impl Foo {}
275   //^^^
276impl T for Foo {}
277         //^^^
278impl T for &Foo {}
279"#,
280        );
281    }
282
283    #[test]
284    fn goto_implementation_to_builtin_derive() {
285        check(
286            r#"
287//- minicore: copy, derive
288  #[derive(Copy)]
289         //^^^^
290struct Foo$0;
291"#,
292        );
293    }
294
295    #[test]
296    fn goto_implementation_type_alias() {
297        check(
298            r#"
299struct Foo;
300
301type Bar$0 = Foo;
302
303impl Foo {}
304   //^^^
305impl Bar {}
306   //^^^
307"#,
308        );
309    }
310
311    #[test]
312    fn goto_implementation_adt_generic() {
313        check(
314            r#"
315struct Foo$0<T>;
316
317impl<T> Foo<T> {}
318      //^^^^^^
319impl Foo<str> {}
320   //^^^^^^^^
321"#,
322        );
323    }
324
325    #[test]
326    fn goto_implementation_builtin() {
327        check(
328            r#"
329//- /lib.rs crate:main deps:core
330fn foo(_: bool$0) {{}}
331//- /libcore.rs crate:core
332#![rustc_coherence_is_core]
333#[lang = "bool"]
334impl bool {}
335   //^^^^
336"#,
337        );
338    }
339
340    #[test]
341    fn goto_implementation_trait_functions() {
342        check(
343            r#"
344trait Tr {
345    fn f$0();
346}
347
348struct S;
349
350impl Tr for S {
351    fn f() {
352     //^
353        println!("Hello, world!");
354    }
355}
356"#,
357        );
358    }
359
360    #[test]
361    fn goto_implementation_trait_assoc_const() {
362        check(
363            r#"
364trait Tr {
365    const C$0: usize;
366}
367
368struct S;
369
370impl Tr for S {
371    const C: usize = 4;
372        //^
373}
374"#,
375        );
376    }
377
378    #[test]
379    fn goto_adt_implementation_inside_block() {
380        check(
381            r#"
382//- minicore: copy, derive
383trait Bar {}
384
385fn test() {
386    #[derive(Copy)]
387  //^^^^^^^^^^^^^^^
388    struct Foo$0;
389
390    impl Foo {}
391       //^^^
392
393    trait Baz {}
394
395    impl Bar for Foo {}
396               //^^^
397
398    impl Baz for Foo {}
399               //^^^
400}
401"#,
402        );
403    }
404
405    #[test]
406    fn goto_trait_implementation_inside_block() {
407        check(
408            r#"
409struct Bar;
410
411fn test() {
412    trait Foo$0 {}
413
414    struct Baz;
415
416    impl Foo for Bar {}
417               //^^^
418
419    impl Foo for Baz {}
420               //^^^
421}
422"#,
423        );
424        check(
425            r#"
426struct Bar;
427
428fn test() {
429    trait Foo {
430        fn foo$0() {}
431    }
432
433    struct Baz;
434
435    impl Foo for Bar {
436        fn foo() {}
437         //^^^
438    }
439
440    impl Foo for Baz {
441        fn foo() {}
442         //^^^
443    }
444}
445"#,
446        );
447    }
448
449    #[test]
450    fn filter_adjacent_derives() {
451        check_with_config(
452            &GotoImplementationConfig { filter_adjacent_derive_implementations: true },
453            r#"
454//- minicore: clone, copy, derive
455
456#[derive(Clone, Copy)]
457struct Foo$0;
458
459trait Bar {}
460
461impl Bar for Foo {}
462          // ^^^
463            "#,
464        );
465    }
466}