Skip to content

Commit

Permalink
Rollup merge of rust-lang#111983 - compiler-errors:type-op-locally, r…
Browse files Browse the repository at this point in the history
…=lcnr

Perform MIR type ops locally in new solver

The new solver already does caching, and it's generally more correct to be using the infcx of the MIR typeck (which has the defining anchor set correctly and has already initialized all the opaques from HIR typeck).

This is based on rust-lang#111918 so look at the final 3 commits.

This actually causes some tests to go from passing to failing, and failing to passing. Here's the full diff: https://www.diffchecker.com/hB4bh1A9/

Putting this up for exposure mostly.

r? `@lcnr`
  • Loading branch information
GuillaumeGomez committed May 27, 2023
2 parents b2abb2b + c4e8a86 commit cd8132b
Show file tree
Hide file tree
Showing 15 changed files with 682 additions and 564 deletions.
2 changes: 1 addition & 1 deletion compiler/rustc_borrowck/src/type_check/liveness/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ use rustc_index::bit_set::HybridBitSet;
use rustc_index::interval::IntervalSet;
use rustc_infer::infer::canonical::QueryRegionConstraints;
use rustc_middle::mir::{BasicBlock, Body, ConstraintCategory, Local, Location};
use rustc_middle::traits::query::DropckOutlivesResult;
use rustc_middle::ty::{Ty, TyCtxt, TypeVisitable, TypeVisitableExt};
use rustc_span::DUMMY_SP;
use rustc_trait_selection::traits::query::dropck_outlives::DropckOutlivesResult;
use rustc_trait_selection::traits::query::type_op::outlives::DropckOutlives;
use rustc_trait_selection::traits::query::type_op::{TypeOp, TypeOpOutput};
use std::rc::Rc;
Expand Down
37 changes: 23 additions & 14 deletions compiler/rustc_trait_selection/src/traits/outlives_bounds.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use crate::infer::InferCtxt;
use crate::traits::query::type_op::{self, TypeOp, TypeOpOutput};
use crate::traits::{ObligationCause, ObligationCtxt};
use rustc_data_structures::fx::FxIndexSet;
use rustc_errors::ErrorGuaranteed;
use rustc_infer::infer::resolve::OpportunisticRegionResolver;
use rustc_infer::infer::InferOk;
use rustc_middle::infer::canonical::{OriginalQueryValues, QueryRegionConstraints};
use rustc_middle::ty::{self, ParamEnv, Ty, TypeFolder, TypeVisitableExt};
use rustc_span::def_id::LocalDefId;

Expand Down Expand Up @@ -68,20 +68,29 @@ impl<'a, 'tcx: 'a> InferCtxtExt<'a, 'tcx> for InferCtxt<'tcx> {
return vec![];
}

