blob: 611947b96b713c23b44aacd77b3ae94ae77fe4cd [file] [log] [blame] [edit]
//! Impl specialization related things
use hir_def::{ImplId, nameres::crate_def_map};
use intern::sym;
use tracing::debug;
use crate::{
db::HirDatabase,
next_solver::{
DbInterner, TypingMode,
infer::{
DbInternerInferExt,
traits::{Obligation, ObligationCause},
},
obligation_ctxt::ObligationCtxt,
},
};
// rustc does not have a cycle handling for the `specializes` query, meaning a cycle is a bug,
// and indeed I was unable to cause cycles even with erroneous code. However, in r-a we can
// create a cycle if there is an error in the impl's where clauses. I believe well formed code
// cannot create a cycle, but a cycle handler is required nevertheless.
fn specializes_cycle(
_db: &dyn HirDatabase,
_specializing_impl_def_id: ImplId,
_parent_impl_def_id: ImplId,
) -> bool {
false
}
/// Is `specializing_impl_def_id` a specialization of `parent_impl_def_id`?
///
/// For every type that could apply to `specializing_impl_def_id`, we prove that
/// the `parent_impl_def_id` also applies (i.e. it has a valid impl header and
/// its where-clauses hold).
///
/// For the purposes of const traits, we also check that the specializing
/// impl is not more restrictive than the parent impl. That is, if the
/// `parent_impl_def_id` is a const impl (conditionally based off of some `[const]`
/// bounds), then `specializing_impl_def_id` must also be const for the same
/// set of types.
#[salsa::tracked(cycle_result = specializes_cycle)]
pub(crate) fn specializes(
db: &dyn HirDatabase,
specializing_impl_def_id: ImplId,
parent_impl_def_id: ImplId,
) -> bool {
let module = specializing_impl_def_id.loc(db).container;
// We check that the specializing impl comes from a crate that has specialization enabled.
//
// We don't really care if the specialized impl (the parent) is in a crate that has
// specialization enabled, since it's not being specialized.
//
// rustc also checks whether the specializing impls comes from a macro marked
// `#[allow_internal_unstable(specialization)]`, but `#[allow_internal_unstable]`
// is an internal feature, std is not using it for specialization nor is likely to
// ever use it, and we don't have the span information necessary to replicate that.
let def_map = crate_def_map(db, module.krate());
if !def_map.is_unstable_feature_enabled(&sym::specialization)
&& !def_map.is_unstable_feature_enabled(&sym::min_specialization)
{
return false;
}
let interner = DbInterner::new_with(db, Some(module.krate()), module.containing_block());
let specializing_impl_signature = db.impl_signature(specializing_impl_def_id);
let parent_impl_signature = db.impl_signature(parent_impl_def_id);
// We determine whether there's a subset relationship by:
//
// - replacing bound vars with placeholders in impl1,
// - assuming the where clauses for impl1,
// - instantiating impl2 with fresh inference variables,
// - unifying,
// - attempting to prove the where clauses for impl2
//
// The last three steps are encapsulated in `fulfill_implication`.
//
// See RFC 1210 for more details and justification.
// Currently we do not allow e.g., a negative impl to specialize a positive one
if specializing_impl_signature.is_negative() != parent_impl_signature.is_negative() {
return false;
}
// create a parameter environment corresponding to an identity instantiation of the specializing impl,
// i.e. the most generic instantiation of the specializing impl.
let param_env = db.trait_environment(specializing_impl_def_id.into()).env;
// Create an infcx, taking the predicates of the specializing impl as assumptions:
let infcx = interner.infer_ctxt().build(TypingMode::non_body_analysis());
let specializing_impl_trait_ref =
db.impl_trait(specializing_impl_def_id).unwrap().instantiate_identity();
let cause = &ObligationCause::dummy();
debug!(
"fulfill_implication({:?}, trait_ref={:?} |- {:?} applies)",
param_env, specializing_impl_trait_ref, parent_impl_def_id
);
// Attempt to prove that the parent impl applies, given all of the above.
let mut ocx = ObligationCtxt::new(&infcx);
let parent_args = infcx.fresh_args_for_item(parent_impl_def_id.into());
let parent_impl_trait_ref = db
.impl_trait(parent_impl_def_id)
.expect("expected source impl to be a trait impl")
.instantiate(interner, parent_args);
// do the impls unify? If not, no specialization.
let Ok(()) = ocx.eq(cause, param_env, specializing_impl_trait_ref, parent_impl_trait_ref)
else {
return false;
};
// Now check that the source trait ref satisfies all the where clauses of the target impl.
// This is not just for correctness; we also need this to constrain any params that may
// only be referenced via projection predicates.
if let Some(predicates) =
db.generic_predicates(parent_impl_def_id.into()).instantiate(interner, parent_args)
{
ocx.register_obligations(
predicates
.map(|predicate| Obligation::new(interner, cause.clone(), param_env, predicate)),
);
}
let errors = ocx.evaluate_obligations_error_on_ambiguity();
if !errors.is_empty() {
// no dice!
debug!(
"fulfill_implication: for impls on {:?} and {:?}, \
could not fulfill: {:?} given {:?}",
specializing_impl_trait_ref, parent_impl_trait_ref, errors, param_env
);
return false;
}
// FIXME: Check impl constness (when we implement const impls).
debug!(
"fulfill_implication: an impl for {:?} specializes {:?}",
specializing_impl_trait_ref, parent_impl_trait_ref
);
true
}