Skip to main content

ide_db/
traits.rs

1//! Functionality for obtaining data related to traits from the DB.
2
3use crate::{RootDatabase, defs::Definition};
4use base_db::FxIndexMap;
5use hir::{AsAssocItem, HasAttrs, HasCrate, Semantics, db::HirDatabase, sym};
6use syntax::{AstNode, ast};
7
8/// Given the `impl` block, attempts to find the trait this `impl` corresponds to.
9pub 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/// Names must be unique between constants and functions. However, type aliases
26/// may share the same name as a function or constant.
27#[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
40/// Given the `impl` block, returns the list of associated items (e.g. functions or types) that are
41/// missing in this `impl` block.
42pub 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    // `Drop` has two methods, `drop()` and `pin_drop()`, and you can only implement one of them, so
63    // we consider `pin_drop()` to not exist, unless you already implement it.
64    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            // No method implemented, return `drop()`.
70            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            // Some method is already implemented, leave it.
79            Vec::new()
80        };
81    }
82
83    let must_implement_one_of = target_trait.must_implement_one_of(db).unwrap_or_default();
84
85    // We keep one map because we want to keep the trait's order.
86    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                // `#[rustc_must_implement_one_of]` always has all its methods with default body.
150                // If it isn't followed, mark one as required.
151                // We mark the first, see https://github.com/rust-lang/rust/pull/106643#issuecomment-5187934543.
152                is_required.0 = true;
153                break;
154            }
155        }
156    }
157
158    trait_items.into_values().collect()
159}
160
161/// Converts associated trait impl items to their trait definition counterpart
162pub(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
174/// If this is an trait (impl) assoc item, returns the assoc item of the corresponding trait definition.
175pub(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    /// Creates analysis from a multi-file fixture, returns positions marked with $0.
218    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}