blob: acf532c5e44a71d934948e8e5c66647ed32efc72 [file] [log] [blame]
//! Handling of opaque types, detection of defining scope and hidden type.
use hir_def::{
AssocItemId, AssocItemLoc, DefWithBodyId, FunctionId, HasModule, ItemContainerId, TypeAliasId,
};
use hir_expand::name::Name;
use la_arena::ArenaMap;
use rustc_type_ir::inherent::Ty as _;
use syntax::ast;
use crate::{
ImplTraitId,
db::{HirDatabase, InternedOpaqueTyId},
lower::{ImplTraitIdx, ImplTraits},
next_solver::{
DbInterner, EarlyBinder, ErrorGuaranteed, SolverDefId, Ty, TypingMode,
infer::{DbInternerInferExt, traits::ObligationCause},
obligation_ctxt::ObligationCtxt,
},
};
pub(crate) fn opaque_types_defined_by(
db: &dyn HirDatabase,
def_id: DefWithBodyId,
result: &mut Vec<SolverDefId>,
) {
if let DefWithBodyId::FunctionId(func) = def_id {
// A function may define its own RPITs.
extend_with_opaques(
db,
ImplTraits::return_type_impl_traits(db, func),
|opaque_idx| ImplTraitId::ReturnTypeImplTrait(func, opaque_idx),
result,
);
}
let extend_with_taits = |type_alias| {
extend_with_opaques(
db,
ImplTraits::type_alias_impl_traits(db, type_alias),
|opaque_idx| ImplTraitId::TypeAliasImplTrait(type_alias, opaque_idx),
result,
);
};
// Collect opaques from assoc items.
let extend_with_atpit_from_assoc_items = |assoc_items: &[(Name, AssocItemId)]| {
assoc_items
.iter()
.filter_map(|&(_, assoc_id)| match assoc_id {
AssocItemId::TypeAliasId(it) => Some(it),
AssocItemId::FunctionId(_) | AssocItemId::ConstId(_) => None,
})
.for_each(extend_with_taits);
};
let extend_with_atpit_from_container = |container| match container {
ItemContainerId::ImplId(impl_id) => {
if db.impl_signature(impl_id).target_trait.is_some() {
extend_with_atpit_from_assoc_items(&impl_id.impl_items(db).items);
}
}
ItemContainerId::TraitId(trait_id) => {
extend_with_atpit_from_assoc_items(&trait_id.trait_items(db).items);
}
_ => {}
};
match def_id {
DefWithBodyId::ConstId(id) => extend_with_atpit_from_container(id.loc(db).container),
DefWithBodyId::FunctionId(id) => extend_with_atpit_from_container(id.loc(db).container),
DefWithBodyId::StaticId(_) | DefWithBodyId::VariantId(_) => {}
}
// FIXME: Collect opaques from `#[define_opaque]`.
fn extend_with_opaques<'db>(
db: &'db dyn HirDatabase,
opaques: &Option<Box<EarlyBinder<'db, ImplTraits<'db>>>>,
mut make_impl_trait: impl FnMut(ImplTraitIdx<'db>) -> ImplTraitId<'db>,
result: &mut Vec<SolverDefId>,
) {
if let Some(opaques) = opaques {
for (opaque_idx, _) in (**opaques).as_ref().skip_binder().impl_traits.iter() {
let opaque_id = InternedOpaqueTyId::new(db, make_impl_trait(opaque_idx));
result.push(opaque_id.into());
}
}
}
}
// These are firewall queries to prevent drawing dependencies between infers:
#[salsa::tracked(returns(ref), unsafe(non_update_return_type))]
pub(crate) fn rpit_hidden_types<'db>(
db: &'db dyn HirDatabase,
function: FunctionId,
) -> ArenaMap<ImplTraitIdx<'db>, EarlyBinder<'db, Ty<'db>>> {
let infer = db.infer(function.into());
let mut result = ArenaMap::new();
for (opaque, hidden_type) in infer.return_position_impl_trait_types(db) {
result.insert(opaque, EarlyBinder::bind(hidden_type));
}
result.shrink_to_fit();
result
}
#[salsa::tracked(returns(ref), unsafe(non_update_return_type))]
pub(crate) fn tait_hidden_types<'db>(
db: &'db dyn HirDatabase,
type_alias: TypeAliasId,
) -> ArenaMap<ImplTraitIdx<'db>, EarlyBinder<'db, Ty<'db>>> {
// Call this first, to not perform redundant work if there are no TAITs.
let Some(taits_count) = ImplTraits::type_alias_impl_traits(db, type_alias)
.as_deref()
.map(|taits| taits.as_ref().skip_binder().impl_traits.len())
else {
return ArenaMap::new();
};
let loc = type_alias.loc(db);
let module = loc.module(db);
let interner = DbInterner::new_with(db, Some(module.krate()), module.containing_block());
let infcx = interner.infer_ctxt().build(TypingMode::non_body_analysis());
let mut ocx = ObligationCtxt::new(&infcx);
let cause = ObligationCause::dummy();
let param_env = db.trait_environment(type_alias.into()).env;
let defining_bodies = tait_defining_bodies(db, &loc);
let mut result = ArenaMap::with_capacity(taits_count);
for defining_body in defining_bodies {
let infer = db.infer(defining_body);
for (&opaque, &hidden_type) in &infer.type_of_opaque {
let ImplTraitId::TypeAliasImplTrait(opaque_owner, opaque_idx) = opaque.loc(db) else {
continue;
};
if opaque_owner != type_alias {
continue;
}
// In the presence of errors, we attempt to create a unified type from all
// types. rustc doesn't do that, but this should improve the experience.
let hidden_type = infcx.insert_type_vars(hidden_type);
match result.entry(opaque_idx) {
la_arena::Entry::Vacant(entry) => {
entry.insert(EarlyBinder::bind(hidden_type));
}
la_arena::Entry::Occupied(entry) => {
_ = ocx.eq(&cause, param_env, entry.get().instantiate_identity(), hidden_type);
}
}
}
}
_ = ocx.try_evaluate_obligations();
// Fill missing entries.
for idx in 0..taits_count {
let idx = la_arena::Idx::from_raw(la_arena::RawIdx::from_u32(idx as u32));
match result.entry(idx) {
la_arena::Entry::Vacant(entry) => {
entry.insert(EarlyBinder::bind(Ty::new_error(interner, ErrorGuaranteed)));
}
la_arena::Entry::Occupied(mut entry) => {
*entry.get_mut() = entry.get().map_bound(|hidden_type| {
infcx.resolve_vars_if_possible(hidden_type).replace_infer_with_error(interner)
});
}
}
}
result
}
fn tait_defining_bodies(
db: &dyn HirDatabase,
loc: &AssocItemLoc<ast::TypeAlias>,
) -> Vec<DefWithBodyId> {
let from_assoc_items = |assoc_items: &[(Name, AssocItemId)]| {
// Associated Type Position Impl Trait.
assoc_items
.iter()
.filter_map(|&(_, assoc_id)| match assoc_id {
AssocItemId::FunctionId(it) => Some(it.into()),
AssocItemId::ConstId(it) => Some(it.into()),
AssocItemId::TypeAliasId(_) => None,
})
.collect()
};
match loc.container {
ItemContainerId::ImplId(impl_id) => {
if db.impl_signature(impl_id).target_trait.is_some() {
return from_assoc_items(&impl_id.impl_items(db).items);
}
}
ItemContainerId::TraitId(trait_id) => {
return from_assoc_items(&trait_id.trait_items(db).items);
}
_ => {}
}
// FIXME: Support general TAITs, or decisively decide not to.
Vec::new()
}