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
commit fec7a79088
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 66 additions and 60 deletions

View file

@ -26,7 +26,7 @@ use rustc_middle::mir::interpret;
use rustc_middle::thir; use rustc_middle::thir;
use rustc_middle::traits::specialization_graph; use rustc_middle::traits::specialization_graph;
use rustc_middle::ty::codec::TyEncoder; use rustc_middle::ty::codec::TyEncoder;
use rustc_middle::ty::fast_reject::{self, SimplifiedType, SimplifyParams}; use rustc_middle::ty::fast_reject::{self, SimplifiedType, TreatParams};
use rustc_middle::ty::query::Providers; use rustc_middle::ty::query::Providers;
use rustc_middle::ty::{self, SymbolName, Ty, TyCtxt}; use rustc_middle::ty::{self, SymbolName, Ty, TyCtxt};
use rustc_serialize::{opaque, Encodable, Encoder}; use rustc_serialize::{opaque, Encodable, Encoder};
@ -2043,7 +2043,7 @@ impl<'tcx, 'v> ItemLikeVisitor<'v> for ImplsVisitor<'tcx> {
let simplified_self_ty = fast_reject::simplify_type( let simplified_self_ty = fast_reject::simplify_type(
self.tcx, self.tcx,
trait_ref.self_ty(), trait_ref.self_ty(),
SimplifyParams::No, TreatParams::AsPlaceholders,
); );
self.impls self.impls

View file

@ -49,9 +49,14 @@ where
} }
#[derive(PartialEq, Eq, Debug, Clone, Copy)] #[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum SimplifyParams { pub enum TreatParams {
Yes, /// Treat parameters as bound types in the given environment.
No, ///
/// 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. /// 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, /// The idea is to get something simple that we can use to quickly decide if two types could unify,
/// for example during method lookup. /// for example during method lookup.
/// ///
/// A special case here are parameters and projections. Projections can be normalized to /// A special case here are parameters and projections, which are only injective
/// a different type, meaning that `<T as Trait>::Assoc` and `u8` can be unified, even though /// if they are treated as bound types.
/// 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.
/// ///
/// When using `SimplifyParams::Yes`, we still return a simplified type for params and projections², /// For example when storing impls based on their simplified self type, we treat
/// the reasoning for this can be seen at the places doing this. /// 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. /// ¹ meaning that if the 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 pub fn simplify_type<'tcx>(
/// candidate selection. We do not consider non blanket impls for `<_ as Trait>::Assoc` even tcx: TyCtxt<'tcx>,
/// though `_` can be inferred to a concrete type later at which point a concrete impl ty: Ty<'tcx>,
/// could actually apply. After experimenting for about an hour I wasn't able to cause any issues treat_params: TreatParams,
/// 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,
) -> Option<SimplifiedType> { ) -> Option<SimplifiedType> {
match *ty.kind() { match *ty.kind() {
ty::Bool => Some(BoolSimplifiedType), ty::Bool => Some(BoolSimplifiedType),
@ -91,7 +91,7 @@ pub fn simplify_type(
ty::Array(..) => Some(ArraySimplifiedType), ty::Array(..) => Some(ArraySimplifiedType),
ty::Slice(..) => Some(SliceSimplifiedType), ty::Slice(..) => Some(SliceSimplifiedType),
ty::RawPtr(ptr) => Some(PtrSimplifiedType(ptr.mutbl)), 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(principal_def_id) if !tcx.trait_is_auto(principal_def_id) => {
Some(TraitSimplifiedType(principal_def_id)) Some(TraitSimplifiedType(principal_def_id))
} }
@ -100,24 +100,21 @@ pub fn simplify_type(
ty::Ref(_, _, mutbl) => Some(RefSimplifiedType(mutbl)), ty::Ref(_, _, mutbl) => Some(RefSimplifiedType(mutbl)),
ty::FnDef(def_id, _) | ty::Closure(def_id, _) => Some(ClosureSimplifiedType(def_id)), ty::FnDef(def_id, _) | ty::Closure(def_id, _) => Some(ClosureSimplifiedType(def_id)),
ty::Generator(def_id, _, _) => Some(GeneratorSimplifiedType(def_id)), ty::Generator(def_id, _, _) => Some(GeneratorSimplifiedType(def_id)),
ty::GeneratorWitness(ref tys) => { ty::GeneratorWitness(tys) => Some(GeneratorWitnessSimplifiedType(tys.skip_binder().len())),
Some(GeneratorWitnessSimplifiedType(tys.skip_binder().len()))
}
ty::Never => Some(NeverSimplifiedType), ty::Never => Some(NeverSimplifiedType),
ty::Tuple(ref tys) => Some(TupleSimplifiedType(tys.len())), ty::Tuple(tys) => Some(TupleSimplifiedType(tys.len())),
ty::FnPtr(ref f) => Some(FunctionSimplifiedType(f.skip_binder().inputs().len())), ty::FnPtr(f) => Some(FunctionSimplifiedType(f.skip_binder().inputs().len())),
ty::Projection(_) | ty::Param(_) => { ty::Param(_) | ty::Projection(_) => match treat_params {
if can_simplify_params == SimplifyParams::Yes { // When treated as bound types, projections don't unify with
// In normalized types, projections don't unify with // anything as long as they are fully normalized.
// anything. when lazy normalization happens, this //
// will change. It would still be nice to have a way // We will have to be careful with lazy normalization here.
// to deal with known-not-to-unify-with-anything TreatParams::AsBoundTypes => {
// projections (e.g., the likes of <__S as Encoder>::Error). debug!("treating `{}` as a bound type", ty);
Some(ParameterSimplifiedType) Some(ParameterSimplifiedType)
} else {
None
}
} }
TreatParams::AsPlaceholders => None,
},
ty::Opaque(def_id, _) => Some(OpaqueSimplifiedType(def_id)), ty::Opaque(def_id, _) => Some(OpaqueSimplifiedType(def_id)),
ty::Foreign(def_id) => Some(ForeignSimplifiedType(def_id)), ty::Foreign(def_id) => Some(ForeignSimplifiedType(def_id)),
ty::Placeholder(..) | ty::Bound(..) | ty::Infer(_) | ty::Error(_) => None, ty::Placeholder(..) | ty::Bound(..) | ty::Infer(_) | ty::Error(_) => None,

View file

@ -1,5 +1,5 @@
use crate::traits::specialization_graph; 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::fold::TypeFoldable;
use crate::ty::{Ident, Ty, TyCtxt}; use crate::ty::{Ident, Ty, TyCtxt};
use rustc_hir as hir; use rustc_hir as hir;
@ -150,7 +150,7 @@ impl<'tcx> TyCtxt<'tcx> {
self_ty: Ty<'tcx>, self_ty: Ty<'tcx>,
) -> impl Iterator<Item = DefId> + 'tcx { ) -> impl Iterator<Item = DefId> + 'tcx {
let impls = self.trait_impls_of(def_id); 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) { if let Some(impls) = impls.non_blanket_impls.get(&simp) {
return impls.iter().copied(); 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 // Note that we're using `TreatParams::AsBoundTypes` to query `non_blanket_impls` while using
// `SimplifyParams::No` while actually adding them. // `TreatParams::AsPlaceholders` while actually adding them.
// //
// This way, when searching for some impl for `T: Trait`, we do not look at any impls // 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 // 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 // `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. // 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) { if let Some(impls) = impls.non_blanket_impls.get(&simp) {
for &impl_def_id in impls { for &impl_def_id in impls {
if let result @ Some(_) = f(impl_def_id) { 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) = 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); impls.non_blanket_impls.entry(simplified_self_ty).or_default().push(impl_def_id);
} else { } else {

View file

@ -20,7 +20,7 @@ use rustc_hir::CRATE_HIR_ID;
use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::infer::TyCtxtInferExt;
use rustc_infer::traits::TraitEngine; use rustc_infer::traits::TraitEngine;
use rustc_middle::traits::specialization_graph::OverlapMode; use rustc_middle::traits::specialization_graph::OverlapMode;
use rustc_middle::ty::fast_reject::{self, SimplifyParams}; use rustc_middle::ty::fast_reject::{self, TreatParams};
use rustc_middle::ty::fold::TypeFoldable; use rustc_middle::ty::fold::TypeFoldable;
use rustc_middle::ty::subst::Subst; use rustc_middle::ty::subst::Subst;
use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_middle::ty::{self, Ty, TyCtxt};
@ -87,8 +87,8 @@ where
impl2_ref.iter().flat_map(|tref| tref.substs.types()), impl2_ref.iter().flat_map(|tref| tref.substs.types()),
) )
.any(|(ty1, ty2)| { .any(|(ty1, ty2)| {
let t1 = fast_reject::simplify_type(tcx, ty1, SimplifyParams::No); let t1 = fast_reject::simplify_type(tcx, ty1, TreatParams::AsPlaceholders);
let t2 = fast_reject::simplify_type(tcx, ty2, SimplifyParams::No); let t2 = fast_reject::simplify_type(tcx, ty2, TreatParams::AsPlaceholders);
if let (Some(t1), Some(t2)) = (t1, t2) { if let (Some(t1), Some(t2)) = (t1, t2) {
// Simplified successfully // Simplified successfully

View file

@ -36,7 +36,7 @@ use rustc_infer::infer::LateBoundRegionConversionTime;
use rustc_middle::dep_graph::{DepKind, DepNodeIndex}; use rustc_middle::dep_graph::{DepKind, DepNodeIndex};
use rustc_middle::mir::interpret::ErrorHandled; use rustc_middle::mir::interpret::ErrorHandled;
use rustc_middle::thir::abstract_const::NotConstEvaluatable; use rustc_middle::thir::abstract_const::NotConstEvaluatable;
use rustc_middle::ty::fast_reject::{self, SimplifyParams}; use rustc_middle::ty::fast_reject::{self, TreatParams};
use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_middle::ty::relate::TypeRelation; use rustc_middle::ty::relate::TypeRelation;
use rustc_middle::ty::subst::{GenericArgKind, Subst, SubstsRef}; use rustc_middle::ty::subst::{GenericArgKind, Subst, SubstsRef};
@ -2176,8 +2176,8 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
fn fast_reject_trait_refs( fn fast_reject_trait_refs(
&mut self, &mut self,
obligation: &TraitObligation<'_>, obligation: &TraitObligation<'tcx>,
impl_trait_ref: &ty::TraitRef<'_>, impl_trait_ref: &ty::TraitRef<'tcx>,
) -> bool { ) -> bool {
// We can avoid creating type variables and doing the full // We can avoid creating type variables and doing the full
// substitution if we find that any of the input types, when // substitution if we find that any of the input types, when
@ -2193,10 +2193,13 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
let simplified_obligation_ty = fast_reject::simplify_type( let simplified_obligation_ty = fast_reject::simplify_type(
self.tcx(), self.tcx(),
obligation_ty, obligation_ty,
SimplifyParams::Yes, TreatParams::AsBoundTypes,
);
let simplified_impl_ty = fast_reject::simplify_type(
self.tcx(),
impl_ty,
TreatParams::AsPlaceholders,
); );
let simplified_impl_ty =
fast_reject::simplify_type(self.tcx(), impl_ty, SimplifyParams::No);
simplified_obligation_ty.is_some() simplified_obligation_ty.is_some()
&& simplified_impl_ty.is_some() && simplified_impl_ty.is_some()

View file

@ -2,7 +2,7 @@ use super::OverlapError;
use crate::traits; use crate::traits;
use rustc_hir::def_id::DefId; use rustc_hir::def_id::DefId;
use rustc_middle::ty::fast_reject::{self, SimplifiedType, SimplifyParams}; use rustc_middle::ty::fast_reject::{self, SimplifiedType, TreatParams};
use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_middle::ty::{self, TyCtxt, TypeFoldable}; use rustc_middle::ty::{self, TyCtxt, TypeFoldable};
@ -49,7 +49,9 @@ impl ChildrenExt<'_> for Children {
/// Insert an impl into this set of children without comparing to any existing impls. /// Insert an impl into this set of children without comparing to any existing impls.
fn insert_blindly(&mut self, tcx: TyCtxt<'_>, impl_def_id: DefId) { fn insert_blindly(&mut self, tcx: TyCtxt<'_>, impl_def_id: DefId) {
let trait_ref = tcx.impl_trait_ref(impl_def_id).unwrap(); let trait_ref = tcx.impl_trait_ref(impl_def_id).unwrap();
if let Some(st) = fast_reject::simplify_type(tcx, trait_ref.self_ty(), SimplifyParams::No) { if let Some(st) =
fast_reject::simplify_type(tcx, trait_ref.self_ty(), TreatParams::AsPlaceholders)
{
debug!("insert_blindly: impl_def_id={:?} st={:?}", impl_def_id, st); debug!("insert_blindly: impl_def_id={:?} st={:?}", impl_def_id, st);
self.non_blanket_impls.entry(st).or_default().push(impl_def_id) self.non_blanket_impls.entry(st).or_default().push(impl_def_id)
} else { } else {
@ -64,7 +66,9 @@ impl ChildrenExt<'_> for Children {
fn remove_existing(&mut self, tcx: TyCtxt<'_>, impl_def_id: DefId) { fn remove_existing(&mut self, tcx: TyCtxt<'_>, impl_def_id: DefId) {
let trait_ref = tcx.impl_trait_ref(impl_def_id).unwrap(); let trait_ref = tcx.impl_trait_ref(impl_def_id).unwrap();
let vec: &mut Vec<DefId>; let vec: &mut Vec<DefId>;
if let Some(st) = fast_reject::simplify_type(tcx, trait_ref.self_ty(), SimplifyParams::No) { if let Some(st) =
fast_reject::simplify_type(tcx, trait_ref.self_ty(), TreatParams::AsPlaceholders)
{
debug!("remove_existing: impl_def_id={:?} st={:?}", impl_def_id, st); debug!("remove_existing: impl_def_id={:?} st={:?}", impl_def_id, st);
vec = self.non_blanket_impls.get_mut(&st).unwrap(); vec = self.non_blanket_impls.get_mut(&st).unwrap();
} else { } else {
@ -312,7 +316,8 @@ impl GraphExt for Graph {
let mut parent = trait_def_id; let mut parent = trait_def_id;
let mut last_lint = None; let mut last_lint = None;
let simplified = fast_reject::simplify_type(tcx, trait_ref.self_ty(), SimplifyParams::No); let simplified =
fast_reject::simplify_type(tcx, trait_ref.self_ty(), TreatParams::AsPlaceholders);
// Descend the specialization tree, where `parent` is the current parent node. // Descend the specialization tree, where `parent` is the current parent node.
loop { loop {

View file

@ -12,7 +12,7 @@ use rustc_hir::lang_items::LangItem;
use rustc_hir::{ExprKind, Node, QPath}; use rustc_hir::{ExprKind, Node, QPath};
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
use rustc_middle::traits::util::supertraits; use rustc_middle::traits::util::supertraits;
use rustc_middle::ty::fast_reject::{simplify_type, SimplifyParams}; use rustc_middle::ty::fast_reject::{simplify_type, TreatParams};
use rustc_middle::ty::print::with_crate_prefix; use rustc_middle::ty::print::with_crate_prefix;
use rustc_middle::ty::ToPolyTraitRef; use rustc_middle::ty::ToPolyTraitRef;
use rustc_middle::ty::{self, DefIdTree, ToPredicate, Ty, TyCtxt, TypeFoldable}; use rustc_middle::ty::{self, DefIdTree, ToPredicate, Ty, TyCtxt, TypeFoldable};
@ -1777,7 +1777,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// FIXME: Even though negative bounds are not implemented, we could maybe handle // FIXME: Even though negative bounds are not implemented, we could maybe handle
// cases where a positive bound implies a negative impl. // cases where a positive bound implies a negative impl.
(candidates, Vec::new()) (candidates, Vec::new())
} else if let Some(simp_rcvr_ty) = simplify_type(self.tcx, rcvr_ty, SimplifyParams::Yes) } else if let Some(simp_rcvr_ty) =
simplify_type(self.tcx, rcvr_ty, TreatParams::AsBoundTypes)
{ {
let mut potential_candidates = Vec::new(); let mut potential_candidates = Vec::new();
let mut explicitly_negative = Vec::new(); let mut explicitly_negative = Vec::new();
@ -1792,7 +1793,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
.any(|imp_did| { .any(|imp_did| {
let imp = self.tcx.impl_trait_ref(imp_did).unwrap(); let imp = self.tcx.impl_trait_ref(imp_did).unwrap();
let imp_simp = let imp_simp =
simplify_type(self.tcx, imp.self_ty(), SimplifyParams::Yes); simplify_type(self.tcx, imp.self_ty(), TreatParams::AsBoundTypes);
imp_simp.map_or(false, |s| s == simp_rcvr_ty) imp_simp.map_or(false, |s| s == simp_rcvr_ty)
}) })
{ {