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