Rollup merge of #94057 - lcnr:simplify_type-uwu, r=nikomatsakis

improve comments for `simplify_type`

Should now correctly describe what's going on. Experimented with checking the invariant for projections
but that ended up requiring fairly involved changes. I assume that it is not possible to get unsoundness here,
at least for now and I can pretty much guarantee that it's impossible to trigger it by accident.

r? `````@nikomatsakis````` cc #92721
This commit is contained in:
Matthias Krüger
2022-03-03 20:01:44 +01:00
committed by GitHub
7 changed files with 66 additions and 60 deletions

View File

@@ -49,9 +49,14 @@ where
}
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum SimplifyParams {
Yes,
No,
pub enum TreatParams {
/// Treat parameters as bound types in the given environment.
///
/// For this to be correct the input has to be fully normalized
/// in its param env as it may otherwise cause us to ignore
/// potentially applying impls.
AsBoundTypes,
AsPlaceholders,
}
/// Tries to simplify a type by only returning the outermost injective¹ layer, if one exists.
@@ -59,26 +64,21 @@ pub enum SimplifyParams {
/// The idea is to get something simple that we can use to quickly decide if two types could unify,
/// for example during method lookup.
///
/// A special case here are parameters and projections. Projections can be normalized to
/// a different type, meaning that `<T as Trait>::Assoc` and `u8` can be unified, even though
/// their outermost layer is different while parameters like `T` of impls are later replaced
/// with an inference variable, which then also allows unification with other types.
/// A special case here are parameters and projections, which are only injective
/// if they are treated as bound types.
///
/// When using `SimplifyParams::Yes`, we still return a simplified type for params and projections²,
/// the reasoning for this can be seen at the places doing this.
/// For example when storing impls based on their simplified self type, we treat
/// generic parameters as placeholders. We must not simplify them here,
/// as they can unify with any other type.
///
/// With projections we have to be even more careful, as even when treating them as bound types
/// this is still only correct if they are fully normalized.
///
/// ¹ meaning that if two outermost layers are different, then the whole types are also different.
/// ² FIXME(@lcnr): this seems like it can actually end up being unsound with the way it's used during
/// candidate selection. We do not consider non blanket impls for `<_ as Trait>::Assoc` even
/// though `_` can be inferred to a concrete type later at which point a concrete impl
/// could actually apply. After experimenting for about an hour I wasn't able to cause any issues
/// this way so I am not going to change this until we actually find an issue as I am really
/// interesting in getting an actual test for this.
pub fn simplify_type(
tcx: TyCtxt<'_>,
ty: Ty<'_>,
can_simplify_params: SimplifyParams,
/// ¹ meaning that if the outermost layers are different, then the whole types are also different.
pub fn simplify_type<'tcx>(
tcx: TyCtxt<'tcx>,
ty: Ty<'tcx>,
treat_params: TreatParams,
) -> Option<SimplifiedType> {
match *ty.kind() {
ty::Bool => Some(BoolSimplifiedType),
@@ -91,7 +91,7 @@ pub fn simplify_type(
ty::Array(..) => Some(ArraySimplifiedType),
ty::Slice(..) => Some(SliceSimplifiedType),
ty::RawPtr(ptr) => Some(PtrSimplifiedType(ptr.mutbl)),
ty::Dynamic(ref trait_info, ..) => match trait_info.principal_def_id() {
ty::Dynamic(trait_info, ..) => match trait_info.principal_def_id() {
Some(principal_def_id) if !tcx.trait_is_auto(principal_def_id) => {
Some(TraitSimplifiedType(principal_def_id))
}
@@ -100,24 +100,21 @@ pub fn simplify_type(
ty::Ref(_, _, mutbl) => Some(RefSimplifiedType(mutbl)),
ty::FnDef(def_id, _) | ty::Closure(def_id, _) => Some(ClosureSimplifiedType(def_id)),
ty::Generator(def_id, _, _) => Some(GeneratorSimplifiedType(def_id)),
ty::GeneratorWitness(ref tys) => {
Some(GeneratorWitnessSimplifiedType(tys.skip_binder().len()))
}
ty::GeneratorWitness(tys) => Some(GeneratorWitnessSimplifiedType(tys.skip_binder().len())),
ty::Never => Some(NeverSimplifiedType),
ty::Tuple(ref tys) => Some(TupleSimplifiedType(tys.len())),
ty::FnPtr(ref f) => Some(FunctionSimplifiedType(f.skip_binder().inputs().len())),
ty::Projection(_) | ty::Param(_) => {
if can_simplify_params == SimplifyParams::Yes {
// In normalized types, projections don't unify with
// anything. when lazy normalization happens, this
// will change. It would still be nice to have a way
// to deal with known-not-to-unify-with-anything
// projections (e.g., the likes of <__S as Encoder>::Error).
ty::Tuple(tys) => Some(TupleSimplifiedType(tys.len())),
ty::FnPtr(f) => Some(FunctionSimplifiedType(f.skip_binder().inputs().len())),
ty::Param(_) | ty::Projection(_) => match treat_params {
// When treated as bound types, projections don't unify with
// anything as long as they are fully normalized.
//
// We will have to be careful with lazy normalization here.
TreatParams::AsBoundTypes => {
debug!("treating `{}` as a bound type", ty);
Some(ParameterSimplifiedType)
} else {
None
}
}
TreatParams::AsPlaceholders => None,
},
ty::Opaque(def_id, _) => Some(OpaqueSimplifiedType(def_id)),
ty::Foreign(def_id) => Some(ForeignSimplifiedType(def_id)),
ty::Placeholder(..) | ty::Bound(..) | ty::Infer(_) | ty::Error(_) => None,

View File

@@ -1,5 +1,5 @@
use crate::traits::specialization_graph;
use crate::ty::fast_reject::{self, SimplifiedType, SimplifyParams};
use crate::ty::fast_reject::{self, SimplifiedType, TreatParams};
use crate::ty::fold::TypeFoldable;
use crate::ty::{Ident, Ty, TyCtxt};
use rustc_hir as hir;
@@ -150,7 +150,7 @@ impl<'tcx> TyCtxt<'tcx> {
self_ty: Ty<'tcx>,
) -> impl Iterator<Item = DefId> + 'tcx {
let impls = self.trait_impls_of(def_id);
if let Some(simp) = fast_reject::simplify_type(self, self_ty, SimplifyParams::No) {
if let Some(simp) = fast_reject::simplify_type(self, self_ty, TreatParams::AsPlaceholders) {
if let Some(impls) = impls.non_blanket_impls.get(&simp) {
return impls.iter().copied();
}
@@ -180,14 +180,14 @@ impl<'tcx> TyCtxt<'tcx> {
}
}
// Note that we're using `SimplifyParams::Yes` to query `non_blanket_impls` while using
// `SimplifyParams::No` while actually adding them.
// Note that we're using `TreatParams::AsBoundTypes` to query `non_blanket_impls` while using
// `TreatParams::AsPlaceholders` while actually adding them.
//
// This way, when searching for some impl for `T: Trait`, we do not look at any impls
// whose outer level is not a parameter or projection. Especially for things like
// `T: Clone` this is incredibly useful as we would otherwise look at all the impls
// of `Clone` for `Option<T>`, `Vec<T>`, `ConcreteType` and so on.
if let Some(simp) = fast_reject::simplify_type(self, self_ty, SimplifyParams::Yes) {
if let Some(simp) = fast_reject::simplify_type(self, self_ty, TreatParams::AsBoundTypes) {
if let Some(impls) = impls.non_blanket_impls.get(&simp) {
for &impl_def_id in impls {
if let result @ Some(_) = f(impl_def_id) {
@@ -247,7 +247,7 @@ pub(super) fn trait_impls_of_provider(tcx: TyCtxt<'_>, trait_id: DefId) -> Trait
}
if let Some(simplified_self_ty) =
fast_reject::simplify_type(tcx, impl_self_ty, SimplifyParams::No)
fast_reject::simplify_type(tcx, impl_self_ty, TreatParams::AsPlaceholders)
{
impls.non_blanket_impls.entry(simplified_self_ty).or_default().push(impl_def_id);
} else {