]> git.lizzy.rs Git - rust.git/blob - crates/ide-db/src/traits.rs
fix: Fix search for associated trait items being inconsistent
[rust.git] / crates / ide-db / src / traits.rs
1 //! Functionality for obtaining data related to traits from the DB.
2
3 use crate::{defs::Definition, RootDatabase};
4 use hir::{db::HirDatabase, AsAssocItem, Semantics};
5 use rustc_hash::FxHashSet;
6 use syntax::{ast, AstNode};
7
8 /// Given the `impl` block, attempts to find the trait this `impl` corresponds to.
9 pub fn resolve_target_trait(
10     sema: &Semantics<RootDatabase>,
11     impl_def: &ast::Impl,
12 ) -> Option<hir::Trait> {
13     let ast_path =
14         impl_def.trait_().map(|it| it.syntax().clone()).and_then(ast::PathType::cast)?.path()?;
15
16     match sema.resolve_path(&ast_path) {
17         Some(hir::PathResolution::Def(hir::ModuleDef::Trait(def))) => Some(def),
18         _ => None,
19     }
20 }
21
22 /// Given the `impl` block, returns the list of associated items (e.g. functions or types) that are
23 /// missing in this `impl` block.
24 pub fn get_missing_assoc_items(
25     sema: &Semantics<RootDatabase>,
26     impl_def: &ast::Impl,
27 ) -> Vec<hir::AssocItem> {
28     let imp = match sema.to_def(impl_def) {
29         Some(it) => it,
30         None => return vec![],
31     };
32
33     // Names must be unique between constants and functions. However, type aliases
34     // may share the same name as a function or constant.
35     let mut impl_fns_consts = FxHashSet::default();
36     let mut impl_type = FxHashSet::default();
37
38     for item in imp.items(sema.db) {
39         match item {
40             hir::AssocItem::Function(it) => {
41                 impl_fns_consts.insert(it.name(sema.db).to_string());
42             }
43             hir::AssocItem::Const(it) => {
44                 if let Some(name) = it.name(sema.db) {
45                     impl_fns_consts.insert(name.to_string());
46                 }
47             }
48             hir::AssocItem::TypeAlias(it) => {
49                 impl_type.insert(it.name(sema.db).to_string());
50             }
51         }
52     }
53
54     resolve_target_trait(sema, impl_def).map_or(vec![], |target_trait| {
55         target_trait
56             .items(sema.db)
57             .into_iter()
58             .filter(|i| match i {
59                 hir::AssocItem::Function(f) => {
60                     !impl_fns_consts.contains(&f.name(sema.db).to_string())
61                 }
62                 hir::AssocItem::TypeAlias(t) => !impl_type.contains(&t.name(sema.db).to_string()),
63                 hir::AssocItem::Const(c) => c
64                     .name(sema.db)
65                     .map(|n| !impl_fns_consts.contains(&n.to_string()))
66                     .unwrap_or_default(),
67             })
68             .collect()
69     })
70 }
71
72 /// Converts associated trait impl items to their trait definition counterpart
73 pub(crate) fn convert_to_def_in_trait(db: &dyn HirDatabase, def: Definition) -> Definition {
74     (|| {
75         let assoc = def.as_assoc_item(db)?;
76         let trait_ = assoc.containing_trait_impl(db)?;
77         assoc_item_of_trait(db, assoc, trait_)
78     })()
79     .unwrap_or(def)
80 }
81
82 /// If this is an trait (impl) assoc item, returns the assoc item of the corresponding trait definition.
83 pub(crate) fn as_trait_assoc_def(db: &dyn HirDatabase, def: Definition) -> Option<Definition> {
84     let assoc = def.as_assoc_item(db)?;
85     let trait_ = match assoc.container(db) {
86         hir::AssocItemContainer::Trait(_) => return Some(def),
87         hir::AssocItemContainer::Impl(i) => i.trait_(db),
88     }?;
89     assoc_item_of_trait(db, assoc, trait_)
90 }
91
92 fn assoc_item_of_trait(
93     db: &dyn HirDatabase,
94     assoc: hir::AssocItem,
95     trait_: hir::Trait,
96 ) -> Option<Definition> {
97     use hir::AssocItem::*;
98     let name = match assoc {
99         Function(it) => it.name(db),
100         Const(it) => it.name(db)?,
101         TypeAlias(it) => it.name(db),
102     };
103     let item = trait_.items(db).into_iter().find(|it| match (it, assoc) {
104         (Function(trait_func), Function(_)) => trait_func.name(db) == name,
105         (Const(trait_konst), Const(_)) => trait_konst.name(db).map_or(false, |it| it == name),
106         (TypeAlias(trait_type_alias), TypeAlias(_)) => trait_type_alias.name(db) == name,
107         _ => false,
108     })?;
109     Some(Definition::from(item))
110 }
111
112 #[cfg(test)]
113 mod tests {
114     use base_db::{fixture::ChangeFixture, FilePosition};
115     use expect_test::{expect, Expect};
116     use hir::Semantics;
117     use syntax::ast::{self, AstNode};
118
119     use crate::RootDatabase;
120
121     /// Creates analysis from a multi-file fixture, returns positions marked with $0.
122     pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
123         let change_fixture = ChangeFixture::parse(ra_fixture);
124         let mut database = RootDatabase::default();
125         database.apply_change(change_fixture.change);
126         let (file_id, range_or_offset) =
127             change_fixture.file_position.expect("expected a marker ($0)");
128         let offset = range_or_offset.expect_offset();
129         (database, FilePosition { file_id, offset })
130     }
131
132     fn check_trait(ra_fixture: &str, expect: Expect) {
133         let (db, position) = position(ra_fixture);
134         let sema = Semantics::new(&db);
135         let file = sema.parse(position.file_id);
136         let impl_block: ast::Impl =
137             sema.find_node_at_offset_with_descend(file.syntax(), position.offset).unwrap();
138         let trait_ = crate::traits::resolve_target_trait(&sema, &impl_block);
139         let actual = match trait_ {
140             Some(trait_) => trait_.name(&db).to_string(),
141             None => String::new(),
142         };
143         expect.assert_eq(&actual);
144     }
145
146     fn check_missing_assoc(ra_fixture: &str, expect: Expect) {
147         let (db, position) = position(ra_fixture);
148         let sema = Semantics::new(&db);
149         let file = sema.parse(position.file_id);
150         let impl_block: ast::Impl =
151             sema.find_node_at_offset_with_descend(file.syntax(), position.offset).unwrap();
152         let items = crate::traits::get_missing_assoc_items(&sema, &impl_block);
153         let actual = items
154             .into_iter()
155             .map(|item| item.name(&db).unwrap().to_string())
156             .collect::<Vec<_>>()
157             .join("\n");
158         expect.assert_eq(&actual);
159     }
160
161     #[test]
162     fn resolve_trait() {
163         check_trait(
164             r#"
165 pub trait Foo {
166     fn bar();
167 }
168 impl Foo for u8 {
169     $0
170 }
171             "#,
172             expect![["Foo"]],
173         );
174         check_trait(
175             r#"
176 pub trait Foo {
177     fn bar();
178 }
179 impl Foo for u8 {
180     fn bar() {
181         fn baz() {
182             $0
183         }
184         baz();
185     }
186 }
187             "#,
188             expect![["Foo"]],
189         );
190         check_trait(
191             r#"
192 pub trait Foo {
193     fn bar();
194 }
195 pub struct Bar;
196 impl Bar {
197     $0
198 }
199             "#,
200             expect![[""]],
201         );
202     }
203
204     #[test]
205     fn missing_assoc_items() {
206         check_missing_assoc(
207             r#"
208 pub trait Foo {
209     const FOO: u8;
210     fn bar();
211 }
212 impl Foo for u8 {
213     $0
214 }"#,
215             expect![[r#"
216                 FOO
217                 bar"#]],
218         );
219
220         check_missing_assoc(
221             r#"
222 pub trait Foo {
223     const FOO: u8;
224     fn bar();
225 }
226 impl Foo for u8 {
227     const FOO: u8 = 10;
228     $0
229 }"#,
230             expect![[r#"
231                 bar"#]],
232         );
233
234         check_missing_assoc(
235             r#"
236 pub trait Foo {
237     const FOO: u8;
238     fn bar();
239 }
240 impl Foo for u8 {
241     const FOO: u8 = 10;
242     fn bar() {$0}
243 }"#,
244             expect![[r#""#]],
245         );
246
247         check_missing_assoc(
248             r#"
249 pub struct Foo;
250 impl Foo {
251     fn bar() {$0}
252 }"#,
253             expect![[r#""#]],
254         );
255
256         check_missing_assoc(
257             r#"
258 trait Tr {
259     fn required();
260 }
261 macro_rules! m {
262     () => { fn required() {} };
263 }
264 impl Tr for () {
265     m!();
266     $0
267 }
268
269             "#,
270             expect![[r#""#]],
271         );
272     }
273 }