ide_db/
traits.rs

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