1use crate::{RootDatabase, defs::Definition};
4use base_db::FxIndexMap;
5use hir::{AsAssocItem, HasAttrs, HasCrate, Semantics, db::HirDatabase, sym};
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
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct IsRequiredAssocItem(pub bool);
24
25#[derive(PartialEq, Eq, Hash)]
28enum AssocItemKind {
29 FnOrConst,
30 Type,
31}
32
33pub fn trait_items_with_required(
34 db: &RootDatabase,
35 trait_: hir::Trait,
36) -> Vec<(hir::AssocItem, IsRequiredAssocItem)> {
37 diff_assoc_items(db, trait_, Vec::new(), trait_.krate(db))
38}
39
40pub fn get_missing_assoc_items(
43 sema: &Semantics<'_, RootDatabase>,
44 impl_def: &ast::Impl,
45) -> Vec<(hir::AssocItem, IsRequiredAssocItem)> {
46 let imp = match sema.to_def(impl_def) {
47 Some(it) => it,
48 None => return vec![],
49 };
50
51 let Some(target_trait) = imp.trait_(sema.db) else { return Vec::new() };
52
53 diff_assoc_items(sema.db, target_trait, imp.items(sema.db), imp.krate(sema.db))
54}
55
56fn diff_assoc_items(
57 db: &RootDatabase,
58 target_trait: hir::Trait,
59 impl_items: Vec<hir::AssocItem>,
60 impl_crate: hir::Crate,
61) -> Vec<(hir::AssocItem, IsRequiredAssocItem)> {
62 let drop_trait = hir::Trait::lang(db, impl_crate, hir::LangItem::Drop);
65 if let Some(drop_trait) = drop_trait
66 && target_trait == drop_trait
67 {
68 return if impl_items.is_empty() {
69 let drop_drop = drop_trait.function(db, sym::drop);
71 match drop_drop {
72 Some(drop_drop) => {
73 vec![(hir::AssocItem::Function(drop_drop), IsRequiredAssocItem(true))]
74 }
75 None => Vec::new(),
76 }
77 } else {
78 Vec::new()
80 };
81 }
82
83 let must_implement_one_of = target_trait.must_implement_one_of(db).unwrap_or_default();
84
85 let mut trait_items = FxIndexMap::default();
87
88 for i in target_trait.items(db) {
89 match i {
90 hir::AssocItem::Function(f) => {
91 let is_required = !f.has_body(db);
92 trait_items.insert(
93 (f.name(db), AssocItemKind::FnOrConst),
94 (i, IsRequiredAssocItem(is_required)),
95 );
96 }
97 hir::AssocItem::Const(c) => {
98 if let Some(name) = c.name(db) {
99 let is_required = !c.has_body(db);
100 trait_items.insert(
101 (name, AssocItemKind::FnOrConst),
102 (i, IsRequiredAssocItem(is_required)),
103 );
104 }
105 }
106 hir::AssocItem::TypeAlias(t) => {
107 let is_required = !t.has_type(db);
108 trait_items.insert(
109 (t.name(db), AssocItemKind::Type),
110 (i, IsRequiredAssocItem(is_required)),
111 );
112 }
113 }
114 }
115
116 let mut abides_must_implement_one_of = must_implement_one_of.is_empty();
117 for item in impl_items {
118 match item {
119 hir::AssocItem::Function(it) => {
120 let name = it.name(db);
121 if !abides_must_implement_one_of && must_implement_one_of.contains(&name) {
122 abides_must_implement_one_of = true;
123 }
124 trait_items.shift_remove(&(name, AssocItemKind::FnOrConst));
125 }
126 hir::AssocItem::Const(it) => {
127 if let Some(name) = it.name(db) {
128 trait_items.shift_remove(&(name, AssocItemKind::FnOrConst));
129 }
130 }
131 hir::AssocItem::TypeAlias(it) => {
132 trait_items.shift_remove(&(it.name(db), AssocItemKind::Type));
133 }
134 }
135 }
136
137 if !abides_must_implement_one_of {
138 for name in must_implement_one_of {
139 let Some((item, is_required)) =
140 trait_items.get_mut(&(name.clone(), AssocItemKind::FnOrConst))
141 else {
142 continue;
143 };
144 if item
145 .attrs(db)
146 .unstable_feature(db)
147 .is_none_or(|feature| impl_crate.is_unstable_feature_enabled(db, &feature))
148 {
149 is_required.0 = true;
153 break;
154 }
155 }
156 }
157
158 trait_items.into_values().collect()
159}
160
161pub(crate) fn convert_to_def_in_trait<'db>(
163 db: &'db dyn HirDatabase,
164 def: Definition<'db>,
165) -> Definition<'db> {
166 (|| {
167 let assoc = def.as_assoc_item(db)?;
168 let trait_ = assoc.implemented_trait(db)?;
169 assoc_item_of_trait(db, assoc, trait_)
170 })()
171 .unwrap_or(def)
172}
173
174pub(crate) fn as_trait_assoc_def<'db>(
176 db: &dyn HirDatabase,
177 def: Definition<'db>,
178) -> Option<Definition<'db>> {
179 let assoc = def.as_assoc_item(db)?;
180 let trait_ = match assoc.container(db) {
181 hir::AssocItemContainer::Trait(_) => return Some(def),
182 hir::AssocItemContainer::Impl(i) => i.trait_(db),
183 }?;
184 assoc_item_of_trait(db, assoc, trait_)
185}
186
187fn assoc_item_of_trait<'db>(
188 db: &dyn HirDatabase,
189 assoc: hir::AssocItem,
190 trait_: hir::Trait,
191) -> Option<Definition<'db>> {
192 use hir::AssocItem::*;
193 let name = match assoc {
194 Function(it) => it.name(db),
195 Const(it) => it.name(db)?,
196 TypeAlias(it) => it.name(db),
197 };
198 let item = trait_.items(db).into_iter().find(|it| match (it, assoc) {
199 (Function(trait_func), Function(_)) => trait_func.name(db) == name,
200 (Const(trait_konst), Const(_)) => trait_konst.name(db).map_or(false, |it| it == name),
201 (TypeAlias(trait_type_alias), TypeAlias(_)) => trait_type_alias.name(db) == name,
202 _ => false,
203 })?;
204 Some(Definition::from(item))
205}
206
207#[cfg(test)]
208mod tests {
209 use expect_test::{Expect, expect};
210 use hir::{EditionedFileId, FilePosition, Semantics};
211 use span::Edition;
212 use syntax::ast::{self, AstNode};
213 use test_fixture::ChangeFixture;
214
215 use crate::RootDatabase;
216
217 pub(crate) fn position(
219 #[rust_analyzer::rust_fixture] ra_fixture: &str,
220 ) -> (RootDatabase, FilePosition) {
221 let mut database = RootDatabase::default();
222 let change_fixture = ChangeFixture::parse(ra_fixture);
223 database.apply_change(change_fixture.change);
224 let (file_id, range_or_offset) =
225 change_fixture.file_position.expect("expected a marker ($0)");
226
227 let file_id = EditionedFileId::from_span_file_id(&database, file_id);
228 let offset = range_or_offset.expect_offset();
229 (database, FilePosition { file_id, offset })
230 }
231
232 fn check_trait(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
233 let (db, position) = position(ra_fixture);
234 let sema = Semantics::new(&db);
235
236 let file = sema.parse(position.file_id);
237 let impl_block: ast::Impl =
238 sema.find_node_at_offset_with_descend(file.syntax(), position.offset).unwrap();
239 let trait_ = crate::traits::resolve_target_trait(&sema, &impl_block);
240 let actual = match trait_ {
241 Some(trait_) => trait_.name(&db).display(&db, Edition::CURRENT).to_string(),
242 None => String::new(),
243 };
244 expect.assert_eq(&actual);
245 }
246
247 fn check_missing_assoc(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
248 let (db, position) = position(ra_fixture);
249 let sema = Semantics::new(&db);
250
251 let file = sema.parse(position.file_id);
252 let impl_block: ast::Impl =
253 sema.find_node_at_offset_with_descend(file.syntax(), position.offset).unwrap();
254 let items =
255 hir::attach_db(&db, || crate::traits::get_missing_assoc_items(&sema, &impl_block));
256 let actual = items
257 .into_iter()
258 .map(|(item, _)| item.name(&db).unwrap().display(&db, Edition::CURRENT).to_string())
259 .collect::<Vec<_>>()
260 .join("\n");
261 expect.assert_eq(&actual);
262 }
263
264 #[test]
265 fn resolve_trait() {
266 check_trait(
267 r#"
268pub trait Foo {
269 fn bar();
270}
271impl Foo for u8 {
272 $0
273}
274 "#,
275 expect![["Foo"]],
276 );
277 check_trait(
278 r#"
279pub trait Foo {
280 fn bar();
281}
282impl Foo for u8 {
283 fn bar() {
284 fn baz() {
285 $0
286 }
287 baz();
288 }
289}
290 "#,
291 expect![["Foo"]],
292 );
293 check_trait(
294 r#"
295pub trait Foo {
296 fn bar();
297}
298pub struct Bar;
299impl Bar {
300 $0
301}
302 "#,
303 expect![[""]],
304 );
305 }
306
307 #[test]
308 fn missing_assoc_items() {
309 check_missing_assoc(
310 r#"
311pub trait Foo {
312 const FOO: u8;
313 fn bar();
314}
315impl Foo for u8 {
316 $0
317}"#,
318 expect![[r#"
319 FOO
320 bar"#]],
321 );
322
323 check_missing_assoc(
324 r#"
325pub trait Foo {
326 const FOO: u8;
327 fn bar();
328}
329impl Foo for u8 {
330 const FOO: u8 = 10;
331 $0
332}"#,
333 expect![[r#"
334 bar"#]],
335 );
336
337 check_missing_assoc(
338 r#"
339pub trait Foo {
340 const FOO: u8;
341 fn bar();
342}
343impl Foo for u8 {
344 const FOO: u8 = 10;
345 fn bar() {$0}
346}"#,
347 expect![[r#""#]],
348 );
349
350 check_missing_assoc(
351 r#"
352pub struct Foo;
353impl Foo {
354 fn bar() {$0}
355}"#,
356 expect![[r#""#]],
357 );
358
359 check_missing_assoc(
360 r#"
361trait Tr {
362 fn required();
363}
364macro_rules! m {
365 () => { fn required() {} };
366}
367impl Tr for () {
368 m!();
369 $0
370}
371
372 "#,
373 expect![[r#""#]],
374 );
375 }
376}