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