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