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: &dyn HirDatabase, def: Definition) -> Definition {
90    (|| {
91        let assoc = def.as_assoc_item(db)?;
92        let trait_ = assoc.implemented_trait(db)?;
93        assoc_item_of_trait(db, assoc, trait_)
94    })()
95    .unwrap_or(def)
96}
97
98/// If this is an trait (impl) assoc item, returns the assoc item of the corresponding trait definition.
99pub(crate) fn as_trait_assoc_def(db: &dyn HirDatabase, def: Definition) -> Option<Definition> {
100    let assoc = def.as_assoc_item(db)?;
101    let trait_ = match assoc.container(db) {
102        hir::AssocItemContainer::Trait(_) => return Some(def),
103        hir::AssocItemContainer::Impl(i) => i.trait_(db),
104    }?;
105    assoc_item_of_trait(db, assoc, trait_)
106}
107
108fn assoc_item_of_trait(
109    db: &dyn HirDatabase,
110    assoc: hir::AssocItem,
111    trait_: hir::Trait,
112) -> Option<Definition> {
113    use hir::AssocItem::*;
114    let name = match assoc {
115        Function(it) => it.name(db),
116        Const(it) => it.name(db)?,
117        TypeAlias(it) => it.name(db),
118    };
119    let item = trait_.items(db).into_iter().find(|it| match (it, assoc) {
120        (Function(trait_func), Function(_)) => trait_func.name(db) == name,
121        (Const(trait_konst), Const(_)) => trait_konst.name(db).map_or(false, |it| it == name),
122        (TypeAlias(trait_type_alias), TypeAlias(_)) => trait_type_alias.name(db) == name,
123        _ => false,
124    })?;
125    Some(Definition::from(item))
126}
127
128#[cfg(test)]
129mod tests {
130    use expect_test::{Expect, expect};
131    use hir::{EditionedFileId, FilePosition, Semantics};
132    use span::Edition;
133    use syntax::ast::{self, AstNode};
134    use test_fixture::ChangeFixture;
135
136    use crate::RootDatabase;
137
138    /// Creates analysis from a multi-file fixture, returns positions marked with $0.
139    pub(crate) fn position(
140        #[rust_analyzer::rust_fixture] ra_fixture: &str,
141    ) -> (RootDatabase, FilePosition) {
142        let mut database = RootDatabase::default();
143        let change_fixture = ChangeFixture::parse(ra_fixture);
144        database.apply_change(change_fixture.change);
145        let (file_id, range_or_offset) =
146            change_fixture.file_position.expect("expected a marker ($0)");
147
148        let file_id = EditionedFileId::from_span_file_id(&database, file_id);
149        let offset = range_or_offset.expect_offset();
150        (database, FilePosition { file_id, offset })
151    }
152
153    fn check_trait(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
154        let (db, position) = position(ra_fixture);
155        let sema = Semantics::new(&db);
156
157        let file = sema.parse(position.file_id);
158        let impl_block: ast::Impl =
159            sema.find_node_at_offset_with_descend(file.syntax(), position.offset).unwrap();
160        let trait_ = crate::traits::resolve_target_trait(&sema, &impl_block);
161        let actual = match trait_ {
162            Some(trait_) => trait_.name(&db).display(&db, Edition::CURRENT).to_string(),
163            None => String::new(),
164        };
165        expect.assert_eq(&actual);
166    }
167
168    fn check_missing_assoc(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
169        let (db, position) = position(ra_fixture);
170        let sema = Semantics::new(&db);
171
172        let file = sema.parse(position.file_id);
173        let impl_block: ast::Impl =
174            sema.find_node_at_offset_with_descend(file.syntax(), position.offset).unwrap();
175        let items =
176            hir::attach_db(&db, || crate::traits::get_missing_assoc_items(&sema, &impl_block));
177        let actual = items
178            .into_iter()
179            .map(|item| item.name(&db).unwrap().display(&db, Edition::CURRENT).to_string())
180            .collect::<Vec<_>>()
181            .join("\n");
182        expect.assert_eq(&actual);
183    }
184
185    #[test]
186    fn resolve_trait() {
187        check_trait(
188            r#"
189pub trait Foo {
190    fn bar();
191}
192impl Foo for u8 {
193    $0
194}
195            "#,
196            expect![["Foo"]],
197        );
198        check_trait(
199            r#"
200pub trait Foo {
201    fn bar();
202}
203impl Foo for u8 {
204    fn bar() {
205        fn baz() {
206            $0
207        }
208        baz();
209    }
210}
211            "#,
212            expect![["Foo"]],
213        );
214        check_trait(
215            r#"
216pub trait Foo {
217    fn bar();
218}
219pub struct Bar;
220impl Bar {
221    $0
222}
223            "#,
224            expect![[""]],
225        );
226    }
227
228    #[test]
229    fn missing_assoc_items() {
230        check_missing_assoc(
231            r#"
232pub trait Foo {
233    const FOO: u8;
234    fn bar();
235}
236impl Foo for u8 {
237    $0
238}"#,
239            expect![[r#"
240                FOO
241                bar"#]],
242        );
243
244        check_missing_assoc(
245            r#"
246pub trait Foo {
247    const FOO: u8;
248    fn bar();
249}
250impl Foo for u8 {
251    const FOO: u8 = 10;
252    $0
253}"#,
254            expect![[r#"
255                bar"#]],
256        );
257
258        check_missing_assoc(
259            r#"
260pub trait Foo {
261    const FOO: u8;
262    fn bar();
263}
264impl Foo for u8 {
265    const FOO: u8 = 10;
266    fn bar() {$0}
267}"#,
268            expect![[r#""#]],
269        );
270
271        check_missing_assoc(
272            r#"
273pub struct Foo;
274impl Foo {
275    fn bar() {$0}
276}"#,
277            expect![[r#""#]],
278        );
279
280        check_missing_assoc(
281            r#"
282trait Tr {
283    fn required();
284}
285macro_rules! m {
286    () => { fn required() {} };
287}
288impl Tr for () {
289    m!();
290    $0
291}
292
293            "#,
294            expect![[r#""#]],
295        );
296    }
297}