diff options
Diffstat (limited to 'crates/ide_db/src/traits.rs')
-rw-r--r-- | crates/ide_db/src/traits.rs | 227 |
1 files changed, 227 insertions, 0 deletions
diff --git a/crates/ide_db/src/traits.rs b/crates/ide_db/src/traits.rs new file mode 100644 index 000000000..f57b6dd91 --- /dev/null +++ b/crates/ide_db/src/traits.rs | |||
@@ -0,0 +1,227 @@ | |||
1 | //! Functionality for obtaining data related to traits from the DB. | ||
2 | |||
3 | use crate::RootDatabase; | ||
4 | use hir::Semantics; | ||
5 | use rustc_hash::FxHashSet; | ||
6 | use syntax::{ | ||
7 | ast::{self, NameOwner}, | ||
8 | AstNode, | ||
9 | }; | ||
10 | |||
11 | /// Given the `impl` block, attempts to find the trait this `impl` corresponds to. | ||
12 | pub fn resolve_target_trait( | ||
13 | sema: &Semantics<RootDatabase>, | ||
14 | impl_def: &ast::Impl, | ||
15 | ) -> Option<hir::Trait> { | ||
16 | let ast_path = | ||
17 | impl_def.trait_().map(|it| it.syntax().clone()).and_then(ast::PathType::cast)?.path()?; | ||
18 | |||
19 | match sema.resolve_path(&ast_path) { | ||
20 | Some(hir::PathResolution::Def(hir::ModuleDef::Trait(def))) => Some(def), | ||
21 | _ => None, | ||
22 | } | ||
23 | } | ||
24 | |||
25 | /// Given the `impl` block, returns the list of associated items (e.g. functions or types) that are | ||
26 | /// missing in this `impl` block. | ||
27 | pub fn get_missing_assoc_items( | ||
28 | sema: &Semantics<RootDatabase>, | ||
29 | impl_def: &ast::Impl, | ||
30 | ) -> Vec<hir::AssocItem> { | ||
31 | // Names must be unique between constants and functions. However, type aliases | ||
32 | // may share the same name as a function or constant. | ||
33 | let mut impl_fns_consts = FxHashSet::default(); | ||
34 | let mut impl_type = FxHashSet::default(); | ||
35 | |||
36 | if let Some(item_list) = impl_def.assoc_item_list() { | ||
37 | for item in item_list.assoc_items() { | ||
38 | match item { | ||
39 | ast::AssocItem::Fn(f) => { | ||
40 | if let Some(n) = f.name() { | ||
41 | impl_fns_consts.insert(n.syntax().to_string()); | ||
42 | } | ||
43 | } | ||
44 | |||
45 | ast::AssocItem::TypeAlias(t) => { | ||
46 | if let Some(n) = t.name() { | ||
47 | impl_type.insert(n.syntax().to_string()); | ||
48 | } | ||
49 | } | ||
50 | |||
51 | ast::AssocItem::Const(c) => { | ||
52 | if let Some(n) = c.name() { | ||
53 | impl_fns_consts.insert(n.syntax().to_string()); | ||
54 | } | ||
55 | } | ||
56 | ast::AssocItem::MacroCall(_) => (), | ||
57 | } | ||
58 | } | ||
59 | } | ||
60 | |||
61 | resolve_target_trait(sema, impl_def).map_or(vec![], |target_trait| { | ||
62 | target_trait | ||
63 | .items(sema.db) | ||
64 | .iter() | ||
65 | .filter(|i| match i { | ||
66 | hir::AssocItem::Function(f) => { | ||
67 | !impl_fns_consts.contains(&f.name(sema.db).to_string()) | ||
68 | } | ||
69 | hir::AssocItem::TypeAlias(t) => !impl_type.contains(&t.name(sema.db).to_string()), | ||
70 | hir::AssocItem::Const(c) => c | ||
71 | .name(sema.db) | ||
72 | .map(|n| !impl_fns_consts.contains(&n.to_string())) | ||
73 | .unwrap_or_default(), | ||
74 | }) | ||
75 | .cloned() | ||
76 | .collect() | ||
77 | }) | ||
78 | } | ||
79 | |||
80 | #[cfg(test)] | ||
81 | mod tests { | ||
82 | use crate::RootDatabase; | ||
83 | use base_db::{fixture::ChangeFixture, FilePosition}; | ||
84 | use expect_test::{expect, Expect}; | ||
85 | use hir::Semantics; | ||
86 | use syntax::ast::{self, AstNode}; | ||
87 | use test_utils::RangeOrOffset; | ||
88 | |||
89 | /// Creates analysis from a multi-file fixture, returns positions marked with <|>. | ||
90 | pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) { | ||
91 | let change_fixture = ChangeFixture::parse(ra_fixture); | ||
92 | let mut database = RootDatabase::default(); | ||
93 | database.apply_change(change_fixture.change); | ||
94 | let (file_id, range_or_offset) = | ||
95 | change_fixture.file_position.expect("expected a marker (<|>)"); | ||
96 | let offset = match range_or_offset { | ||
97 | RangeOrOffset::Range(_) => panic!(), | ||
98 | RangeOrOffset::Offset(it) => it, | ||
99 | }; | ||
100 | (database, FilePosition { file_id, offset }) | ||
101 | } | ||
102 | |||
103 | fn check_trait(ra_fixture: &str, expect: Expect) { | ||
104 | let (db, position) = position(ra_fixture); | ||
105 | let sema = Semantics::new(&db); | ||
106 | let file = sema.parse(position.file_id); | ||
107 | let impl_block: ast::Impl = | ||
108 | sema.find_node_at_offset_with_descend(file.syntax(), position.offset).unwrap(); | ||
109 | let trait_ = crate::traits::resolve_target_trait(&sema, &impl_block); | ||
110 | let actual = match trait_ { | ||
111 | Some(trait_) => trait_.name(&db).to_string(), | ||
112 | None => String::new(), | ||
113 | }; | ||
114 | expect.assert_eq(&actual); | ||
115 | } | ||
116 | |||
117 | fn check_missing_assoc(ra_fixture: &str, expect: Expect) { | ||
118 | let (db, position) = position(ra_fixture); | ||
119 | let sema = Semantics::new(&db); | ||
120 | let file = sema.parse(position.file_id); | ||
121 | let impl_block: ast::Impl = | ||
122 | sema.find_node_at_offset_with_descend(file.syntax(), position.offset).unwrap(); | ||
123 | let items = crate::traits::get_missing_assoc_items(&sema, &impl_block); | ||
124 | let actual = items | ||
125 | .into_iter() | ||
126 | .map(|item| item.name(&db).unwrap().to_string()) | ||
127 | .collect::<Vec<_>>() | ||
128 | .join("\n"); | ||
129 | expect.assert_eq(&actual); | ||
130 | } | ||
131 | |||
132 | #[test] | ||
133 | fn resolve_trait() { | ||
134 | check_trait( | ||
135 | r#" | ||
136 | pub trait Foo { | ||
137 | fn bar(); | ||
138 | } | ||
139 | impl Foo for u8 { | ||
140 | <|> | ||
141 | } | ||
142 | "#, | ||
143 | expect![["Foo"]], | ||
144 | ); | ||
145 | check_trait( | ||
146 | r#" | ||
147 | pub trait Foo { | ||
148 | fn bar(); | ||
149 | } | ||
150 | impl Foo for u8 { | ||
151 | fn bar() { | ||
152 | fn baz() { | ||
153 | <|> | ||
154 | } | ||
155 | baz(); | ||
156 | } | ||
157 | } | ||
158 | "#, | ||
159 | expect![["Foo"]], | ||
160 | ); | ||
161 | check_trait( | ||
162 | r#" | ||
163 | pub trait Foo { | ||
164 | fn bar(); | ||
165 | } | ||
166 | pub struct Bar; | ||
167 | impl Bar { | ||
168 | <|> | ||
169 | } | ||
170 | "#, | ||
171 | expect![[""]], | ||
172 | ); | ||
173 | } | ||
174 | |||
175 | #[test] | ||
176 | fn missing_assoc_items() { | ||
177 | check_missing_assoc( | ||
178 | r#" | ||
179 | pub trait Foo { | ||
180 | const FOO: u8; | ||
181 | fn bar(); | ||
182 | } | ||
183 | impl Foo for u8 { | ||
184 | <|> | ||
185 | }"#, | ||
186 | expect![[r#" | ||
187 | FOO | ||
188 | bar"#]], | ||
189 | ); | ||
190 | |||
191 | check_missing_assoc( | ||
192 | r#" | ||
193 | pub trait Foo { | ||
194 | const FOO: u8; | ||
195 | fn bar(); | ||
196 | } | ||
197 | impl Foo for u8 { | ||
198 | const FOO: u8 = 10; | ||
199 | <|> | ||
200 | }"#, | ||
201 | expect![[r#" | ||
202 | bar"#]], | ||
203 | ); | ||
204 | |||
205 | check_missing_assoc( | ||
206 | r#" | ||
207 | pub trait Foo { | ||
208 | const FOO: u8; | ||
209 | fn bar(); | ||
210 | } | ||
211 | impl Foo for u8 { | ||
212 | const FOO: u8 = 10; | ||
213 | fn bar() {<|>} | ||
214 | }"#, | ||
215 | expect![[r#""#]], | ||
216 | ); | ||
217 | |||
218 | check_missing_assoc( | ||
219 | r#" | ||
220 | pub struct Foo; | ||
221 | impl Foo { | ||
222 | fn bar() {<|>} | ||
223 | }"#, | ||
224 | expect![[r#""#]], | ||
225 | ); | ||
226 | } | ||
227 | } | ||