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.
resolve_target_trait( sema: &Semantics<'_, RootDatabase>, impl_def: &ast::Impl, ) -> Option<hir::Trait>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.
get_missing_assoc_items( sema: &Semantics<'_, RootDatabase>, impl_def: &ast::Impl, ) -> Vec<hir::AssocItem>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).display(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.display(sema.db).to_string());
46 }
47 }
48 hir::AssocItem::TypeAlias(it) => {
49 impl_type.insert(it.name(sema.db).display(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).display(sema.db).to_string())
61 }
62 hir::AssocItem::TypeAlias(t) => {
63 !impl_type.contains(&t.name(sema.db).display(sema.db).to_string())
64 }
65 hir::AssocItem::Const(c) => c
66 .name(sema.db)
67 .map(|n| !impl_fns_consts.contains(&n.display(sema.db).to_string()))
68 .unwrap_or_default(),
69 })
70 .collect()
71 })
72 }
73
74 /// Converts associated trait impl items to their trait definition counterpart
convert_to_def_in_trait(db: &dyn HirDatabase, def: Definition) -> Definition75 pub(crate) fn convert_to_def_in_trait(db: &dyn HirDatabase, def: Definition) -> Definition {
76 (|| {
77 let assoc = def.as_assoc_item(db)?;
78 let trait_ = assoc.containing_trait_impl(db)?;
79 assoc_item_of_trait(db, assoc, trait_)
80 })()
81 .unwrap_or(def)
82 }
83
84 /// If this is an trait (impl) assoc item, returns the assoc item of the corresponding trait definition.
as_trait_assoc_def(db: &dyn HirDatabase, def: Definition) -> Option<Definition>85 pub(crate) fn as_trait_assoc_def(db: &dyn HirDatabase, def: Definition) -> Option<Definition> {
86 let assoc = def.as_assoc_item(db)?;
87 let trait_ = match assoc.container(db) {
88 hir::AssocItemContainer::Trait(_) => return Some(def),
89 hir::AssocItemContainer::Impl(i) => i.trait_(db),
90 }?;
91 assoc_item_of_trait(db, assoc, trait_)
92 }
93
assoc_item_of_trait( db: &dyn HirDatabase, assoc: hir::AssocItem, trait_: hir::Trait, ) -> Option<Definition>94 fn assoc_item_of_trait(
95 db: &dyn HirDatabase,
96 assoc: hir::AssocItem,
97 trait_: hir::Trait,
98 ) -> Option<Definition> {
99 use hir::AssocItem::*;
100 let name = match assoc {
101 Function(it) => it.name(db),
102 Const(it) => it.name(db)?,
103 TypeAlias(it) => it.name(db),
104 };
105 let item = trait_.items(db).into_iter().find(|it| match (it, assoc) {
106 (Function(trait_func), Function(_)) => trait_func.name(db) == name,
107 (Const(trait_konst), Const(_)) => trait_konst.name(db).map_or(false, |it| it == name),
108 (TypeAlias(trait_type_alias), TypeAlias(_)) => trait_type_alias.name(db) == name,
109 _ => false,
110 })?;
111 Some(Definition::from(item))
112 }
113
114 #[cfg(test)]
115 mod tests {
116 use base_db::{fixture::ChangeFixture, FilePosition};
117 use expect_test::{expect, Expect};
118 use hir::Semantics;
119 use syntax::ast::{self, AstNode};
120
121 use crate::RootDatabase;
122
123 /// Creates analysis from a multi-file fixture, returns positions marked with $0.
position(ra_fixture: &str) -> (RootDatabase, FilePosition)124 pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
125 let change_fixture = ChangeFixture::parse(ra_fixture);
126 let mut database = RootDatabase::default();
127 database.apply_change(change_fixture.change);
128 let (file_id, range_or_offset) =
129 change_fixture.file_position.expect("expected a marker ($0)");
130 let offset = range_or_offset.expect_offset();
131 (database, FilePosition { file_id, offset })
132 }
133
check_trait(ra_fixture: &str, expect: Expect)134 fn check_trait(ra_fixture: &str, expect: Expect) {
135 let (db, position) = position(ra_fixture);
136 let sema = Semantics::new(&db);
137 let file = sema.parse(position.file_id);
138 let impl_block: ast::Impl =
139 sema.find_node_at_offset_with_descend(file.syntax(), position.offset).unwrap();
140 let trait_ = crate::traits::resolve_target_trait(&sema, &impl_block);
141 let actual = match trait_ {
142 Some(trait_) => trait_.name(&db).display(&db).to_string(),
143 None => String::new(),
144 };
145 expect.assert_eq(&actual);
146 }
147
check_missing_assoc(ra_fixture: &str, expect: Expect)148 fn check_missing_assoc(ra_fixture: &str, expect: Expect) {
149 let (db, position) = position(ra_fixture);
150 let sema = Semantics::new(&db);
151 let file = sema.parse(position.file_id);
152 let impl_block: ast::Impl =
153 sema.find_node_at_offset_with_descend(file.syntax(), position.offset).unwrap();
154 let items = crate::traits::get_missing_assoc_items(&sema, &impl_block);
155 let actual = items
156 .into_iter()
157 .map(|item| item.name(&db).unwrap().display(&db).to_string())
158 .collect::<Vec<_>>()
159 .join("\n");
160 expect.assert_eq(&actual);
161 }
162
163 #[test]
resolve_trait()164 fn resolve_trait() {
165 check_trait(
166 r#"
167 pub trait Foo {
168 fn bar();
169 }
170 impl Foo for u8 {
171 $0
172 }
173 "#,
174 expect![["Foo"]],
175 );
176 check_trait(
177 r#"
178 pub trait Foo {
179 fn bar();
180 }
181 impl Foo for u8 {
182 fn bar() {
183 fn baz() {
184 $0
185 }
186 baz();
187 }
188 }
189 "#,
190 expect![["Foo"]],
191 );
192 check_trait(
193 r#"
194 pub trait Foo {
195 fn bar();
196 }
197 pub struct Bar;
198 impl Bar {
199 $0
200 }
201 "#,
202 expect![[""]],
203 );
204 }
205
206 #[test]
missing_assoc_items()207 fn missing_assoc_items() {
208 check_missing_assoc(
209 r#"
210 pub trait Foo {
211 const FOO: u8;
212 fn bar();
213 }
214 impl Foo for u8 {
215 $0
216 }"#,
217 expect![[r#"
218 FOO
219 bar"#]],
220 );
221
222 check_missing_assoc(
223 r#"
224 pub trait Foo {
225 const FOO: u8;
226 fn bar();
227 }
228 impl Foo for u8 {
229 const FOO: u8 = 10;
230 $0
231 }"#,
232 expect![[r#"
233 bar"#]],
234 );
235
236 check_missing_assoc(
237 r#"
238 pub trait Foo {
239 const FOO: u8;
240 fn bar();
241 }
242 impl Foo for u8 {
243 const FOO: u8 = 10;
244 fn bar() {$0}
245 }"#,
246 expect![[r#""#]],
247 );
248
249 check_missing_assoc(
250 r#"
251 pub struct Foo;
252 impl Foo {
253 fn bar() {$0}
254 }"#,
255 expect![[r#""#]],
256 );
257
258 check_missing_assoc(
259 r#"
260 trait Tr {
261 fn required();
262 }
263 macro_rules! m {
264 () => { fn required() {} };
265 }
266 impl Tr for () {
267 m!();
268 $0
269 }
270
271 "#,
272 expect![[r#""#]],
273 );
274 }
275 }
276