let span = self.tcx.def_span(body_id);
let result: Result<_, ErrorGuaranteed> = param_env
.and(type_op::implied_outlives_bounds::ImpliedOutlivesBounds { ty })
.fully_perform(self, span);
let result = match result {
Ok(r) => r,
Err(_) => {
return vec![];
}
let mut canonical_var_values = OriginalQueryValues::default();
let canonical_ty =
self.canonicalize_query_keep_static(param_env.and(ty), &mut canonical_var_values);
let Ok(canonical_result) = self.tcx.implied_outlives_bounds(canonical_ty) else {
return vec![];
};

let mut constraints = QueryRegionConstraints::default();
let Ok(InferOk { value, obligations }) = self
.instantiate_nll_query_response_and_region_obligations(
&ObligationCause::dummy(),
param_env,
&canonical_var_values,
canonical_result,
&mut constraints,
) else {
return vec![];
};
assert_eq!(&obligations, &[]);

let TypeOpOutput { output, constraints, .. } = result;
if !constraints.is_empty() {
let span = self.tcx.def_span(body_id);

if let Some(constraints) = constraints {
debug!(?constraints);
if !constraints.member_constraints.is_empty() {
span_bug!(span, "{:#?}", constraints.member_constraints);
Expand All @@ -108,7 +117,7 @@ impl<'a, 'tcx: 'a> InferCtxtExt<'a, 'tcx> for InferCtxt<'tcx> {
}
};

output
value
}

fn implied_bounds_tys(
Expand Down
269 changes: 267 additions & 2 deletions compiler/rustc_trait_selection/src/traits/query/dropck_outlives.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
use rustc_middle::ty::{self, Ty, TyCtxt};
use crate::traits::query::normalize::QueryNormalizeExt;
use crate::traits::query::NoSolution;
use crate::traits::{Normalized, ObligationCause, ObligationCtxt};

pub use rustc_middle::traits::query::{DropckConstraint, DropckOutlivesResult};
use rustc_data_structures::fx::FxHashSet;
use rustc_middle::traits::query::{DropckConstraint, DropckOutlivesResult};
use rustc_middle::ty::{self, EarlyBinder, ParamEnvAnd, Ty, TyCtxt};
use rustc_span::source_map::{Span, DUMMY_SP};

/// This returns true if the type `ty` is "trivial" for
/// dropck-outlives -- that is, if it doesn't require any types to
Expand Down Expand Up @@ -71,3 +76,263 @@ pub fn trivial_dropck_outlives<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool {
| ty::Generator(..) => false,
}
}

pub fn compute_dropck_outlives_inner<'tcx>(
ocx: &ObligationCtxt<'_, 'tcx>,
goal: ParamEnvAnd<'tcx, Ty<'tcx>>,
) -> Result<DropckOutlivesResult<'tcx>, NoSolution> {
let tcx = ocx.infcx.tcx;
let ParamEnvAnd { param_env, value: for_ty } = goal;

let mut result = DropckOutlivesResult { kinds: vec![], overflows: vec![] };

// A stack of types left to process. Each round, we pop
// something from the stack and invoke
// `dtorck_constraint_for_ty_inner`. This may produce new types that
// have to be pushed on the stack. This continues until we have explored
// all the reachable types from the type `for_ty`.
//
// Example: Imagine that we have the following code:
//
// ```rust
// struct A {
// value: B,
// children: Vec<A>,
// }
//
// struct B {
// value: u32
// }
//
// fn f() {
// let a: A = ...;
// ..
// } // here, `a` is dropped
// ```
//
// at the point where `a` is dropped, we need to figure out
// which types inside of `a` contain region data that may be
// accessed by any destructors in `a`. We begin by pushing `A`
// onto the stack, as that is the type of `a`. We will then
// invoke `dtorck_constraint_for_ty_inner` which will expand `A`
// into the types of its fields `(B, Vec<A>)`. These will get
// pushed onto the stack. Eventually, expanding `Vec<A>` will
// lead to us trying to push `A` a second time -- to prevent
// infinite recursion, we notice that `A` was already pushed
// once and stop.
let mut ty_stack = vec![(for_ty, 0)];

// Set used to detect infinite recursion.
let mut ty_set = FxHashSet::default();

let cause = ObligationCause::dummy();
let mut constraints = DropckConstraint::empty();
while let Some((ty, depth)) = ty_stack.pop() {
debug!(
"{} kinds, {} overflows, {} ty_stack",
result.kinds.len(),
result.overflows.len(),
ty_stack.len()
);
dtorck_constraint_for_ty_inner(tcx, DUMMY_SP, for_ty, depth, ty, &mut constraints)?;

// "outlives" represent types/regions that may be touched
// by a destructor.
result.kinds.append(&mut constraints.outlives);
result.overflows.append(&mut constraints.overflows);

// If we have even one overflow, we should stop trying to evaluate further --
// chances are, the subsequent overflows for this evaluation won't provide useful
// information and will just decrease the speed at which we can emit these errors
// (since we'll be printing for just that much longer for the often enormous types
// that result here).
if !result.overflows.is_empty() {
break;
}

// dtorck types are "types that will get dropped but which
// do not themselves define a destructor", more or less. We have
// to push them onto the stack to be expanded.
for ty in constraints.dtorck_types.drain(..) {
let Normalized { value: ty, obligations } =
ocx.infcx.at(&cause, param_env).query_normalize(ty)?;
ocx.register_obligations(obligations);

debug!("dropck_outlives: ty from dtorck_types = {:?}", ty);

match ty.kind() {
// All parameters live for the duration of the
// function.
ty::Param(..) => {}

// A projection that we couldn't resolve - it
// might have a destructor.
ty::Alias(..) => {
result.kinds.push(ty.into());
}

_ => {
if ty_set.insert(ty) {
ty_stack.push((ty, depth + 1));
}
}
}
}
}

debug!("dropck_outlives: result = {:#?}", result);
Ok(result)
}

