274 lines
7.4 KiB
Rust
274 lines
7.4 KiB
Rust
//! Functionality for obtaining data related to traits from the DB.
|
|
|
|
use crate::{defs::Definition, RootDatabase};
|
|
use hir::{db::HirDatabase, AsAssocItem, Semantics};
|
|
use rustc_hash::FxHashSet;
|
|
use syntax::{ast, AstNode};
|
|
|
|
/// Given the `impl` block, attempts to find the trait this `impl` corresponds to.
|
|
pub fn resolve_target_trait(
|
|
sema: &Semantics<'_, RootDatabase>,
|
|
impl_def: &ast::Impl,
|
|
) -> Option<hir::Trait> {
|
|
let ast_path =
|
|
impl_def.trait_().map(|it| it.syntax().clone()).and_then(ast::PathType::cast)?.path()?;
|
|
|
|
match sema.resolve_path(&ast_path) {
|
|
Some(hir::PathResolution::Def(hir::ModuleDef::Trait(def))) => Some(def),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// Given the `impl` block, returns the list of associated items (e.g. functions or types) that are
|
|
/// missing in this `impl` block.
|
|
pub fn get_missing_assoc_items(
|
|
sema: &Semantics<'_, RootDatabase>,
|
|
impl_def: &ast::Impl,
|
|
) -> Vec<hir::AssocItem> {
|
|
let imp = match sema.to_def(impl_def) {
|
|
Some(it) => it,
|
|
None => return vec![],
|
|
};
|
|
|
|
// Names must be unique between constants and functions. However, type aliases
|
|
// may share the same name as a function or constant.
|
|
let mut impl_fns_consts = FxHashSet::default();
|
|
let mut impl_type = FxHashSet::default();
|
|
|
|
for item in imp.items(sema.db) {
|
|
match item {
|
|
hir::AssocItem::Function(it) => {
|
|
impl_fns_consts.insert(it.name(sema.db).to_string());
|
|
}
|
|
hir::AssocItem::Const(it) => {
|
|
if let Some(name) = it.name(sema.db) {
|
|
impl_fns_consts.insert(name.to_string());
|
|
}
|
|
}
|
|
hir::AssocItem::TypeAlias(it) => {
|
|
impl_type.insert(it.name(sema.db).to_string());
|
|
}
|
|
}
|
|
}
|
|
|
|
resolve_target_trait(sema, impl_def).map_or(vec![], |target_trait| {
|
|
target_trait
|
|
.items(sema.db)
|
|
.into_iter()
|
|
.filter(|i| match i {
|
|
hir::AssocItem::Function(f) => {
|
|
!impl_fns_consts.contains(&f.name(sema.db).to_string())
|
|
}
|
|
hir::AssocItem::TypeAlias(t) => !impl_type.contains(&t.name(sema.db).to_string()),
|
|
hir::AssocItem::Const(c) => c
|
|
.name(sema.db)
|
|
.map(|n| !impl_fns_consts.contains(&n.to_string()))
|
|
.unwrap_or_default(),
|
|
})
|
|
.collect()
|
|
})
|
|
}
|
|
|
|
/// Converts associated trait impl items to their trait definition counterpart
|
|
pub(crate) fn convert_to_def_in_trait(db: &dyn HirDatabase, def: Definition) -> Definition {
|
|
(|| {
|
|
let assoc = def.as_assoc_item(db)?;
|
|
let trait_ = assoc.containing_trait_impl(db)?;
|
|
assoc_item_of_trait(db, assoc, trait_)
|
|
})()
|
|
.unwrap_or(def)
|
|
}
|
|
|
|
/// If this is an trait (impl) assoc item, returns the assoc item of the corresponding trait definition.
|
|
pub(crate) fn as_trait_assoc_def(db: &dyn HirDatabase, def: Definition) -> Option<Definition> {
|
|
let assoc = def.as_assoc_item(db)?;
|
|
let trait_ = match assoc.container(db) {
|
|
hir::AssocItemContainer::Trait(_) => return Some(def),
|
|
hir::AssocItemContainer::Impl(i) => i.trait_(db),
|
|
}?;
|
|
assoc_item_of_trait(db, assoc, trait_)
|
|
}
|
|
|
|
fn assoc_item_of_trait(
|
|
db: &dyn HirDatabase,
|
|
assoc: hir::AssocItem,
|
|
trait_: hir::Trait,
|
|
) -> Option<Definition> {
|
|
use hir::AssocItem::*;
|
|
let name = match assoc {
|
|
Function(it) => it.name(db),
|
|
Const(it) => it.name(db)?,
|
|
TypeAlias(it) => it.name(db),
|
|
};
|
|
let item = trait_.items(db).into_iter().find(|it| match (it, assoc) {
|
|
(Function(trait_func), Function(_)) => trait_func.name(db) == name,
|
|
(Const(trait_konst), Const(_)) => trait_konst.name(db).map_or(false, |it| it == name),
|
|
(TypeAlias(trait_type_alias), TypeAlias(_)) => trait_type_alias.name(db) == name,
|
|
_ => false,
|
|
})?;
|
|
Some(Definition::from(item))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use base_db::{fixture::ChangeFixture, FilePosition};
|
|
use expect_test::{expect, Expect};
|
|
use hir::Semantics;
|
|
use syntax::ast::{self, AstNode};
|
|
|
|
use crate::RootDatabase;
|
|
|
|
/// Creates analysis from a multi-file fixture, returns positions marked with $0.
|
|
pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
|
|
let change_fixture = ChangeFixture::parse(ra_fixture);
|
|
let mut database = RootDatabase::default();
|
|
database.apply_change(change_fixture.change);
|
|
let (file_id, range_or_offset) =
|
|
change_fixture.file_position.expect("expected a marker ($0)");
|
|
let offset = range_or_offset.expect_offset();
|
|
(database, FilePosition { file_id, offset })
|
|
}
|
|
|
|
fn check_trait(ra_fixture: &str, expect: Expect) {
|
|
let (db, position) = position(ra_fixture);
|
|
let sema = Semantics::new(&db);
|
|
let file = sema.parse(position.file_id);
|
|
let impl_block: ast::Impl =
|
|
sema.find_node_at_offset_with_descend(file.syntax(), position.offset).unwrap();
|
|
let trait_ = crate::traits::resolve_target_trait(&sema, &impl_block);
|
|
let actual = match trait_ {
|
|
Some(trait_) => trait_.name(&db).to_string(),
|
|
None => String::new(),
|
|
};
|
|
expect.assert_eq(&actual);
|
|
}
|
|
|
|
fn check_missing_assoc(ra_fixture: &str, expect: Expect) {
|
|
let (db, position) = position(ra_fixture);
|
|
let sema = Semantics::new(&db);
|
|
let file = sema.parse(position.file_id);
|
|
let impl_block: ast::Impl =
|
|
sema.find_node_at_offset_with_descend(file.syntax(), position.offset).unwrap();
|
|
let items = crate::traits::get_missing_assoc_items(&sema, &impl_block);
|
|
let actual = items
|
|
.into_iter()
|
|
.map(|item| item.name(&db).unwrap().to_string())
|
|
.collect::<Vec<_>>()
|
|
.join("\n");
|
|
expect.assert_eq(&actual);
|
|
}
|
|
|
|
#[test]
|
|
fn resolve_trait() {
|
|
check_trait(
|
|
r#"
|
|
pub trait Foo {
|
|
fn bar();
|
|
}
|
|
impl Foo for u8 {
|
|
$0
|
|
}
|
|
"#,
|
|
expect![["Foo"]],
|
|
);
|
|
check_trait(
|
|
r#"
|
|
pub trait Foo {
|
|
fn bar();
|
|
}
|
|
impl Foo for u8 {
|
|
fn bar() {
|
|
fn baz() {
|
|
$0
|
|
}
|
|
baz();
|
|
}
|
|
}
|
|
"#,
|
|
expect![["Foo"]],
|
|
);
|
|
check_trait(
|
|
r#"
|
|
pub trait Foo {
|
|
fn bar();
|
|
}
|
|
pub struct Bar;
|
|
impl Bar {
|
|
$0
|
|
}
|
|
"#,
|
|
expect![[""]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn missing_assoc_items() {
|
|
check_missing_assoc(
|
|
r#"
|
|
pub trait Foo {
|
|
const FOO: u8;
|
|
fn bar();
|
|
}
|
|
impl Foo for u8 {
|
|
$0
|
|
}"#,
|
|
expect![[r#"
|
|
FOO
|
|
bar"#]],
|
|
);
|
|
|
|
check_missing_assoc(
|
|
r#"
|
|
pub trait Foo {
|
|
const FOO: u8;
|
|
fn bar();
|
|
}
|
|
impl Foo for u8 {
|
|
const FOO: u8 = 10;
|
|
$0
|
|
}"#,
|
|
expect![[r#"
|
|
bar"#]],
|
|
);
|
|
|
|
check_missing_assoc(
|
|
r#"
|
|
pub trait Foo {
|
|
const FOO: u8;
|
|
fn bar();
|
|
}
|
|
impl Foo for u8 {
|
|
const FOO: u8 = 10;
|
|
fn bar() {$0}
|
|
}"#,
|
|
expect![[r#""#]],
|
|
);
|
|
|
|
check_missing_assoc(
|
|
r#"
|
|
pub struct Foo;
|
|
impl Foo {
|
|
fn bar() {$0}
|
|
}"#,
|
|
expect![[r#""#]],
|
|
);
|
|
|
|
check_missing_assoc(
|
|
r#"
|
|
trait Tr {
|
|
fn required();
|
|
}
|
|
macro_rules! m {
|
|
() => { fn required() {} };
|
|
}
|
|
impl Tr for () {
|
|
m!();
|
|
$0
|
|
}
|
|
|
|
"#,
|
|
expect![[r#""#]],
|
|
);
|
|
}
|
|
}
|