Skip to content

Commit cd8132b

Browse files
Rollup merge of #111983 - compiler-errors:type-op-locally, r=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 #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`
2 parents b2abb2b + c4e8a86 commit cd8132b

File tree

15 files changed

+682
-564
lines changed

15 files changed

+682
-564
lines changed

compiler/rustc_borrowck/src/type_check/liveness/trace.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ use rustc_index::bit_set::HybridBitSet;
33
use rustc_index::interval::IntervalSet;
44
use rustc_infer::infer::canonical::QueryRegionConstraints;
55
use rustc_middle::mir::{BasicBlock, Body, ConstraintCategory, Local, Location};
6+
use rustc_middle::traits::query::DropckOutlivesResult;
67
use rustc_middle::ty::{Ty, TyCtxt, TypeVisitable, TypeVisitableExt};
78
use rustc_span::DUMMY_SP;
8-
use rustc_trait_selection::traits::query::dropck_outlives::DropckOutlivesResult;
99
use rustc_trait_selection::traits::query::type_op::outlives::DropckOutlives;
1010
use rustc_trait_selection::traits::query::type_op::{TypeOp, TypeOpOutput};
1111
use std::rc::Rc;

compiler/rustc_trait_selection/src/traits/outlives_bounds.rs

+23-14
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use crate::infer::InferCtxt;
2-
use crate::traits::query::type_op::{self, TypeOp, TypeOpOutput};
32
use crate::traits::{ObligationCause, ObligationCtxt};
43
use rustc_data_structures::fx::FxIndexSet;
5-
use rustc_errors::ErrorGuaranteed;
64
use rustc_infer::infer::resolve::OpportunisticRegionResolver;
5+
use rustc_infer::infer::InferOk;
6+
use rustc_middle::infer::canonical::{OriginalQueryValues, QueryRegionConstraints};
77
use rustc_middle::ty::{self, ParamEnv, Ty, TypeFolder, TypeVisitableExt};
88
use rustc_span::def_id::LocalDefId;
99

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

