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().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::FilePosition;
118    use hir::Semantics;
119    use span::Edition;
120    use syntax::ast::{self, AstNode};
121    use test_fixture::ChangeFixture;
122
123    use crate::RootDatabase;
124
125    /// Creates analysis from a multi-file fixture, returns positions marked with $0.
126    pub(crate) fn position(
127        #[rust_analyzer::rust_fixture] ra_fixture: &str,
128    ) -> (RootDatabase, FilePosition) {
129        let mut database = RootDatabase::default();
130        let change_fixture = ChangeFixture::parse(&database, ra_fixture);
131        database.apply_change(change_fixture.change);
132        let (file_id, range_or_offset) =
133            change_fixture.file_position.expect("expected a marker ($0)");
134        let offset = range_or_offset.expect_offset();
135        (database, FilePosition { file_id, offset })
136    }
137
138    fn check_trait(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
139        let (db, position) = position(ra_fixture);
140        let sema = Semantics::new(&db);
141
142        let file = sema.parse(position.file_id);
143        let impl_block: ast::Impl =
144            sema.find_node_at_offset_with_descend(file.syntax(), position.offset).unwrap();
145        let trait_ = crate::traits::resolve_target_trait(&sema, &impl_block);
146        let actual = match trait_ {
147            Some(trait_) => trait_.name(&db).display(&db, Edition::CURRENT).to_string(),
148            None => String::new(),
149        };
150        expect.assert_eq(&actual);
151    }
152
153    fn check_missing_assoc(#[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 items = crate::traits::get_missing_assoc_items(&sema, &impl_block);
161        let actual = items
162            .into_iter()
163            .map(|item| item.name(&db).unwrap().display(&db, Edition::CURRENT).to_string())
164            .collect::<Vec<_>>()
165            .join("\n");
166        expect.assert_eq(&actual);
167    }
168
169    #[test]
170    fn resolve_trait() {
171        check_trait(
172            r#"
173pub trait Foo {
174    fn bar();
175}
176impl Foo for u8 {
177    $0
178}
179            "#,
180            expect![["Foo"]],
181        );
182        check_trait(
183            r#"
184pub trait Foo {
185    fn bar();
186}
187impl Foo for u8 {
188    fn bar() {
189        fn baz() {
190            $0
191        }
192        baz();
193    }
194}
195            "#,
196            expect![["Foo"]],
197        );
198        check_trait(
199            r#"
200pub trait Foo {
201    fn bar();
202}
203pub struct Bar;
204impl Bar {
205    $0
206}
207            "#,
208            expect![[""]],
209        );
210    }
211
212    #[test]
213    fn missing_assoc_items() {
214        check_missing_assoc(
215            r#"
216pub trait Foo {
217    const FOO: u8;
218    fn bar();
219}
220impl Foo for u8 {
221    $0
222}"#,
223            expect![[r#"
224                FOO
225                bar"#]],
226        );
227
228        check_missing_assoc(
229            r#"
230pub trait Foo {
231    const FOO: u8;
232    fn bar();
233}
234impl Foo for u8 {
235    const FOO: u8 = 10;
236    $0
237}"#,
238            expect![[r#"
239                bar"#]],
240        );
241
242        check_missing_assoc(
243            r#"
244pub trait Foo {
245    const FOO: u8;
246    fn bar();
247}
248impl Foo for u8 {
249    const FOO: u8 = 10;
250    fn bar() {$0}
251}"#,
252            expect![[r#""#]],
253        );
254
255        check_missing_assoc(
256            r#"
257pub struct Foo;
258impl Foo {
259    fn bar() {$0}
260}"#,
261            expect![[r#""#]],
262        );
263
264        check_missing_assoc(
265            r#"
266trait Tr {
267    fn required();
268}
269macro_rules! m {
270    () => { fn required() {} };
271}
272impl Tr for () {
273    m!();
274    $0
275}
276
277            "#,
278            expect![[r#""#]],
279        );
280    }
281}