Skip to content

Commit

Permalink
Auto merge of rust-lang#85499 - jackh726:assoc-type-norm-rebase, r=ni…
Browse files Browse the repository at this point in the history
…komatsakis

Normalize projections under binders

Fixes rust-lang#70243
Fixes rust-lang#70120
Fixes rust-lang#62529
Fixes rust-lang#87219

Issues to followup on after (probably fixed, but no test added here):
rust-lang#76956
rust-lang#56556
rust-lang#79207
rust-lang#85636

r? `@nikomatsakis`
  • Loading branch information
bors committed Aug 25, 2021
2 parents 7b0e554 + 994a6bb commit 0afc208
Show file tree
Hide file tree
Showing 55 changed files with 811 additions and 391 deletions.
3 changes: 1 addition & 2 deletions compiler/rustc_middle/src/ty/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2483,10 +2483,9 @@ impl<'tcx> ty::Instance<'tcx> {
// `src/test/ui/polymorphization/normalized_sig_types.rs`), and codegen not keeping
// track of a polymorphization `ParamEnv` to allow normalizing later.
let mut sig = match *ty.kind() {
ty::FnDef(def_id, substs) if tcx.sess.opts.debugging_opts.polymorphize => tcx
ty::FnDef(def_id, substs) => tcx
.normalize_erasing_regions(tcx.param_env(def_id), tcx.fn_sig(def_id))
.subst(tcx, substs),
ty::FnDef(def_id, substs) => tcx.fn_sig(def_id).subst(tcx, substs),
_ => unreachable!(),
};

Expand Down
54 changes: 44 additions & 10 deletions compiler/rustc_mir/src/borrow_check/type_check/input_output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@

use rustc_infer::infer::LateBoundRegionConversionTime;
use rustc_middle::mir::*;
use rustc_middle::ty::Ty;
use rustc_middle::traits::ObligationCause;
use rustc_middle::ty::{self, Ty};
use rustc_trait_selection::traits::query::normalize::AtExt;

use rustc_index::vec::Idx;
use rustc_span::Span;
Expand Down Expand Up @@ -162,17 +164,49 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
fn equate_normalized_input_or_output(&mut self, a: Ty<'tcx>, b: Ty<'tcx>, span: Span) {
debug!("equate_normalized_input_or_output(a={:?}, b={:?})", a, b);

if let Err(terr) =
if let Err(_) =
self.eq_types(a, b, Locations::All(span), ConstraintCategory::BoringNoLocation)
{
span_mirbug!(
self,
Location::START,
"equate_normalized_input_or_output: `{:?}=={:?}` failed with `{:?}`",
a,
b,
terr
);
// FIXME(jackh726): This is a hack. It's somewhat like
// `rustc_traits::normalize_after_erasing_regions`. Ideally, we'd
// like to normalize *before* inserting into `local_decls`, but
// doing so ends up causing some other trouble.
let b = match self
.infcx
.at(&ObligationCause::dummy(), ty::ParamEnv::empty())
.normalize(b)
{
Ok(n) => {
debug!("equate_inputs_and_outputs: {:?}", n);
if n.obligations.iter().all(|o| {
matches!(
o.predicate.kind().skip_binder(),
ty::PredicateKind::RegionOutlives(_)
| ty::PredicateKind::TypeOutlives(_)
)
}) {
n.value
} else {
b
}
}
Err(_) => {
debug!("equate_inputs_and_outputs: NoSolution");
b
}
};
if let Err(terr) =
self.eq_types(a, b, Locations::All(span), ConstraintCategory::BoringNoLocation)
{
span_mirbug!(
self,
Location::START,
"equate_normalized_input_or_output: `{:?}=={:?}` failed with `{:?}`",
a,
b,
terr
);
}
}
}
}
1 change: 1 addition & 0 deletions compiler/rustc_mir/src/borrow_check/type_check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1053,6 +1053,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
);
for user_annotation in self.user_type_annotations {
let CanonicalUserTypeAnnotation { span, ref user_ty, inferred_ty } = *user_annotation;
let inferred_ty = self.normalize(inferred_ty, Locations::All(span));
let annotation = self.instantiate_canonical_with_fresh_inference_vars(span, user_ty);
match annotation {
UserType::Ty(mut ty) => {
Expand Down
103 changes: 59 additions & 44 deletions compiler/rustc_trait_selection/src/traits/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,25 +362,40 @@ impl<'a, 'b, 'tcx> TypeFolder<'tcx> for AssocTypeNormalizer<'a, 'b, 'tcx> {
if !needs_normalization(&ty, self.param_env.reveal()) {
return ty;
}
// We don't want to normalize associated types that occur inside of region
// binders, because they may contain bound regions, and we can't cope with that.

// We try to be a little clever here as a performance optimization in
// cases where there are nested projections under binders.
// For example:
// ```
// for<'a> fn(<T as Foo>::One<'a, Box<dyn Bar<'a, Item=<T as Foo>::Two<'a>>>>)
// ```
// We normalize the substs on the projection before the projecting, but
// if we're naive, we'll
// replace bound vars on inner, project inner, replace placeholders on inner,
// replace bound vars on outer, project outer, replace placeholders on outer
//
// Example:
// However, if we're a bit more clever, we can replace the bound vars
// on the entire type before normalizing nested projections, meaning we
// replace bound vars on outer, project inner,
// project outer, replace placeholders on outer
//
// for<'a> fn(<T as Foo<&'a>>::A)
// This is possible because the inner `'a` will already be a placeholder
// when we need to normalize the inner projection
//
// Instead of normalizing `<T as Foo<&'a>>::A` here, we'll
// normalize it when we instantiate those bound regions (which
// should occur eventually).
// On the other hand, this does add a bit of complexity, since we only
// replace bound vars if the current type is a `Projection` and we need
// to make sure we don't forget to fold the substs regardless.

let ty = ty.super_fold_with(self);
match *ty.kind() {
ty::Opaque(def_id, substs) if !substs.has_escaping_bound_vars() => {
ty::Opaque(def_id, substs) => {
// Only normalize `impl Trait` after type-checking, usually in codegen.
match self.param_env.reveal() {
Reveal::UserFacing => ty,
Reveal::UserFacing => ty.super_fold_with(self),

Reveal::All => {
// N.b. there is an assumption here all this code can handle
// escaping bound vars.

let recursion_limit = self.tcx().recursion_limit();
if !recursion_limit.value_within_limit(self.depth) {
let obligation = Obligation::with_depth(
Expand All @@ -392,6 +407,7 @@ impl<'a, 'b, 'tcx> TypeFolder<'tcx> for AssocTypeNormalizer<'a, 'b, 'tcx> {
self.selcx.infcx().report_overflow_error(&obligation, true);
}

let substs = substs.super_fold_with(self);
let generic_ty = self.tcx().type_of(def_id);
let concrete_ty = generic_ty.subst(self.tcx(), substs);
self.depth += 1;
Expand All @@ -403,18 +419,13 @@ impl<'a, 'b, 'tcx> TypeFolder<'tcx> for AssocTypeNormalizer<'a, 'b, 'tcx> {
}

ty::Projection(data) if !data.has_escaping_bound_vars() => {
// This is kind of hacky -- we need to be able to
// handle normalization within binders because
// otherwise we wind up a need to normalize when doing
// trait matching (since you can have a trait
// obligation like `for<'a> T::B: Fn(&'a i32)`), but
// we can't normalize with bound regions in scope. So
// far now we just ignore binders but only normalize
// if all bound regions are gone (and then we still
// have to renormalize whenever we instantiate a
// binder). It would be better to normalize in a
// binding-aware fashion.
// This branch is *mostly* just an optimization: when we don't
// have escaping bound vars, we don't need to replace them with
// placeholders (see branch below). *Also*, we know that we can
// register an obligation to *later* project, since we know
// there won't be bound vars there.

let data = data.super_fold_with(self);
let normalized_ty = normalize_projection_type(
self.selcx,
self.param_env,
Expand All @@ -433,22 +444,23 @@ impl<'a, 'b, 'tcx> TypeFolder<'tcx> for AssocTypeNormalizer<'a, 'b, 'tcx> {
normalized_ty
}

ty::Projection(data) if !data.trait_ref(self.tcx()).has_escaping_bound_vars() => {
// Okay, so you thought the previous branch was hacky. Well, to
// extend upon this, when the *trait ref* doesn't have escaping
// bound vars, but the associated item *does* (can only occur
// with GATs), then we might still be able to project the type.
// For this, we temporarily replace the bound vars with
// placeholders. Note though, that in the case that we still
// can't project for whatever reason (e.g. self type isn't
// known enough), we *can't* register an obligation and return
// an inference variable (since then that obligation would have
// bound vars and that's a can of worms). Instead, we just
// give up and fall back to pretending like we never tried!
ty::Projection(data) => {
// If there are escaping bound vars, we temporarily replace the
// bound vars with placeholders. Note though, that in the case
// that we still can't project for whatever reason (e.g. self
// type isn't known enough), we *can't* register an obligation
// and return an inference variable (since then that obligation
// would have bound vars and that's a can of worms). Instead,
// we just give up and fall back to pretending like we never tried!
//
// Note: this isn't necessarily the final approach here; we may
// want to figure out how to register obligations with escaping vars
// or handle this some other way.

let infcx = self.selcx.infcx();
let (data, mapped_regions, mapped_types, mapped_consts) =
BoundVarReplacer::replace_bound_vars(infcx, &mut self.universes, data);
let data = data.super_fold_with(self);
let normalized_ty = opt_normalize_projection_type(
self.selcx,
self.param_env,
Expand All @@ -459,16 +471,18 @@ impl<'a, 'b, 'tcx> TypeFolder<'tcx> for AssocTypeNormalizer<'a, 'b, 'tcx> {
)
.ok()
.flatten()
.unwrap_or_else(|| ty);

let normalized_ty = PlaceholderReplacer::replace_placeholders(
infcx,
mapped_regions,
mapped_types,
mapped_consts,
&self.universes,
normalized_ty,
);
.map(|normalized_ty| {
PlaceholderReplacer::replace_placeholders(
infcx,
mapped_regions,
mapped_types,
mapped_consts,
&self.universes,
normalized_ty,
)
})
.unwrap_or_else(|| ty.super_fold_with(self));

debug!(
?self.depth,
?ty,
Expand All @@ -479,7 +493,7 @@ impl<'a, 'b, 'tcx> TypeFolder<'tcx> for AssocTypeNormalizer<'a, 'b, 'tcx> {
normalized_ty
}

_ => ty,
_ => ty.super_fold_with(self),
}
}

Expand Down Expand Up @@ -908,6 +922,7 @@ fn opt_normalize_projection_type<'a, 'b, 'tcx>(
// an impl, where-clause etc) and hence we must
// re-normalize it

let projected_ty = selcx.infcx().resolve_vars_if_possible(projected_ty);
debug!(?projected_ty, ?depth, ?projected_obligations);

let result = if projected_ty.has_projections() {
Expand Down
Loading

0 comments on commit 0afc208

Please sign in to comment.