blob: 8f9ff6b10b3fe1fd5c371496724493498537a6e1 [file]
//! Functionality for obtaining data related to traits from the DB.
use crate::{RootDatabase, defs::Definition};
use base_db::FxIndexMap;
use hir::{AsAssocItem, HasAttrs, HasCrate, Semantics, db::HirDatabase, sym};
use syntax::{AstNode, ast};
/// 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,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IsRequiredAssocItem(pub bool);
/// Names must be unique between constants and functions. However, type aliases
/// may share the same name as a function or constant.
#[derive(PartialEq, Eq, Hash)]
enum AssocItemKind {
FnOrConst,
Type,
}
pub fn trait_items_with_required(
db: &RootDatabase,
trait_: hir::Trait,
) -> Vec<(hir::AssocItem, IsRequiredAssocItem)> {
diff_assoc_items(db, trait_, Vec::new(), trait_.krate(db))
}
/// 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, IsRequiredAssocItem)> {
let imp = match sema.to_def(impl_def) {
Some(it) => it,
None => return vec![],
};
let Some(target_trait) = imp.trait_(sema.db) else { return Vec::new() };
diff_assoc_items(sema.db, target_trait, imp.items(sema.db), imp.krate(sema.db))
}
fn diff_assoc_items(
db: &RootDatabase,
target_trait: hir::Trait,
impl_items: Vec<hir::AssocItem>,
impl_crate: hir::Crate,
) -> Vec<(hir::AssocItem, IsRequiredAssocItem)> {
// `Drop` has two methods, `drop()` and `pin_drop()`, and you can only implement one of them, so
// we consider `pin_drop()` to not exist, unless you already implement it.
let drop_trait = hir::Trait::lang(db, impl_crate, hir::LangItem::Drop);
if let Some(drop_trait) = drop_trait
&& target_trait == drop_trait
{
return if impl_items.is_empty() {
// No method implemented, return `drop()`.
let drop_drop = drop_trait.function(db, sym::drop);
match drop_drop {
Some(drop_drop) => {
vec![(hir::AssocItem::Function(drop_drop), IsRequiredAssocItem(true))]
}
None => Vec::new(),
}
} else {
// Some method is already implemented, leave it.
Vec::new()
};
}
let must_implement_one_of = target_trait.must_implement_one_of(db).unwrap_or_default();
// We keep one map because we want to keep the trait's order.
let mut trait_items = FxIndexMap::default();
for i in target_trait.items(db) {
match i {
hir::AssocItem::Function(f) => {
let is_required = !f.has_body(db);
trait_items.insert(
(f.name(db), AssocItemKind::FnOrConst),
(i, IsRequiredAssocItem(is_required)),
);
}
hir::AssocItem::Const(c) => {
if let Some(name) = c.name(db) {
let is_required = !c.has_body(db);
trait_items.insert(
(name, AssocItemKind::FnOrConst),
(i, IsRequiredAssocItem(is_required)),
);
}
}
hir::AssocItem::TypeAlias(t) => {
let is_required = !t.has_type(db);
trait_items.insert(
(t.name(db), AssocItemKind::Type),
(i, IsRequiredAssocItem(is_required)),
);
}
}
}
let mut abides_must_implement_one_of = must_implement_one_of.is_empty();
for item in impl_items {
match item {
hir::AssocItem::Function(it) => {
let name = it.name(db);
if !abides_must_implement_one_of && must_implement_one_of.contains(&name) {
abides_must_implement_one_of = true;
}
trait_items.shift_remove(&(name, AssocItemKind::FnOrConst));
}
hir::AssocItem::Const(it) => {
if let Some(name) = it.name(db) {
trait_items.shift_remove(&(name, AssocItemKind::FnOrConst));
}
}
hir::AssocItem::TypeAlias(it) => {
trait_items.shift_remove(&(it.name(db), AssocItemKind::Type));
}
}
}
if !abides_must_implement_one_of {
for name in must_implement_one_of {
let Some((item, is_required)) =
trait_items.get_mut(&(name.clone(), AssocItemKind::FnOrConst))
else {
continue;
};
if item
.attrs(db)
.unstable_feature(db)
.is_none_or(|feature| impl_crate.is_unstable_feature_enabled(db, &feature))
{
// `#[rustc_must_implement_one_of]` always has all its methods with default body.
// If it isn't followed, mark one as required.
// We mark the first, see https://github.com/rust-lang/rust/pull/106643#issuecomment-5187934543.
is_required.0 = true;
break;
}
}
}
trait_items.into_values().collect()
}
/// Converts associated trait impl items to their trait definition counterpart
pub(crate) fn convert_to_def_in_trait<'db>(
db: &'db dyn HirDatabase,
def: Definition<'db>,
) -> Definition<'db> {
(|| {
let assoc = def.as_assoc_item(db)?;
let trait_ = assoc.implemented_trait(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>(
db: &dyn HirDatabase,
def: Definition<'db>,
) -> Option<Definition<'db>> {
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>(
db: &dyn HirDatabase,
assoc: hir::AssocItem,
trait_: hir::Trait,
) -> Option<Definition<'db>> {
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 expect_test::{Expect, expect};
use hir::{EditionedFileId, FilePosition, Semantics};
use span::Edition;
use syntax::ast::{self, AstNode};
use test_fixture::ChangeFixture;
use crate::RootDatabase;
/// Creates analysis from a multi-file fixture, returns positions marked with $0.
pub(crate) fn position(
#[rust_analyzer::rust_fixture] ra_fixture: &str,
) -> (RootDatabase, FilePosition) {
let mut database = RootDatabase::default();
let change_fixture = ChangeFixture::parse(ra_fixture);
database.apply_change(change_fixture.change);
let (file_id, range_or_offset) =
change_fixture.file_position.expect("expected a marker ($0)");
let file_id = EditionedFileId::from_span_file_id(&database, file_id);
let offset = range_or_offset.expect_offset();
(database, FilePosition { file_id, offset })
}
fn check_trait(#[rust_analyzer::rust_fixture] 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).display(&db, Edition::CURRENT).to_string(),
None => String::new(),
};
expect.assert_eq(&actual);
}
fn check_missing_assoc(#[rust_analyzer::rust_fixture] 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 =
hir::attach_db(&db, || crate::traits::get_missing_assoc_items(&sema, &impl_block));
let actual = items
.into_iter()
.map(|(item, _)| item.name(&db).unwrap().display(&db, Edition::CURRENT).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#""#]],
);
}
}