rustc_trait_selection/traits/query/dropck_outlives.rs
1use rustc_data_structures::fx::FxHashSet;
2use rustc_infer::traits::query::type_op::DropckOutlives;
3use rustc_middle::traits::query::{DropckConstraint, DropckOutlivesResult};
4use rustc_middle::ty::{self, EarlyBinder, ParamEnvAnd, Ty, TyCtxt};
5use rustc_span::Span;
6use tracing::{debug, instrument};
7
8use crate::solve::NextSolverError;
9use crate::traits::query::NoSolution;
10use crate::traits::query::normalize::QueryNormalizeExt;
11use crate::traits::{FromSolverError, Normalized, ObligationCause, ObligationCtxt};
12
13/// This returns true if the type `ty` is "trivial" for
14/// dropck-outlives -- that is, if it doesn't require any types to
15/// outlive. This is similar but not *quite* the same as the
16/// `needs_drop` test in the compiler already -- that is, for every
17/// type T for which this function return true, needs-drop would
18/// return `false`. But the reverse does not hold: in particular,
19/// `needs_drop` returns false for `PhantomData`, but it is not
20/// trivial for dropck-outlives.
21///
22/// Note also that `needs_drop` requires a "global" type (i.e., one
23/// with erased regions), but this function does not.
24///
25// FIXME(@lcnr): remove this module and move this function somewhere else.
26pub fn trivial_dropck_outlives<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool {
27 match ty.kind() {
28 // None of these types have a destructor and hence they do not
29 // require anything in particular to outlive the dtor's
30 // execution.
31 ty::Infer(ty::FreshIntTy(_))
32 | ty::Infer(ty::FreshFloatTy(_))
33 | ty::Bool
34 | ty::Int(_)
35 | ty::Uint(_)
36 | ty::Float(_)
37 | ty::Never
38 | ty::FnDef(..)
39 | ty::FnPtr(..)
40 | ty::Char
41 | ty::CoroutineWitness(..)
42 | ty::RawPtr(_, _)
43 | ty::Ref(..)
44 | ty::Str
45 | ty::Foreign(..)
46 | ty::Error(_) => true,
47
48 // `T is PAT` and `[T]` have same properties as T.
49 ty::Pat(ty, _) | ty::Slice(ty) => trivial_dropck_outlives(tcx, *ty),
50 ty::Array(ty, size) => {
51 // Empty array never has a dtor. See issue #110288.
52 match size.try_to_target_usize(tcx) {
53 Some(0) => true,
54 _ => trivial_dropck_outlives(tcx, *ty),
55 }
56 }
57
58 // (T1..Tn) and closures have same properties as T1..Tn --
59 // check if *all* of them are trivial.
60 ty::Tuple(tys) => tys.iter().all(|t| trivial_dropck_outlives(tcx, t)),
61
62 ty::Closure(_, args) => trivial_dropck_outlives(tcx, args.as_closure().tupled_upvars_ty()),
63 ty::CoroutineClosure(_, args) => {
64 trivial_dropck_outlives(tcx, args.as_coroutine_closure().tupled_upvars_ty())
65 }
66
67 ty::Adt(def, _) => {
68 if def.is_manually_drop() {
69 // `ManuallyDrop` never has a dtor.
70 true
71 } else {
72 // Other types might. Moreover, PhantomData doesn't
73 // have a dtor, but it is considered to own its
74 // content, so it is non-trivial. Unions can have `impl Drop`,
75 // and hence are non-trivial as well.
76 false
77 }
78 }
79
80 // The following *might* require a destructor: needs deeper inspection.
81 ty::Dynamic(..)
82 | ty::Alias(..)
83 | ty::Param(_)
84 | ty::Placeholder(..)
85 | ty::Infer(_)
86 | ty::Bound(..)
87 | ty::Coroutine(..)
88 | ty::UnsafeBinder(_) => false,
89 }
90}
91
92pub fn compute_dropck_outlives_inner<'tcx>(
93 ocx: &ObligationCtxt<'_, 'tcx>,
94 goal: ParamEnvAnd<'tcx, DropckOutlives<'tcx>>,
95 span: Span,
96) -> Result<DropckOutlivesResult<'tcx>, NoSolution> {
97 match compute_dropck_outlives_with_errors(ocx, goal, span) {
98 Ok(r) => Ok(r),
99 Err(_) => Err(NoSolution),
100 }
101}
102
103pub fn compute_dropck_outlives_with_errors<'tcx, E>(
104 ocx: &ObligationCtxt<'_, 'tcx, E>,
105 goal: ParamEnvAnd<'tcx, DropckOutlives<'tcx>>,
106 span: Span,
107) -> Result<DropckOutlivesResult<'tcx>, Vec<E>>
108where
109 E: FromSolverError<'tcx, NextSolverError<'tcx>>,
110{
111 let tcx = ocx.infcx.tcx;
112 let ParamEnvAnd { param_env, value: DropckOutlives { dropped_ty } } = goal;
113
114 let mut result = DropckOutlivesResult { kinds: vec![], overflows: vec![] };
115
116 // A stack of types left to process. Each round, we pop
117 // something from the stack and invoke
118 // `dtorck_constraint_for_ty_inner`. This may produce new types that
119 // have to be pushed on the stack. This continues until we have explored
120 // all the reachable types from the type `dropped_ty`.
121 //
122 // Example: Imagine that we have the following code:
123 //
124 // ```rust
125 // struct A {
126 // value: B,
127 // children: Vec<A>,
128 // }
129 //
130 // struct B {
131 // value: u32
132 // }
133 //
134 // fn f() {
135 // let a: A = ...;
136 // ..
137 // } // here, `a` is dropped
138 // ```
139 //
140 // at the point where `a` is dropped, we need to figure out
141 // which types inside of `a` contain region data that may be
142 // accessed by any destructors in `a`. We begin by pushing `A`
143 // onto the stack, as that is the type of `a`. We will then
144 // invoke `dtorck_constraint_for_ty_inner` which will expand `A`
145 // into the types of its fields `(B, Vec<A>)`. These will get
146 // pushed onto the stack. Eventually, expanding `Vec<A>` will
147 // lead to us trying to push `A` a second time -- to prevent
148 // infinite recursion, we notice that `A` was already pushed
149 // once and stop.
150 let mut ty_stack = vec![(dropped_ty, 0)];
151
152 // Set used to detect infinite recursion.
153 let mut ty_set = FxHashSet::default();
154
155 let cause = ObligationCause::dummy_with_span(span);
156 let mut constraints = DropckConstraint::empty();
157 while let Some((ty, depth)) = ty_stack.pop() {
158 debug!(
159 "{} kinds, {} overflows, {} ty_stack",
160 result.kinds.len(),
161 result.overflows.len(),
162 ty_stack.len()
163 );
164 dtorck_constraint_for_ty_inner(
165 tcx,
166 ocx.infcx.typing_env(param_env),
167 span,
168 depth,
169 ty,
170 &mut constraints,
171 );
172
173 // "outlives" represent types/regions that may be touched
174 // by a destructor.
175 result.kinds.append(&mut constraints.outlives);
176 result.overflows.append(&mut constraints.overflows);
177
178 // If we have even one overflow, we should stop trying to evaluate further --
179 // chances are, the subsequent overflows for this evaluation won't provide useful
180 // information and will just decrease the speed at which we can emit these errors
181 // (since we'll be printing for just that much longer for the often enormous types
182 // that result here).
183 if !result.overflows.is_empty() {
184 break;
185 }
186
187 // dtorck types are "types that will get dropped but which
188 // do not themselves define a destructor", more or less. We have
189 // to push them onto the stack to be expanded.
190 for ty in constraints.dtorck_types.drain(..) {
191 let ty = if let Ok(Normalized { value: ty, obligations }) =
192 ocx.infcx.at(&cause, param_env).query_normalize(ty)
193 {
194 ocx.register_obligations(obligations);
195
196 debug!("dropck_outlives: ty from dtorck_types = {:?}", ty);
197 ty
198 } else {
199 // Flush errors b/c `deeply_normalize` doesn't expect pending
200 // obligations, and we may have pending obligations from the
201 // branch above (from other types).
202 let errors = ocx.select_all_or_error();
203 if !errors.is_empty() {
204 return Err(errors);
205 }
206
207 // When query normalization fails, we don't get back an interesting
208 // reason that we could use to report an error in borrowck. In order to turn
209 // this into a reportable error, we deeply normalize again. We don't expect
210 // this to succeed, so delay a bug if it does.
211 match ocx.deeply_normalize(&cause, param_env, ty) {
212 Ok(_) => {
213 tcx.dcx().span_delayed_bug(
214 span,
215 format!(
216 "query normalize succeeded of {ty}, \
217 but deep normalize failed",
218 ),
219 );
220 ty
221 }
222 Err(errors) => return Err(errors),
223 }
224 };
225
226 match ty.kind() {
227 // All parameters live for the duration of the
228 // function.
229 ty::Param(..) => {}
230
231 // A projection that we couldn't resolve - it
232 // might have a destructor.
233 ty::Alias(..) => {
234 result.kinds.push(ty.into());
235 }
236
237 _ => {
238 if ty_set.insert(ty) {
239 ty_stack.push((ty, depth + 1));
240 }
241 }
242 }
243 }
244 }
245
246 debug!("dropck_outlives: result = {:#?}", result);
247 Ok(result)
248}
249
250/// Returns a set of constraints that needs to be satisfied in
251/// order for `ty` to be valid for destruction.
252#[instrument(level = "debug", skip(tcx, typing_env, span, constraints))]
253pub fn dtorck_constraint_for_ty_inner<'tcx>(
254 tcx: TyCtxt<'tcx>,
255 typing_env: ty::TypingEnv<'tcx>,
256 span: Span,
257 depth: usize,
258 ty: Ty<'tcx>,
259 constraints: &mut DropckConstraint<'tcx>,
260) {
261 if !tcx.recursion_limit().value_within_limit(depth) {
262 constraints.overflows.push(ty);
263 return;
264 }
265
266 if trivial_dropck_outlives(tcx, ty) {
267 return;
268 }
269
270 match ty.kind() {
271 ty::Bool
272 | ty::Char
273 | ty::Int(_)
274 | ty::Uint(_)
275 | ty::Float(_)
276 | ty::Str
277 | ty::Never
278 | ty::Foreign(..)
279 | ty::RawPtr(..)
280 | ty::Ref(..)
281 | ty::FnDef(..)
282 | ty::FnPtr(..)
283 | ty::CoroutineWitness(..) => {
284 // these types never have a destructor
285 }
286
287 ty::Pat(ety, _) | ty::Array(ety, _) | ty::Slice(ety) => {
288 // single-element containers, behave like their element
289 rustc_data_structures::stack::ensure_sufficient_stack(|| {
290 dtorck_constraint_for_ty_inner(tcx, typing_env, span, depth + 1, *ety, constraints)
291 });
292 }
293
294 ty::Tuple(tys) => rustc_data_structures::stack::ensure_sufficient_stack(|| {
295 for ty in tys.iter() {
296 dtorck_constraint_for_ty_inner(tcx, typing_env, span, depth + 1, ty, constraints);
297 }
298 }),
299
300 ty::Closure(_, args) => rustc_data_structures::stack::ensure_sufficient_stack(|| {
301 for ty in args.as_closure().upvar_tys() {
302 dtorck_constraint_for_ty_inner(tcx, typing_env, span, depth + 1, ty, constraints);
303 }
304 }),
305
306 ty::CoroutineClosure(_, args) => {
307 rustc_data_structures::stack::ensure_sufficient_stack(|| {
308 for ty in args.as_coroutine_closure().upvar_tys() {
309 dtorck_constraint_for_ty_inner(
310 tcx,
311 typing_env,
312 span,
313 depth + 1,
314 ty,
315 constraints,
316 );
317 }
318 })
319 }
320
321 ty::Coroutine(def_id, args) => {
322 // rust-lang/rust#49918: Locals can be stored across await points in the coroutine,
323 // called interior/witness types. Since we do not compute these witnesses until after
324 // building MIR, we consider all coroutines to unconditionally require a drop during
325 // MIR building. However, considering the coroutine to unconditionally require a drop
326 // here may unnecessarily require its upvars' regions to be live when they don't need
327 // to be, leading to borrowck errors: <https://github.com/rust-lang/rust/issues/116242>.
328 //
329 // Here, we implement a more precise approximation for the coroutine's dtorck constraint
330 // by considering whether any of the interior types needs drop. Note that this is still
331 // an approximation because the coroutine interior has its regions erased, so we must add
332 // *all* of the upvars to live types set if we find that *any* interior type needs drop.
333 // This is because any of the regions captured in the upvars may be stored in the interior,
334 // which then has its regions replaced by a binder (conceptually erasing the regions),
335 // so there's no way to enforce that the precise region in the interior type is live
336 // since we've lost that information by this point.
337 //
338 // Note also that this check requires that the coroutine's upvars are use-live, since
339 // a region from a type that does not have a destructor that was captured in an upvar
340 // may flow into an interior type with a destructor. This is stronger than requiring
341 // the upvars are drop-live.
342 //
343 // For example, if we capture two upvar references `&'1 (), &'2 ()` and have some type
344 // in the interior, `for<'r> { NeedsDrop<'r> }`, we have no way to tell whether the
345 // region `'r` came from the `'1` or `'2` region, so we require both are live. This
346 // could even be unnecessary if `'r` was actually a `'static` region or some region
347 // local to the coroutine! That's why it's an approximation.
348 let args = args.as_coroutine();
349
350 // Note that we don't care about whether the resume type has any drops since this is
351 // redundant; there is no storage for the resume type, so if it is actually stored
352 // in the interior, we'll already detect the need for a drop by checking the interior.
353 //
354 // FIXME(@lcnr): Why do we erase regions in the env here? Seems odd
355 let typing_env = tcx.erase_and_anonymize_regions(typing_env);
356 let needs_drop = tcx.mir_coroutine_witnesses(def_id).is_some_and(|witness| {
357 witness.field_tys.iter().any(|field| field.ty.needs_drop(tcx, typing_env))
358 });
359 if needs_drop {
360 // Pushing types directly to `constraints.outlives` is equivalent
361 // to requiring them to be use-live, since if we were instead to
362 // recurse on them like we do below, we only end up collecting the
363 // types that are relevant for drop-liveness.
364 constraints.outlives.extend(args.upvar_tys().iter().map(ty::GenericArg::from));
365 constraints.outlives.push(args.resume_ty().into());
366 } else {
367 // Even if a witness type doesn't need a drop, we still require that
368 // the upvars are drop-live. This is only needed if we aren't already
369 // counting *all* of the upvars as use-live above, since use-liveness
370 // is a *stronger requirement* than drop-liveness. Recursing here
371 // unconditionally would just be collecting duplicated types for no
372 // reason.
373 for ty in args.upvar_tys() {
374 dtorck_constraint_for_ty_inner(
375 tcx,
376 typing_env,
377 span,
378 depth + 1,
379 ty,
380 constraints,
381 );
382 }
383 }
384 }
385
386 ty::Adt(def, args) => {
387 let DropckConstraint { dtorck_types, outlives, overflows } =
388 tcx.at(span).adt_dtorck_constraint(def.did());
389 // FIXME: we can try to recursively `dtorck_constraint_on_ty`
390 // there, but that needs some way to handle cycles.
391 constraints
392 .dtorck_types
393 .extend(dtorck_types.iter().map(|t| EarlyBinder::bind(*t).instantiate(tcx, args)));
394 constraints
395 .outlives
396 .extend(outlives.iter().map(|t| EarlyBinder::bind(*t).instantiate(tcx, args)));
397 constraints
398 .overflows
399 .extend(overflows.iter().map(|t| EarlyBinder::bind(*t).instantiate(tcx, args)));
400 }
401
402 // Objects must be alive in order for their destructor
403 // to be called.
404 ty::Dynamic(..) => {
405 constraints.outlives.push(ty.into());
406 }
407
408 // Types that can't be resolved. Pass them forward.
409 ty::Alias(..) | ty::Param(..) => {
410 constraints.dtorck_types.push(ty);
411 }
412
413 // Can't instantiate binder here.
414 ty::UnsafeBinder(_) => {
415 constraints.dtorck_types.push(ty);
416 }
417
418 ty::Placeholder(..) | ty::Bound(..) | ty::Infer(..) | ty::Error(_) => {
419 // By the time this code runs, all type variables ought to
420 // be fully resolved.
421 tcx.dcx().span_delayed_bug(span, format!("Unresolved type in dropck: {:?}.", ty));
422 }
423 }
424}