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 hir::{AsAssocItem, HasCrate, Semantics, db::HirDatabase, sym};
5use rustc_hash::FxHashSet;
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/// Given the `impl` block, returns the list of associated items (e.g. functions or types) that are
23/// missing in this `impl` block.
24pub 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    // Names must be unique between constants and functions. However, type aliases
34    // may share the same name as a function or constant.
35    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    let Some(target_trait) = imp.trait_(sema.db) else { return Vec::new() };
55
56    // `Drop` has two methods, `drop()` and `pin_drop()`, and you can only implement one of them, so
57    // we consider `pin_drop()` to not exist, unless you already implement it.
58    let drop_trait = hir::Trait::lang(sema.db, imp.krate(sema.db), hir::LangItem::Drop);
59    if let Some(drop_trait) = drop_trait
60        && target_trait == drop_trait
61    {
62        return if impl_fns_consts.is_empty() {
63            // No method implemented, return `drop()`.
64            let drop_drop = drop_trait.function(sema.db, sym::drop);
65            match drop_drop {
66                Some(drop_drop) => vec![hir::AssocItem::Function(drop_drop)],
67                None => Vec::new(),
68            }
69        } else {
70            // Some method is already implemented, leave it.
71            Vec::new()
72        };
73    }
74
75    target_trait
76        .items(sema.db)
77        .into_iter()
78        .filter(|i| match i {
79            hir::AssocItem::Function(f) => !impl_fns_consts.contains(&f.name(sema.db)),
80            hir::AssocItem::TypeAlias(t) => !impl_type.contains(&t.name(sema.db)),
81            hir::AssocItem::Const(c) => {
82                c.name(sema.db).map(|n| !impl_fns_consts.contains(&n)).unwrap_or_default()
83            }
84        })
85        .collect()
86}
87
88/// Converts associated trait impl items to their trait definition counterpart
89pub(crate) fn convert_to_def_in_trait<'db>(
90    db: &'db dyn HirDatabase,
91    def: Definition<'db>,
92) -> Definition<'db> {
93    (|| {
94        let assoc = def.as_assoc_item(db)?;
95        let trait_ = assoc.implemented_trait(db)?;
96        assoc_item_of_trait(db, assoc, trait_)
97    })()
98    .unwrap_or(def)
99}
100
101/// If this is an trait (impl) assoc item, returns the assoc item of the corresponding trait definition.
102pub(crate) fn as_trait_assoc_def<'db>(
103    db: &dyn HirDatabase,
104    def: Definition<'db>,
105) -> Option<Definition<'db>> {
106    let assoc = def.as_assoc_item(db)?;
107    let trait_ = match assoc.container(db) {
108        hir::AssocItemContainer::Trait(_) => return Some(def),
109        hir::AssocItemContainer::Impl(i) => i.trait_(db),
110    }?;
111    assoc_item_of_trait(db, assoc, trait_)
112}
113
114fn assoc_item_of_trait<'db>(
115    db: &dyn HirDatabase,
116    assoc: hir::AssocItem,
117    trait_: hir::Trait,
118) -> Option<Definition<'db>> {
119    use hir::AssocItem::*;
120    let name = match assoc {
121        Function(it) => it.name(db),
122        Const(it) => it.name(db)?,
123        TypeAlias(it) => it.name(db),
124    };
125    let item = trait_.items(db).into_iter().find(|it| match (it, assoc) {
126        (Function(trait_func), Function(_)) => trait_func.name(db) == name,
127        (Const(trait_konst), Const(_)) => trait_konst.name(db).map_or(false, |it| it == name),
128        (TypeAlias(trait_type_alias), TypeAlias(_)) => trait_type_alias.name(db) == name,
129        _ => false,
130    })?;
131    Some(Definition::from(item))
132}
133
134#[cfg(test)]
135mod tests {
136    use expect_test::{Expect, expect};
137    use hir::{EditionedFileId, FilePosition, Semantics};
138    use span::Edition;
139    use syntax::ast::{self, AstNode};
140    use test_fixture::ChangeFixture;
141
142    use crate::RootDatabase;
143
144    /// Creates analysis from a multi-file fixture, returns positions marked with $0.
145    pub(crate) fn position(
146        #[rust_analyzer::rust_fixture] ra_fixture: &str,
147    ) -> (RootDatabase, FilePosition) {
148        let mut database = RootDatabase::default();
149        let change_fixture = ChangeFixture::parse(ra_fixture);
150        database.apply_change(change_fixture.change);
151        let (file_id, range_or_offset) =
152            change_fixture.file_position.expect("expected a marker ($0)");
153
154        let file_id = EditionedFileId::from_span_file_id(&database, file_id);
155        let offset = range_or_offset.expect_offset();
156        (database, FilePosition { file_id, offset })
157    }
158
159    fn check_trait(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
160        let (db, position) = position(ra_fixture);
161        let sema = Semantics::new(&db);
162
163        let file = sema.parse(position.file_id);
164        let impl_block: ast::Impl =
165            sema.find_node_at_offset_with_descend(file.syntax(), position.offset).unwrap();
166        let trait_ = crate::traits::resolve_target_trait(&sema, &impl_block);
167        let actual = match trait_ {
168            Some(trait_) => trait_.name(&db).display(&db, Edition::CURRENT).to_string(),
169            None => String::new(),
170        };
171        expect.assert_eq(&actual);
172    }
173
174    fn check_missing_assoc(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
175        let (db, position) = position(ra_fixture);
176        let sema = Semantics::new(&db);
177
178        let file = sema.parse(position.file_id);
179        let impl_block: ast::Impl =
180            sema.find_node_at_offset_with_descend(file.syntax(), position.offset).unwrap();
181        let items =
182            hir::attach_db(&db, || crate::traits::get_missing_assoc_items(&sema, &impl_block));
183        let actual = items
184            .into_iter()
185            .map(|item| item.name(&db).unwrap().display(&db, Edition::CURRENT).to_string())
186            .collect::<Vec<_>>()
187            .join("\n");
188        expect.assert_eq(&actual);
189    }
190
191    #[test]
192    fn resolve_trait() {
193        check_trait(
194            r#"
195pub trait Foo {
196    fn bar();
197}
198impl Foo for u8 {
199    $0
200}
201            "#,
202            expect![["Foo"]],
203        );
204        check_trait(
205            r#"
206pub trait Foo {
207    fn bar();
208}
209impl Foo for u8 {
210    fn bar() {
211        fn baz() {
212            $0
213        }
214        baz();
215    }
216}
217            "#,
218            expect![["Foo"]],
219        );
220        check_trait(
221            r#"
222pub trait Foo {
223    fn bar();
224}
225pub struct Bar;
226impl Bar {
227    $0
228}
229            "#,
230            expect![[""]],
231        );
232    }
233
234    #[test]
235    fn missing_assoc_items() {
236        check_missing_assoc(
237            r#"
238pub trait Foo {
239    const FOO: u8;
240    fn bar();
241}
242impl Foo for u8 {
243    $0
244}"#,
245            expect![[r#"
246                FOO
247                bar"#]],
248        );
249
250        check_missing_assoc(
251            r#"
252pub trait Foo {
253    const FOO: u8;
254    fn bar();
255}
256impl Foo for u8 {
257    const FOO: u8 = 10;
258    $0
259}"#,
260            expect![[r#"
261                bar"#]],
262        );
263
264        check_missing_assoc(
265            r#"
266pub trait Foo {
267    const FOO: u8;
268    fn bar();
269}
270impl Foo for u8 {
271    const FOO: u8 = 10;
272    fn bar() {$0}
273}"#,
274            expect![[r#""#]],
275        );
276
277        check_missing_assoc(
278            r#"
279pub struct Foo;
280impl Foo {
281    fn bar() {$0}
282}"#,
283            expect![[r#""#]],
284        );
285
286        check_missing_assoc(
287            r#"
288trait Tr {
289    fn required();
290}
291macro_rules! m {
292    () => { fn required() {} };
293}
294impl Tr for () {
295    m!();
296    $0
297}
298
299            "#,
300            expect![[r#""#]],
301        );
302    }
303}