Merge pull request #20893 from ChayimFriedman2/specialization-ns
fix: Implement `Interner::impl_specializes()`
This commit is contained in:
commit
b12c794608
6 changed files with 163 additions and 9 deletions
|
|
@ -27,6 +27,7 @@ mod infer;
|
|||
mod inhabitedness;
|
||||
mod lower;
|
||||
pub mod next_solver;
|
||||
mod specialization;
|
||||
mod target_feature;
|
||||
mod utils;
|
||||
|
||||
|
|
|
|||
|
|
@ -636,16 +636,13 @@ fn main() {
|
|||
);
|
||||
}
|
||||
|
||||
#[ignore = "
|
||||
FIXME(next-solver):
|
||||
This does not work currently because I replaced homemade selection with selection by the trait solver;
|
||||
This will work once we implement `Interner::impl_specializes()` properly.
|
||||
"]
|
||||
#[test]
|
||||
fn specialization_array_clone() {
|
||||
check_pass(
|
||||
r#"
|
||||
//- minicore: copy, derive, slice, index, coerce_unsized, panic
|
||||
#![feature(min_specialization)]
|
||||
|
||||
impl<T: Clone, const N: usize> Clone for [T; N] {
|
||||
#[inline]
|
||||
fn clone(&self) -> Self {
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ macro_rules! declare_id_wrapper {
|
|||
|
||||
impl std::fmt::Debug for $name {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::Debug::fmt(&self.0, f)
|
||||
std::fmt::Debug::fmt(&SolverDefId::from(self.0), f)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1922,10 +1922,14 @@ impl<'db> Interner for DbInterner<'db> {
|
|||
|
||||
fn impl_specializes(
|
||||
self,
|
||||
_specializing_impl_def_id: Self::ImplId,
|
||||
_parent_impl_def_id: Self::ImplId,
|
||||
specializing_impl_def_id: Self::ImplId,
|
||||
parent_impl_def_id: Self::ImplId,
|
||||
) -> bool {
|
||||
false
|
||||
crate::specialization::specializes(
|
||||
self.db,
|
||||
specializing_impl_def_id.0,
|
||||
parent_impl_def_id.0,
|
||||
)
|
||||
}
|
||||
|
||||
fn next_trait_solver_globally(self) -> bool {
|
||||
|
|
|
|||
150
src/tools/rust-analyzer/crates/hir-ty/src/specialization.rs
Normal file
150
src/tools/rust-analyzer/crates/hir-ty/src/specialization.rs
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
//! 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
|
||||
}
|
||||
|
|
@ -517,4 +517,6 @@ define_symbols! {
|
|||
precision,
|
||||
width,
|
||||
never_type_fallback,
|
||||
specialization,
|
||||
min_specialization,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue