• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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