71-
let span = self.tcx.def_span(body_id);
72-
let result: Result<_, ErrorGuaranteed> = param_env
73-
.and(type_op::implied_outlives_bounds::ImpliedOutlivesBounds { ty })
74-
.fully_perform(self, span);
75-
let result = match result {
76-
Ok(r) => r,
77-
Err(_) => {
78-
return vec![];
79-
}
71+
let mut canonical_var_values = OriginalQueryValues::default();
72+
let canonical_ty =
73+
self.canonicalize_query_keep_static(param_env.and(ty), &mut canonical_var_values);
74+
let Ok(canonical_result) = self.tcx.implied_outlives_bounds(canonical_ty) else {
75+
return vec![];
76+
};
77+
78+
let mut constraints = QueryRegionConstraints::default();
79+
let Ok(InferOk { value, obligations }) = self
80+
.instantiate_nll_query_response_and_region_obligations(
81+
&ObligationCause::dummy(),
82+
param_env,
83+
&canonical_var_values,
84+
canonical_result,
85+
&mut constraints,
86+
) else {
87+
return vec![];
8088
};
89+
assert_eq!(&obligations, &[]);
8190

82-
let TypeOpOutput { output, constraints, .. } = result;
91+
if !constraints.is_empty() {
92+
let span = self.tcx.def_span(body_id);
8393

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

111-
output
120+
value
112121
}
113122

114123
fn implied_bounds_tys(

compiler/rustc_trait_selection/src/traits/query/dropck_outlives.rs

+267-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1-
use rustc_middle::ty::{self, Ty, TyCtxt};
1+
use crate::traits::query::normalize::QueryNormalizeExt;
2+
use crate::traits::query::NoSolution;
3+
use crate::traits::{Normalized, ObligationCause, ObligationCtxt};
24

3-
pub use rustc_middle::traits::query::{DropckConstraint, DropckOutlivesResult};
5+
use rustc_data_structures::fx::FxHashSet;
6+
use rustc_middle::traits::query::{DropckConstraint, DropckOutlivesResult};
7+
use rustc_middle::ty::{self, EarlyBinder, ParamEnvAnd, Ty, TyCtxt};
8+
use rustc_span::source_map::{Span, DUMMY_SP};
49

510
/// This returns true if the type `ty` is "trivial" for
611
/// dropck-outlives -- that is, if it doesn't require any types to
@@ -71,3 +76,263 @@ pub fn trivial_dropck_outlives<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool {
7176
| ty::Generator(..) => false,
7277
}
7378
}
79+
80+
pub fn compute_dropck_outlives_inner<'tcx>(
81+
ocx: &ObligationCtxt<'_, 'tcx>,
82+
goal: ParamEnvAnd<'tcx, Ty<'tcx>>,
83+
) -> Result<DropckOutlivesResult<'tcx>, NoSolution> {
84+
let tcx = ocx.infcx.tcx;
85+
let ParamEnvAnd { param_env, value: for_ty } = goal;
86+
87+
let mut result = DropckOutlivesResult { kinds: vec![], overflows: vec![] };
88+
89+
// A stack of types left to process. Each round, we pop
90+
// something from the stack and invoke
91+
// `dtorck_constraint_for_ty_inner`. This may produce new types that
92+
// have to be pushed on the stack. This continues until we have explored
93+
// all the reachable types from the type `for_ty`.
94+
//
95+
// Example: Imagine that we have the following code:
96+
//
97+
// ```rust
98+
// struct A {
99+
// value: B,
100+
// children: Vec<A>,
101+
// }
102+
//
103+
// struct B {
104+
// value: u32
105+
// }
106+
//
107+
// fn f() {
108+
// let a: A = ...;
109+
// ..
110+
// } // here, `a` is dropped
111+
// ```
112+
//
113+
// at the point where `a` is dropped, we need to figure out
114+
// which types inside of `a` contain region data that may be
115+
// accessed by any destructors in `a`. We begin by pushing `A`
116+
// onto the stack, as that is the type of `a`. We will then
117+
// invoke `dtorck_constraint_for_ty_inner` which will expand `A`
118+
// into the types of its fields `(B, Vec<A>)`. These will get
119+
// pushed onto the stack. Eventually, expanding `Vec<A>` will
120+
// lead to us trying to push `A` a second time -- to prevent
121+
// infinite recursion, we notice that `A` was already pushed
122+
// once and stop.
123+
let mut ty_stack = vec![(for_ty, 0)];
124+
125+
// Set used to detect infinite recursion.
126+
let mut ty_set = FxHashSet::default();
127+
128+
let cause = ObligationCause::dummy();
129+
let mut constraints = DropckConstraint::empty();
130+
while let Some((ty, depth)) = ty_stack.pop() {
131+
debug!(
132+
"{} kinds, {} overflows, {} ty_stack",
133+
result.kinds.len(),
134+
result.overflows.len(),
135+
ty_stack.len()
136+
);
137+
dtorck_constraint_for_ty_inner(tcx, DUMMY_SP, for_ty, depth, ty, &mut constraints)?;
138+
139+
// "outlives" represent types/regions that may be touched
140+
// by a destructor.
141+
result.kinds.append(&mut constraints.outlives);
142+
result.overflows.append(&mut constraints.overflows);
143+
144+
// If we have even one overflow, we should stop trying to evaluate further --
145+
// chances are, the subsequent overflows for this evaluation won't provide useful
146+
// information and will just decrease the speed at which we can emit these errors
147+
// (since we'll be printing for just that much longer for the often enormous types
148+
// that result here).
149+
if !result.overflows.is_empty() {
150+
break;
151+
}
152+
153+
// dtorck types are "types that will get dropped but which
154+
// do not themselves define a destructor", more or less. We have
155+
// to push them onto the stack to be expanded.
156+
for ty in constraints.dtorck_types.drain(..) {
157+
let Normalized { value: ty, obligations } =
158+
ocx.infcx.at(&cause, param_env).query_normalize(ty)?;
159+
ocx.register_obligations(obligations);
160+
161+
debug!("dropck_outlives: ty from dtorck_types = {:?}", ty);
162+
163+
match ty.kind() {
164+
// All parameters live for the duration of the
165+
// function.
166+
ty::Param(..) => {}
167+
168+
// A projection that we couldn't resolve - it
169+
// might have a destructor.
170+
ty::Alias(..) => {
171+
result.kinds.push(ty.into());
172+
}
173+
174+
_ => {
175+
if ty_set.insert(ty) {
176+
ty_stack.push((ty, depth + 1));
177+
}
178+
}
179+
}
180+
}
181+
}
182+
183+
debug!("dropck_outlives: result = {:#?}", result);
184+
Ok(result)
185+
}
186+
187+
/// Returns a set of constraints that needs to be satisfied in
188+
/// order for `ty` to be valid for destruction.
189+
pub fn dtorck_constraint_for_ty_inner<'tcx>(
190+
tcx: TyCtxt<'tcx>,
191+
span: Span,
192+
for_ty: Ty<'tcx>,
193+
depth: usize,
194+
ty: Ty<'tcx>,
195+
constraints: &mut DropckConstraint<'tcx>,
196+
) -> Result<(), NoSolution> {
197+
debug!("dtorck_constraint_for_ty_inner({:?}, {:?}, {:?}, {:?})", span, for_ty, depth, ty);
198+
199+
if !tcx.recursion_limit().value_within_limit(depth) {
200+
constraints.overflows.push(ty);
201+
return Ok(());
202+
}
203+
204+
if trivial_dropck_outlives(tcx, ty) {
205+
return Ok(());
206+
}
207+
208+
match ty.kind() {
209+
ty::Bool
210+
| ty::Char
211+
| ty::Int(_)
212+
| ty::Uint(_)
213+
| ty::Float(_)
214+
| ty::Str
215+
| ty::Never
216+
| ty::Foreign(..)
217+
| ty::RawPtr(..)
218+
| ty::Ref(..)
219+
| ty::FnDef(..)
220+
| ty::FnPtr(_)
221+
| ty::GeneratorWitness(..)
222+
| ty::GeneratorWitnessMIR(..) => {
223+
// these types never have a destructor
224+
}
225+
226+
ty::Array(ety, _) | ty::Slice(ety) => {
227+
// single-element containers, behave like their element
228+
rustc_data_structures::stack::ensure_sufficient_stack(|| {
229+
dtorck_constraint_for_ty_inner(tcx, span, for_ty, depth + 1, *ety, constraints)
230+
})?;
231+
}
232+
233+
ty::Tuple(tys) => rustc_data_structures::stack::ensure_sufficient_stack(|| {
234+
for ty in tys.iter() {
235+
dtorck_constraint_for_ty_inner(tcx, span, for_ty, depth + 1, ty, constraints)?;
236+
}
237+
Ok::<_, NoSolution>(())
238+
})?,
239+
240+
ty::Closure(_, substs) => {
241+
if !substs.as_closure().is_valid() {
242+
// By the time this code runs, all type variables ought to
243+
// be fully resolved.
244+
245+
tcx.sess.delay_span_bug(
246+
span,
247+
format!("upvar_tys for closure not found. Expected capture information for closure {ty}",),
248+
);
249+
return Err(NoSolution);
250+
}
251+
252+
rustc_data_structures::stack::ensure_sufficient_stack(|| {
253+
for ty in substs.as_closure().upvar_tys() {
254+
dtorck_constraint_for_ty_inner(tcx, span, for_ty, depth + 1, ty, constraints)?;
255+
}
256+
Ok::<_, NoSolution>(())
257+
})?
258+
}
259+
260+
ty::Generator(_, substs, _movability) => {
261+
// rust-lang/rust#49918: types can be constructed, stored
262+
// in the interior, and sit idle when generator yields
263+
// (and is subsequently dropped).
264+
//
265+
// It would be nice to descend into interior of a
266+
// generator to determine what effects dropping it might
267+
// have (by looking at any drop effects associated with
268+
// its interior).
269+
//
270+
// However, the interior's representation uses things like
271+
// GeneratorWitness that explicitly assume they are not
272+
// traversed in such a manner. So instead, we will
273+
// simplify things for now by treating all generators as
274+
// if they were like trait objects, where its upvars must
275+
// all be alive for the generator's (potential)
276+
// destructor.
277+
//
278+
// In particular, skipping over `_interior` is safe
279+
// because any side-effects from dropping `_interior` can
280+
// only take place through references with lifetimes
281+
// derived from lifetimes attached to the upvars and resume
282+
// argument, and we *do* incorporate those here.
283+
284+
if !substs.as_generator().is_valid() {
285+
// By the time this code runs, all type variables ought to
286+
// be fully resolved.
287+
tcx.sess.delay_span_bug(
288+
span,
289+
format!("upvar_tys for generator not found. Expected capture information for generator {ty}",),
290+
);
291+
return Err(NoSolution);
292+
}
293+
294+
constraints.outlives.extend(
295+
substs
296+
.as_generator()
297+
.upvar_tys()
298+
.map(|t| -> ty::subst::GenericArg<'tcx> { t.into() }),
299+
);
300+
constraints.outlives.push(substs.as_generator().resume_ty().into());
301+
}
302+
303+
ty::Adt(def, substs) => {
304+
let DropckConstraint { dtorck_types, outlives, overflows } =
305+
tcx.at(span).adt_dtorck_constraint(def.did())?;
306+
// FIXME: we can try to recursively `dtorck_constraint_on_ty`
307+
// there, but that needs some way to handle cycles.
308+
constraints
309+
.dtorck_types
310+
.extend(dtorck_types.iter().map(|t| EarlyBinder(*t).subst(tcx, substs)));
311+
constraints
312+
.outlives
313+
.extend(outlives.iter().map(|t| EarlyBinder(*t).subst(tcx, substs)));
314+
constraints
315+
.overflows
316+
.extend(overflows.iter().map(|t| EarlyBinder(*t).subst(tcx, substs)));
317+
}
318+
319+
// Objects must be alive in order for their destructor
320+
// to be called.
321+
ty::Dynamic(..) => {
322+
constraints.outlives.push(ty.into());
323+
}
324+
325+
// Types that can't be resolved. Pass them forward.
326+
ty::Alias(..) | ty::Param(..) => {
327+
constraints.dtorck_types.push(ty);
328+
}
329+
330+
ty::Placeholder(..) | ty::Bound(..) | ty::Infer(..) | ty::Error(_) => {
331+
// By the time this code runs, all type variables ought to
332+
// be fully resolved.
333+
return Err(NoSolution);
334+
}
335+
}
336+
337+
Ok(())
338+
}

0 commit comments

Comments
 (0)