aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_db/src/traits.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_db/src/traits.rs')
-rw-r--r--crates/ide_db/src/traits.rs227
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
3use crate::RootDatabase;
4use hir::Semantics;
5use rustc_hash::FxHashSet;
6use syntax::{
7 ast::{self, NameOwner},
8 AstNode,
9};
10
11/// Given the `impl` block, attempts to find the trait this `impl` corresponds to.
12pub 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.
27pub 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)]
81mod 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#"
136pub trait Foo {
137 fn bar();
138}
139impl Foo for u8 {
140 <|>
141}
142 "#,
143 expect![["Foo"]],
144 );
145 check_trait(
146 r#"
147pub trait Foo {
148 fn bar();
149}
150impl 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#"
163pub trait Foo {
164 fn bar();
165}
166pub struct Bar;
167impl Bar {
168 <|>
169}
170 "#,
171 expect![[""]],
172 );
173 }
174
175 #[test]
176 fn missing_assoc_items() {
177 check_missing_assoc(
178 r#"
179pub trait Foo {
180 const FOO: u8;
181 fn bar();
182}
183impl Foo for u8 {
184 <|>
185}"#,
186 expect![[r#"
187 FOO
188 bar"#]],
189 );
190
191 check_missing_assoc(
192 r#"
193pub trait Foo {
194 const FOO: u8;
195 fn bar();
196}
197impl Foo for u8 {
198 const FOO: u8 = 10;
199 <|>
200}"#,
201 expect![[r#"
202 bar"#]],
203 );
204
205 check_missing_assoc(
206 r#"
207pub trait Foo {
208 const FOO: u8;
209 fn bar();
210}
211impl Foo for u8 {
212 const FOO: u8 = 10;
213 fn bar() {<|>}
214}"#,
215 expect![[r#""#]],
216 );
217
218 check_missing_assoc(
219 r#"
220pub struct Foo;
221impl Foo {
222 fn bar() {<|>}
223}"#,
224 expect![[r#""#]],
225 );
226 }
227}