]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_typeck/src/check/generator_interior.rs
Auto merge of #88865 - guswynn:must_not_suspend, r=oli-obk
[rust.git] / compiler / rustc_typeck / src / check / generator_interior.rs
1 //! This calculates the types which has storage which lives across a suspension point in a
2 //! generator from the perspective of typeck. The actual types used at runtime
3 //! is calculated in `rustc_const_eval::transform::generator` and may be a subset of the
4 //! types computed here.
5
6 use super::FnCtxt;
7 use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
8 use rustc_errors::pluralize;
9 use rustc_hir as hir;
10 use rustc_hir::def::{CtorKind, DefKind, Res};
11 use rustc_hir::def_id::DefId;
12 use rustc_hir::hir_id::HirIdSet;
13 use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
14 use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, Pat, PatKind};
15 use rustc_middle::middle::region::{self, YieldData};
16 use rustc_middle::ty::{self, Ty, TyCtxt};
17 use rustc_span::symbol::sym;
18 use rustc_span::Span;
19 use smallvec::SmallVec;
20 use tracing::debug;
21
22 struct InteriorVisitor<'a, 'tcx> {
23     fcx: &'a FnCtxt<'a, 'tcx>,
24     types: FxIndexSet<ty::GeneratorInteriorTypeCause<'tcx>>,
25     region_scope_tree: &'tcx region::ScopeTree,
26     expr_count: usize,
27     kind: hir::GeneratorKind,
28     prev_unresolved_span: Option<Span>,
29     /// Match arm guards have temporary borrows from the pattern bindings.
30     /// In case there is a yield point in a guard with a reference to such bindings,
31     /// such borrows can span across this yield point.
32     /// As such, we need to track these borrows and record them despite of the fact
33     /// that they may succeed the said yield point in the post-order.
34     guard_bindings: SmallVec<[SmallVec<[HirId; 4]>; 1]>,
35     guard_bindings_set: HirIdSet,
36     linted_values: HirIdSet,
37 }
38
39 impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> {
40     fn record(
41         &mut self,
42         ty: Ty<'tcx>,
43         hir_id: HirId,
44         scope: Option<region::Scope>,
45         expr: Option<&'tcx Expr<'tcx>>,
46         source_span: Span,
47         guard_borrowing_from_pattern: bool,
48     ) {
49         use rustc_span::DUMMY_SP;
50
51         debug!(
52             "generator_interior: attempting to record type {:?} {:?} {:?} {:?}",
53             ty, scope, expr, source_span
54         );
55
56         let live_across_yield = scope
57             .map(|s| {
58                 self.region_scope_tree.yield_in_scope(s).and_then(|yield_data| {
59                     // If we are recording an expression that is the last yield
60                     // in the scope, or that has a postorder CFG index larger
61                     // than the one of all of the yields, then its value can't
62                     // be storage-live (and therefore live) at any of the yields.
63                     //
64                     // See the mega-comment at `yield_in_scope` for a proof.
65
66                     debug!(
67                         "comparing counts yield: {} self: {}, source_span = {:?}",
68                         yield_data.expr_and_pat_count, self.expr_count, source_span
69                     );
70
71                     // If it is a borrowing happening in the guard,
72                     // it needs to be recorded regardless because they
73                     // do live across this yield point.
74                     if guard_borrowing_from_pattern
75                         || yield_data.expr_and_pat_count >= self.expr_count
76                     {
77                         Some(yield_data)
78                     } else {
79                         None
80                     }
81                 })
82             })
83             .unwrap_or_else(|| {
84                 Some(YieldData { span: DUMMY_SP, expr_and_pat_count: 0, source: self.kind.into() })
85             });
86
87         if let Some(yield_data) = live_across_yield {
88             let ty = self.fcx.resolve_vars_if_possible(ty);
89             debug!(
90                 "type in expr = {:?}, scope = {:?}, type = {:?}, count = {}, yield_span = {:?}",
91                 expr, scope, ty, self.expr_count, yield_data.span
92             );
93
94             if let Some((unresolved_type, unresolved_type_span)) =
95                 self.fcx.unresolved_type_vars(&ty)
96             {
97                 // If unresolved type isn't a ty_var then unresolved_type_span is None
98                 let span = self
99                     .prev_unresolved_span
100                     .unwrap_or_else(|| unresolved_type_span.unwrap_or(source_span));
101
102                 // If we encounter an int/float variable, then inference fallback didn't
103                 // finish due to some other error. Don't emit spurious additional errors.
104                 if let ty::Infer(ty::InferTy::IntVar(_) | ty::InferTy::FloatVar(_)) =
105                     unresolved_type.kind()
106                 {
107                     self.fcx
108                         .tcx
109                         .sess
110                         .delay_span_bug(span, &format!("Encountered var {:?}", unresolved_type));
111                 } else {
112                     let note = format!(
113                         "the type is part of the {} because of this {}",
114                         self.kind, yield_data.source
115                     );
116
117                     self.fcx
118                         .need_type_info_err_in_generator(self.kind, span, unresolved_type)
119                         .span_note(yield_data.span, &*note)
120                         .emit();
121                 }
122             } else {
123                 // Insert the type into the ordered set.
124                 let scope_span = scope.map(|s| s.span(self.fcx.tcx, self.region_scope_tree));
125
126                 if !self.linted_values.contains(&hir_id) {
127                     check_must_not_suspend_ty(
128                         self.fcx,
129                         ty,
130                         hir_id,
131                         SuspendCheckData {
132                             expr,
133                             source_span,
134                             yield_span: yield_data.span,
135                             plural_len: 1,
136                             ..Default::default()
137                         },
138                     );
139                     self.linted_values.insert(hir_id);
140                 }
141
142                 self.types.insert(ty::GeneratorInteriorTypeCause {
143                     span: source_span,
144                     ty: &ty,
145                     scope_span,
146                     yield_span: yield_data.span,
147                     expr: expr.map(|e| e.hir_id),
148                 });
149             }
150         } else {
151             debug!(
152                 "no type in expr = {:?}, count = {:?}, span = {:?}",
153                 expr,
154                 self.expr_count,
155                 expr.map(|e| e.span)
156             );
157             let ty = self.fcx.resolve_vars_if_possible(ty);
158             if let Some((unresolved_type, unresolved_type_span)) =
159                 self.fcx.unresolved_type_vars(&ty)
160             {
161                 debug!(
162                     "remained unresolved_type = {:?}, unresolved_type_span: {:?}",
163                     unresolved_type, unresolved_type_span
164                 );
165                 self.prev_unresolved_span = unresolved_type_span;
166             }
167         }
168     }
169 }
170
171 pub fn resolve_interior<'a, 'tcx>(
172     fcx: &'a FnCtxt<'a, 'tcx>,
173     def_id: DefId,
174     body_id: hir::BodyId,
175     interior: Ty<'tcx>,
176     kind: hir::GeneratorKind,
177 ) {
178     let body = fcx.tcx.hir().body(body_id);
179     let mut visitor = InteriorVisitor {
180         fcx,
181         types: FxIndexSet::default(),
182         region_scope_tree: fcx.tcx.region_scope_tree(def_id),
183         expr_count: 0,
184         kind,
185         prev_unresolved_span: None,
186         guard_bindings: <_>::default(),
187         guard_bindings_set: <_>::default(),
188         linted_values: <_>::default(),
189     };
190     intravisit::walk_body(&mut visitor, body);
191
192     // Check that we visited the same amount of expressions and the RegionResolutionVisitor
193     let region_expr_count = visitor.region_scope_tree.body_expr_count(body_id).unwrap();
194     assert_eq!(region_expr_count, visitor.expr_count);
195
196     // The types are already kept in insertion order.
197     let types = visitor.types;
198
199     // The types in the generator interior contain lifetimes local to the generator itself,
200     // which should not be exposed outside of the generator. Therefore, we replace these
201     // lifetimes with existentially-bound lifetimes, which reflect the exact value of the
202     // lifetimes not being known by users.
203     //
204     // These lifetimes are used in auto trait impl checking (for example,
205     // if a Sync generator contains an &'α T, we need to check whether &'α T: Sync),
206     // so knowledge of the exact relationships between them isn't particularly important.
207
208     debug!("types in generator {:?}, span = {:?}", types, body.value.span);
209
210     let mut counter = 0;
211     let mut captured_tys = FxHashSet::default();
212     let type_causes: Vec<_> = types
213         .into_iter()
214         .filter_map(|mut cause| {
215             // Erase regions and canonicalize late-bound regions to deduplicate as many types as we
216             // can.
217             let erased = fcx.tcx.erase_regions(cause.ty);
218             if captured_tys.insert(erased) {
219                 // Replace all regions inside the generator interior with late bound regions.
220                 // Note that each region slot in the types gets a new fresh late bound region,
221                 // which means that none of the regions inside relate to any other, even if
222                 // typeck had previously found constraints that would cause them to be related.
223                 let folded = fcx.tcx.fold_regions(erased, &mut false, |_, current_depth| {
224                     let br = ty::BoundRegion {
225                         var: ty::BoundVar::from_u32(counter),
226                         kind: ty::BrAnon(counter),
227                     };
228                     let r = fcx.tcx.mk_region(ty::ReLateBound(current_depth, br));
229                     counter += 1;
230                     r
231                 });
232
233                 cause.ty = folded;
234                 Some(cause)
235             } else {
236                 None
237             }
238         })
239         .collect();
240
241     // Extract type components to build the witness type.
242     let type_list = fcx.tcx.mk_type_list(type_causes.iter().map(|cause| cause.ty));
243     let bound_vars = fcx.tcx.mk_bound_variable_kinds(
244         (0..counter).map(|i| ty::BoundVariableKind::Region(ty::BrAnon(i))),
245     );
246     let witness =
247         fcx.tcx.mk_generator_witness(ty::Binder::bind_with_vars(type_list, bound_vars.clone()));
248
249     // Store the generator types and spans into the typeck results for this generator.
250     visitor.fcx.inh.typeck_results.borrow_mut().generator_interior_types =
251         ty::Binder::bind_with_vars(type_causes, bound_vars);
252
253     debug!(
254         "types in generator after region replacement {:?}, span = {:?}",
255         witness, body.value.span
256     );
257
258     // Unify the type variable inside the generator with the new witness
259     match fcx.at(&fcx.misc(body.value.span), fcx.param_env).eq(interior, witness) {
260         Ok(ok) => fcx.register_infer_ok_obligations(ok),
261         _ => bug!(),
262     }
263 }
264
265 // This visitor has to have the same visit_expr calls as RegionResolutionVisitor in
266 // librustc_middle/middle/region.rs since `expr_count` is compared against the results
267 // there.
268 impl<'a, 'tcx> Visitor<'tcx> for InteriorVisitor<'a, 'tcx> {
269     type Map = intravisit::ErasedMap<'tcx>;
270
271     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
272         NestedVisitorMap::None
273     }
274
275     fn visit_arm(&mut self, arm: &'tcx Arm<'tcx>) {
276         let Arm { guard, pat, body, .. } = arm;
277         self.visit_pat(pat);
278         if let Some(ref g) = guard {
279             self.guard_bindings.push(<_>::default());
280             ArmPatCollector {
281                 guard_bindings_set: &mut self.guard_bindings_set,
282                 guard_bindings: self
283                     .guard_bindings
284                     .last_mut()
285                     .expect("should have pushed at least one earlier"),
286             }
287             .visit_pat(pat);
288
289             match g {
290                 Guard::If(ref e) => {
291                     self.visit_expr(e);
292                 }
293                 Guard::IfLet(ref pat, ref e) => {
294                     self.visit_pat(pat);
295                     self.visit_expr(e);
296                 }
297             }
298
299             let mut scope_var_ids =
300                 self.guard_bindings.pop().expect("should have pushed at least one earlier");
301             for var_id in scope_var_ids.drain(..) {
302                 self.guard_bindings_set.remove(&var_id);
303             }
304         }
305         self.visit_expr(body);
306     }
307
308     fn visit_pat(&mut self, pat: &'tcx Pat<'tcx>) {
309         intravisit::walk_pat(self, pat);
310
311         self.expr_count += 1;
312
313         if let PatKind::Binding(..) = pat.kind {
314             let scope = self.region_scope_tree.var_scope(pat.hir_id.local_id);
315             let ty = self.fcx.typeck_results.borrow().pat_ty(pat);
316             self.record(ty, pat.hir_id, Some(scope), None, pat.span, false);
317         }
318     }
319
320     fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
321         let mut guard_borrowing_from_pattern = false;
322         match &expr.kind {
323             ExprKind::Call(callee, args) => match &callee.kind {
324                 ExprKind::Path(qpath) => {
325                     let res = self.fcx.typeck_results.borrow().qpath_res(qpath, callee.hir_id);
326                     match res {
327                         // Direct calls never need to keep the callee `ty::FnDef`
328                         // ZST in a temporary, so skip its type, just in case it
329                         // can significantly complicate the generator type.
330                         Res::Def(
331                             DefKind::Fn | DefKind::AssocFn | DefKind::Ctor(_, CtorKind::Fn),
332                             _,
333                         ) => {
334                             // NOTE(eddyb) this assumes a path expression has
335                             // no nested expressions to keep track of.
336                             self.expr_count += 1;
337
338                             // Record the rest of the call expression normally.
339                             for arg in *args {
340                                 self.visit_expr(arg);
341                             }
342                         }
343                         _ => intravisit::walk_expr(self, expr),
344                     }
345                 }
346                 _ => intravisit::walk_expr(self, expr),
347             },
348             ExprKind::Path(qpath) => {
349                 intravisit::walk_expr(self, expr);
350                 let res = self.fcx.typeck_results.borrow().qpath_res(qpath, expr.hir_id);
351                 match res {
352                     Res::Local(id) if self.guard_bindings_set.contains(&id) => {
353                         guard_borrowing_from_pattern = true;
354                     }
355                     _ => {}
356                 }
357             }
358             _ => intravisit::walk_expr(self, expr),
359         }
360
361         self.expr_count += 1;
362
363         let scope = self.region_scope_tree.temporary_scope(expr.hir_id.local_id);
364
365         // If there are adjustments, then record the final type --
366         // this is the actual value that is being produced.
367         if let Some(adjusted_ty) = self.fcx.typeck_results.borrow().expr_ty_adjusted_opt(expr) {
368             self.record(
369                 adjusted_ty,
370                 expr.hir_id,
371                 scope,
372                 Some(expr),
373                 expr.span,
374                 guard_borrowing_from_pattern,
375             );
376         }
377
378         // Also record the unadjusted type (which is the only type if
379         // there are no adjustments). The reason for this is that the
380         // unadjusted value is sometimes a "temporary" that would wind
381         // up in a MIR temporary.
382         //
383         // As an example, consider an expression like `vec![].push(x)`.
384         // Here, the `vec![]` would wind up MIR stored into a
385         // temporary variable `t` which we can borrow to invoke
386         // `<Vec<_>>::push(&mut t, x)`.
387         //
388         // Note that an expression can have many adjustments, and we
389         // are just ignoring those intermediate types. This is because
390         // those intermediate values are always linearly "consumed" by
391         // the other adjustments, and hence would never be directly
392         // captured in the MIR.
393         //
394         // (Note that this partly relies on the fact that the `Deref`
395         // traits always return references, which means their content
396         // can be reborrowed without needing to spill to a temporary.
397         // If this were not the case, then we could conceivably have
398         // to create intermediate temporaries.)
399         //
400         // The type table might not have information for this expression
401         // if it is in a malformed scope. (#66387)
402         if let Some(ty) = self.fcx.typeck_results.borrow().expr_ty_opt(expr) {
403             if guard_borrowing_from_pattern {
404                 // Match guards create references to all the bindings in the pattern that are used
405                 // in the guard, e.g. `y if is_even(y) => ...` becomes `is_even(*r_y)` where `r_y`
406                 // is a reference to `y`, so we must record a reference to the type of the binding.
407                 let tcx = self.fcx.tcx;
408                 let ref_ty = tcx.mk_ref(
409                     // Use `ReErased` as `resolve_interior` is going to replace all the regions anyway.
410                     tcx.mk_region(ty::RegionKind::ReErased),
411                     ty::TypeAndMut { ty, mutbl: hir::Mutability::Not },
412                 );
413                 self.record(
414                     ref_ty,
415                     expr.hir_id,
416                     scope,
417                     Some(expr),
418                     expr.span,
419                     guard_borrowing_from_pattern,
420                 );
421             }
422             self.record(
423                 ty,
424                 expr.hir_id,
425                 scope,
426                 Some(expr),
427                 expr.span,
428                 guard_borrowing_from_pattern,
429             );
430         } else {
431             self.fcx.tcx.sess.delay_span_bug(expr.span, "no type for node");
432         }
433     }
434 }
435
436 struct ArmPatCollector<'a> {
437     guard_bindings_set: &'a mut HirIdSet,
438     guard_bindings: &'a mut SmallVec<[HirId; 4]>,
439 }
440
441 impl<'a, 'tcx> Visitor<'tcx> for ArmPatCollector<'a> {
442     type Map = intravisit::ErasedMap<'tcx>;
443
444     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
445         NestedVisitorMap::None
446     }
447
448     fn visit_pat(&mut self, pat: &'tcx Pat<'tcx>) {
449         intravisit::walk_pat(self, pat);
450         if let PatKind::Binding(_, id, ..) = pat.kind {
451             self.guard_bindings.push(id);
452             self.guard_bindings_set.insert(id);
453         }
454     }
455 }
456
457 #[derive(Default)]
458 pub struct SuspendCheckData<'a, 'tcx> {
459     expr: Option<&'tcx Expr<'tcx>>,
460     source_span: Span,
461     yield_span: Span,
462     descr_pre: &'a str,
463     descr_post: &'a str,
464     plural_len: usize,
465 }
466
467 // Returns whether it emitted a diagnostic or not
468 // Note that this fn and the proceding one are based on the code
469 // for creating must_use diagnostics
470 //
471 // Note that this technique was chosen over things like a `Suspend` marker trait
472 // as it is simpler and has precendent in the compiler
473 pub fn check_must_not_suspend_ty<'tcx>(
474     fcx: &FnCtxt<'_, 'tcx>,
475     ty: Ty<'tcx>,
476     hir_id: HirId,
477     data: SuspendCheckData<'_, 'tcx>,
478 ) -> bool {
479     if ty.is_unit()
480     // FIXME: should this check `is_ty_uninhabited_from`. This query is not available in this stage
481     // of typeck (before ReVar and RePlaceholder are removed), but may remove noise, like in
482     // `must_use`
483     // || fcx.tcx.is_ty_uninhabited_from(fcx.tcx.parent_module(hir_id).to_def_id(), ty, fcx.param_env)
484     {
485         return false;
486     }
487
488     let plural_suffix = pluralize!(data.plural_len);
489
490     match *ty.kind() {
491         ty::Adt(..) if ty.is_box() => {
492             let boxed_ty = ty.boxed_ty();
493             let descr_pre = &format!("{}boxed ", data.descr_pre);
494             check_must_not_suspend_ty(fcx, boxed_ty, hir_id, SuspendCheckData { descr_pre, ..data })
495         }
496         ty::Adt(def, _) => check_must_not_suspend_def(fcx.tcx, def.did, hir_id, data),
497         // FIXME: support adding the attribute to TAITs
498         ty::Opaque(def, _) => {
499             let mut has_emitted = false;
500             for &(predicate, _) in fcx.tcx.explicit_item_bounds(def) {
501                 // We only look at the `DefId`, so it is safe to skip the binder here.
502                 if let ty::PredicateKind::Trait(ref poly_trait_predicate) =
503                     predicate.kind().skip_binder()
504                 {
505                     let def_id = poly_trait_predicate.trait_ref.def_id;
506                     let descr_pre = &format!("{}implementer{} of ", data.descr_pre, plural_suffix);
507                     if check_must_not_suspend_def(
508                         fcx.tcx,
509                         def_id,
510                         hir_id,
511                         SuspendCheckData { descr_pre, ..data },
512                     ) {
513                         has_emitted = true;
514                         break;
515                     }
516                 }
517             }
518             has_emitted
519         }
520         ty::Dynamic(binder, _) => {
521             let mut has_emitted = false;
522             for predicate in binder.iter() {
523                 if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder() {
524                     let def_id = trait_ref.def_id;
525                     let descr_post = &format!(" trait object{}{}", plural_suffix, data.descr_post);
526                     if check_must_not_suspend_def(
527                         fcx.tcx,
528                         def_id,
529                         hir_id,
530                         SuspendCheckData { descr_post, ..data },
531                     ) {
532                         has_emitted = true;
533                         break;
534                     }
535                 }
536             }
537             has_emitted
538         }
539         ty::Tuple(ref tys) => {
540             let mut has_emitted = false;
541             let spans = if let Some(hir::ExprKind::Tup(comps)) = data.expr.map(|e| &e.kind) {
542                 debug_assert_eq!(comps.len(), tys.len());
543                 comps.iter().map(|e| e.span).collect()
544             } else {
545                 vec![]
546             };
547             for (i, ty) in tys.iter().map(|k| k.expect_ty()).enumerate() {
548                 let descr_post = &format!(" in tuple element {}", i);
549                 let span = *spans.get(i).unwrap_or(&data.source_span);
550                 if check_must_not_suspend_ty(
551                     fcx,
552                     ty,
553                     hir_id,
554                     SuspendCheckData { descr_post, source_span: span, ..data },
555                 ) {
556                     has_emitted = true;
557                 }
558             }
559             has_emitted
560         }
561         ty::Array(ty, len) => {
562             let descr_pre = &format!("{}array{} of ", data.descr_pre, plural_suffix);
563             check_must_not_suspend_ty(
564                 fcx,
565                 ty,
566                 hir_id,
567                 SuspendCheckData {
568                     descr_pre,
569                     plural_len: len.try_eval_usize(fcx.tcx, fcx.param_env).unwrap_or(0) as usize
570                         + 1,
571                     ..data
572                 },
573             )
574         }
575         _ => false,
576     }
577 }
578
579 fn check_must_not_suspend_def(
580     tcx: TyCtxt<'_>,
581     def_id: DefId,
582     hir_id: HirId,
583     data: SuspendCheckData<'_, '_>,
584 ) -> bool {
585     for attr in tcx.get_attrs(def_id).iter() {
586         if attr.has_name(sym::must_not_suspend) {
587             tcx.struct_span_lint_hir(
588                 rustc_session::lint::builtin::MUST_NOT_SUSPEND,
589                 hir_id,
590                 data.source_span,
591                 |lint| {
592                     let msg = format!(
593                         "{}`{}`{} held across a suspend point, but should not be",
594                         data.descr_pre,
595                         tcx.def_path_str(def_id),
596                         data.descr_post,
597                     );
598                     let mut err = lint.build(&msg);
599
600                     // add span pointing to the offending yield/await
601                     err.span_label(data.yield_span, "the value is held across this suspend point");
602
603                     // Add optional reason note
604                     if let Some(note) = attr.value_str() {
605                         // FIXME(guswynn): consider formatting this better
606                         err.span_note(data.source_span, &note.as_str());
607                     }
608
609                     // Add some quick suggestions on what to do
610                     // FIXME: can `drop` work as a suggestion here as well?
611                     err.span_help(
612                         data.source_span,
613                         "consider using a block (`{ ... }`) \
614                         to shrink the value's scope, ending before the suspend point",
615                     );
616
617                     err.emit();
618                 },
619             );
620
621             return true;
622         }
623     }
624     false
625 }