/// Returns a set of constraints that needs to be satisfied in
/// order for `ty` to be valid for destruction.
pub fn dtorck_constraint_for_ty_inner<'tcx>(
tcx: TyCtxt<'tcx>,
span: Span,
for_ty: Ty<'tcx>,
depth: usize,
ty: Ty<'tcx>,
constraints: &mut DropckConstraint<'tcx>,
) -> Result<(), NoSolution> {
debug!("dtorck_constraint_for_ty_inner({:?}, {:?}, {:?}, {:?})", span, for_ty, depth, ty);

if !tcx.recursion_limit().value_within_limit(depth) {
constraints.overflows.push(ty);
return Ok(());
}

if trivial_dropck_outlives(tcx, ty) {
return Ok(());
}

match ty.kind() {
ty::Bool
| ty::Char
| ty::Int(_)
| ty::Uint(_)
| ty::Float(_)
| ty::Str
| ty::Never
| ty::Foreign(..)
| ty::RawPtr(..)
| ty::Ref(..)
| ty::FnDef(..)
| ty::FnPtr(_)
| ty::GeneratorWitness(..)
| ty::GeneratorWitnessMIR(..) => {
// these types never have a destructor
}

ty::Array(ety, _) | ty::Slice(ety) => {
// single-element containers, behave like their element
rustc_data_structures::stack::ensure_sufficient_stack(|| {
dtorck_constraint_for_ty_inner(tcx, span, for_ty, depth + 1, *ety, constraints)
})?;
}

ty::Tuple(tys) => rustc_data_structures::stack::ensure_sufficient_stack(|| {
for ty in tys.iter() {
dtorck_constraint_for_ty_inner(tcx, span, for_ty, depth + 1, ty, constraints)?;
}
Ok::<_, NoSolution>(())
})?,

ty::Closure(_, substs) => {
if !substs.as_closure().is_valid() {
// By the time this code runs, all type variables ought to
// be fully resolved.

tcx.sess.delay_span_bug(
span,
format!("upvar_tys for closure not found. Expected capture information for closure {ty}",),
);
return Err(NoSolution);
}

rustc_data_structures::stack::ensure_sufficient_stack(|| {
for ty in substs.as_closure().upvar_tys() {
dtorck_constraint_for_ty_inner(tcx, span, for_ty, depth + 1, ty, constraints)?;
}
Ok::<_, NoSolution>(())
})?
}

ty::Generator(_, substs, _movability) => {
// rust-lang/rust#49918: types can be constructed, stored
// in the interior, and sit idle when generator yields
// (and is subsequently dropped).
//
// It would be nice to descend into interior of a
// generator to determine what effects dropping it might
// have (by looking at any drop effects associated with
// its interior).
//
// However, the interior's representation uses things like
// GeneratorWitness that explicitly assume they are not
// traversed in such a manner. So instead, we will
// simplify things for now by treating all generators as
// if they were like trait objects, where its upvars must
// all be alive for the generator's (potential)
// destructor.
//
// In particular, skipping over `_interior` is safe
// because any side-effects from dropping `_interior` can
// only take place through references with lifetimes
// derived from lifetimes attached to the upvars and resume
// argument, and we *do* incorporate those here.

if !substs.as_generator().is_valid() {
// By the time this code runs, all type variables ought to
// be fully resolved.
tcx.sess.delay_span_bug(
span,
format!("upvar_tys for generator not found. Expected capture information for generator {ty}",),
);
return Err(NoSolution);
}

constraints.outlives.extend(
substs
.as_generator()
.upvar_tys()
.map(|t| -> ty::subst::GenericArg<'tcx> { t.into() }),
);
constraints.outlives.push(substs.as_generator().resume_ty().into());
}

ty::Adt(def, substs) => {
let DropckConstraint { dtorck_types, outlives, overflows } =
tcx.at(span).adt_dtorck_constraint(def.did())?;
// FIXME: we can try to recursively `dtorck_constraint_on_ty`
// there, but that needs some way to handle cycles.
constraints
.dtorck_types
.extend(dtorck_types.iter().map(|t| EarlyBinder(*t).subst(tcx, substs)));
constraints
.outlives
.extend(outlives.iter().map(|t| EarlyBinder(*t).subst(tcx, substs)));
constraints
.overflows
.extend(overflows.iter().map(|t| EarlyBinder(*t).subst(tcx, substs)));
}

// Objects must be alive in order for their destructor
// to be called.
ty::Dynamic(..) => {
constraints.outlives.push(ty.into());
}

// Types that can't be resolved. Pass them forward.
ty::Alias(..) | ty::Param(..) => {
constraints.dtorck_types.push(ty);
}

ty::Placeholder(..) | ty::Bound(..) | ty::Infer(..) | ty::Error(_) => {
// By the time this code runs, all type variables ought to
// be fully resolved.
return Err(NoSolution);
}
}

Ok(())
}
Loading

0 comments on commit cd8132b

Please sign in to comment.