rustc_middle/ty/closure.rs
1use std::fmt::Write;
2
3use rustc_data_structures::fx::FxIndexMap;
4use rustc_hir as hir;
5use rustc_hir::HirId;
6use rustc_hir::def_id::LocalDefId;
7use rustc_macros::{HashStable, TyDecodable, TyEncodable, TypeFoldable, TypeVisitable};
8use rustc_span::def_id::LocalDefIdMap;
9use rustc_span::{Ident, Span, Symbol};
10
11use super::TyCtxt;
12use crate::hir::place::{
13 Place as HirPlace, PlaceBase as HirPlaceBase, ProjectionKind as HirProjectionKind,
14};
15use crate::query::Providers;
16use crate::{mir, ty};
17
18/// Captures are represented using fields inside a structure.
19/// This represents accessing self in the closure structure
20pub const CAPTURE_STRUCT_LOCAL: mir::Local = mir::Local::from_u32(1);
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, TyEncodable, TyDecodable, HashStable)]
23#[derive(TypeFoldable, TypeVisitable)]
24pub struct UpvarPath {
25 pub hir_id: HirId,
26}
27
28/// Upvars do not get their own `NodeId`. Instead, we use the pair of
29/// the original var ID (that is, the root variable that is referenced
30/// by the upvar) and the ID of the closure expression.
31#[derive(Clone, Copy, PartialEq, Eq, Hash, TyEncodable, TyDecodable, HashStable)]
32#[derive(TypeFoldable, TypeVisitable)]
33pub struct UpvarId {
34 pub var_path: UpvarPath,
35 pub closure_expr_id: LocalDefId,
36}
37
38impl UpvarId {
39 pub fn new(var_hir_id: HirId, closure_def_id: LocalDefId) -> UpvarId {
40 UpvarId { var_path: UpvarPath { hir_id: var_hir_id }, closure_expr_id: closure_def_id }
41 }
42}
43
44/// Information describing the capture of an upvar. This is computed
45/// during `typeck`, specifically by `regionck`.
46#[derive(Eq, PartialEq, Clone, Debug, Copy, TyEncodable, TyDecodable, HashStable, Hash)]
47#[derive(TypeFoldable, TypeVisitable)]
48pub enum UpvarCapture {
49 /// Upvar is captured by value. This is always true when the
50 /// closure is labeled `move`, but can also be true in other cases
51 /// depending on inference.
52 ByValue,
53
54 /// Upvar is captured by use. This is true when the closure is labeled `use`.
55 ByUse,
56
57 /// Upvar is captured by reference.
58 ByRef(BorrowKind),
59}
60
61/// Given the closure DefId this map provides a map of root variables to minimum
62/// set of `CapturedPlace`s that need to be tracked to support all captures of that closure.
63pub type MinCaptureInformationMap<'tcx> = LocalDefIdMap<RootVariableMinCaptureList<'tcx>>;
64
65/// Part of `MinCaptureInformationMap`; Maps a root variable to the list of `CapturedPlace`.
66/// Used to track the minimum set of `Place`s that need to be captured to support all
67/// Places captured by the closure starting at a given root variable.
68///
69/// This provides a convenient and quick way of checking if a variable being used within
70/// a closure is a capture of a local variable.
71pub type RootVariableMinCaptureList<'tcx> = FxIndexMap<HirId, MinCaptureList<'tcx>>;
72
73/// Part of `MinCaptureInformationMap`; List of `CapturePlace`s.
74pub type MinCaptureList<'tcx> = Vec<CapturedPlace<'tcx>>;
75
76/// A composite describing a `Place` that is captured by a closure.
77#[derive(Eq, PartialEq, Clone, Debug, TyEncodable, TyDecodable, HashStable, Hash)]
78#[derive(TypeFoldable, TypeVisitable)]
79pub struct CapturedPlace<'tcx> {
80 /// Name and span where the binding happens.
81 pub var_ident: Ident,
82
83 /// The `Place` that is captured.
84 pub place: HirPlace<'tcx>,
85
86 /// `CaptureKind` and expression(s) that resulted in such capture of `place`.
87 pub info: CaptureInfo,
88
89 /// Represents if `place` can be mutated or not.
90 pub mutability: hir::Mutability,
91}
92
93impl<'tcx> CapturedPlace<'tcx> {
94 pub fn to_string(&self, tcx: TyCtxt<'tcx>) -> String {
95 place_to_string_for_capture(tcx, &self.place)
96 }
97
98 /// Returns a symbol of the captured upvar, which looks like `name__field1__field2`.
99 pub fn to_symbol(&self) -> Symbol {
100 let mut symbol = self.var_ident.to_string();
101
102 let mut ty = self.place.base_ty;
103 for proj in self.place.projections.iter() {
104 match proj.kind {
105 HirProjectionKind::Field(idx, variant) => match ty.kind() {
106 ty::Tuple(_) => write!(&mut symbol, "__{}", idx.index()).unwrap(),
107 ty::Adt(def, ..) => {
108 write!(
109 &mut symbol,
110 "__{}",
111 def.variant(variant).fields[idx].name.as_str(),
112 )
113 .unwrap();
114 }
115 ty => {
116 bug!("Unexpected type {:?} for `Field` projection", ty)
117 }
118 },
119
120 HirProjectionKind::UnwrapUnsafeBinder => {
121 write!(&mut symbol, "__unwrap").unwrap();
122 }
123
124 // Ignore derefs for now, as they are likely caused by
125 // autoderefs that don't appear in the original code.
126 HirProjectionKind::Deref => {}
127 // Just change the type to the hidden type, so we can actually project.
128 HirProjectionKind::OpaqueCast => {}
129 proj => bug!("Unexpected projection {:?} in captured place", proj),
130 }
131 ty = proj.ty;
132 }
133
134 Symbol::intern(&symbol)
135 }
136
137 /// Returns the hir-id of the root variable for the captured place.
138 /// e.g., if `a.b.c` was captured, would return the hir-id for `a`.
139 pub fn get_root_variable(&self) -> HirId {
140 match self.place.base {
141 HirPlaceBase::Upvar(upvar_id) => upvar_id.var_path.hir_id,
142 base => bug!("Expected upvar, found={:?}", base),
143 }
144 }
145
146 /// Returns the `LocalDefId` of the closure that captured this Place
147 pub fn get_closure_local_def_id(&self) -> LocalDefId {
148 match self.place.base {
149 HirPlaceBase::Upvar(upvar_id) => upvar_id.closure_expr_id,
150 base => bug!("expected upvar, found={:?}", base),
151 }
152 }
153
154 /// Return span pointing to use that resulted in selecting the captured path
155 pub fn get_path_span(&self, tcx: TyCtxt<'tcx>) -> Span {
156 if let Some(path_expr_id) = self.info.path_expr_id {
157 tcx.hir_span(path_expr_id)
158 } else if let Some(capture_kind_expr_id) = self.info.capture_kind_expr_id {
159 tcx.hir_span(capture_kind_expr_id)
160 } else {
161 // Fallback on upvars mentioned if neither path or capture expr id is captured
162
163 // Safe to unwrap since we know this place is captured by the closure, therefore the closure must have upvars.
164 tcx.upvars_mentioned(self.get_closure_local_def_id()).unwrap()
165 [&self.get_root_variable()]
166 .span
167 }
168 }
169
170 /// Return span pointing to use that resulted in selecting the current capture kind
171 pub fn get_capture_kind_span(&self, tcx: TyCtxt<'tcx>) -> Span {
172 if let Some(capture_kind_expr_id) = self.info.capture_kind_expr_id {
173 tcx.hir_span(capture_kind_expr_id)
174 } else if let Some(path_expr_id) = self.info.path_expr_id {
175 tcx.hir_span(path_expr_id)
176 } else {
177 // Fallback on upvars mentioned if neither path or capture expr id is captured
178
179 // Safe to unwrap since we know this place is captured by the closure, therefore the closure must have upvars.
180 tcx.upvars_mentioned(self.get_closure_local_def_id()).unwrap()
181 [&self.get_root_variable()]
182 .span
183 }
184 }
185
186 pub fn is_by_ref(&self) -> bool {
187 match self.info.capture_kind {
188 ty::UpvarCapture::ByValue | ty::UpvarCapture::ByUse => false,
189 ty::UpvarCapture::ByRef(..) => true,
190 }
191 }
192}
193
194#[derive(Copy, Clone, Debug, HashStable)]
195pub struct ClosureTypeInfo<'tcx> {
196 user_provided_sig: ty::CanonicalPolyFnSig<'tcx>,
197 captures: &'tcx ty::List<&'tcx ty::CapturedPlace<'tcx>>,
198 kind_origin: Option<&'tcx (Span, HirPlace<'tcx>)>,
199}
200
201fn closure_typeinfo<'tcx>(tcx: TyCtxt<'tcx>, def: LocalDefId) -> ClosureTypeInfo<'tcx> {
202 debug_assert!(tcx.is_closure_like(def.to_def_id()));
203 let typeck_results = tcx.typeck(def);
204 let user_provided_sig = typeck_results.user_provided_sigs[&def];
205 let captures = typeck_results.closure_min_captures_flattened(def);
206 let captures = tcx.mk_captures_from_iter(captures);
207 let hir_id = tcx.local_def_id_to_hir_id(def);
208 let kind_origin = typeck_results.closure_kind_origins().get(hir_id);
209 ClosureTypeInfo { user_provided_sig, captures, kind_origin }
210}
211
212impl<'tcx> TyCtxt<'tcx> {
213 pub fn closure_kind_origin(self, def_id: LocalDefId) -> Option<&'tcx (Span, HirPlace<'tcx>)> {
214 self.closure_typeinfo(def_id).kind_origin
215 }
216
217 pub fn closure_user_provided_sig(self, def_id: LocalDefId) -> ty::CanonicalPolyFnSig<'tcx> {
218 self.closure_typeinfo(def_id).user_provided_sig
219 }
220
221 pub fn closure_captures(self, def_id: LocalDefId) -> &'tcx [&'tcx ty::CapturedPlace<'tcx>] {
222 if !self.is_closure_like(def_id.to_def_id()) {
223 return &[];
224 }
225 self.closure_typeinfo(def_id).captures
226 }
227}
228
229/// Return true if the `proj_possible_ancestor` represents an ancestor path
230/// to `proj_capture` or `proj_possible_ancestor` is same as `proj_capture`,
231/// assuming they both start off of the same root variable.
232///
233/// **Note:** It's the caller's responsibility to ensure that both lists of projections
234/// start off of the same root variable.
235///
236/// Eg: 1. `foo.x` which is represented using `projections=[Field(x)]` is an ancestor of
237/// `foo.x.y` which is represented using `projections=[Field(x), Field(y)]`.
238/// Note both `foo.x` and `foo.x.y` start off of the same root variable `foo`.
239/// 2. Since we only look at the projections here function will return `bar.x` as a valid
240/// ancestor of `foo.x.y`. It's the caller's responsibility to ensure that both projections
241/// list are being applied to the same root variable.
242pub fn is_ancestor_or_same_capture(
243 proj_possible_ancestor: &[HirProjectionKind],
244 proj_capture: &[HirProjectionKind],
245) -> bool {
246 // We want to make sure `is_ancestor_or_same_capture("x.0.0", "x.0")` to return false.
247 // Therefore we can't just check if all projections are same in the zipped iterator below.
248 if proj_possible_ancestor.len() > proj_capture.len() {
249 return false;
250 }
251
252 proj_possible_ancestor.iter().zip(proj_capture).all(|(a, b)| a == b)
253}
254
255/// Part of `MinCaptureInformationMap`; describes the capture kind (&, &mut, move)
256/// for a particular capture as well as identifying the part of the source code
257/// that triggered this capture to occur.
258#[derive(Eq, PartialEq, Clone, Debug, Copy, TyEncodable, TyDecodable, HashStable, Hash)]
259#[derive(TypeFoldable, TypeVisitable)]
260pub struct CaptureInfo {
261 /// Expr Id pointing to use that resulted in selecting the current capture kind
262 ///
263 /// Eg:
264 /// ```rust,no_run
265 /// let mut t = (0,1);
266 ///
267 /// let c = || {
268 /// println!("{t:?}"); // L1
269 /// t.1 = 4; // L2
270 /// };
271 /// ```
272 /// `capture_kind_expr_id` will point to the use on L2 and `path_expr_id` will point to the
273 /// use on L1.
274 ///
275 /// If the user doesn't enable feature `capture_disjoint_fields` (RFC 2229) then, it is
276 /// possible that we don't see the use of a particular place resulting in capture_kind_expr_id being
277 /// None. In such case we fallback on uvpars_mentioned for span.
278 ///
279 /// Eg:
280 /// ```rust,no_run
281 /// let x = 5;
282 ///
283 /// let c = || {
284 /// let _ = x;
285 /// };
286 /// ```
287 ///
288 /// In this example, if `capture_disjoint_fields` is **not** set, then x will be captured,
289 /// but we won't see it being used during capture analysis, since it's essentially a discard.
290 pub capture_kind_expr_id: Option<HirId>,
291 /// Expr Id pointing to use that resulted the corresponding place being captured
292 ///
293 /// See `capture_kind_expr_id` for example.
294 ///
295 pub path_expr_id: Option<HirId>,
296
297 /// Capture mode that was selected
298 pub capture_kind: UpvarCapture,
299}
300
301pub fn place_to_string_for_capture<'tcx>(tcx: TyCtxt<'tcx>, place: &HirPlace<'tcx>) -> String {
302 let mut curr_string: String = match place.base {
303 HirPlaceBase::Upvar(upvar_id) => tcx.hir_name(upvar_id.var_path.hir_id).to_string(),
304 _ => bug!("Capture_information should only contain upvars"),
305 };
306
307 for (i, proj) in place.projections.iter().enumerate() {
308 match proj.kind {
309 HirProjectionKind::Deref => {
310 curr_string = format!("*{curr_string}");
311 }
312 HirProjectionKind::Field(idx, variant) => match place.ty_before_projection(i).kind() {
313 ty::Adt(def, ..) => {
314 curr_string = format!(
315 "{}.{}",
316 curr_string,
317 def.variant(variant).fields[idx].name.as_str()
318 );
319 }
320 ty::Tuple(_) => {
321 curr_string = format!("{}.{}", curr_string, idx.index());
322 }
323 _ => {
324 bug!(
325 "Field projection applied to a type other than Adt or Tuple: {:?}.",
326 place.ty_before_projection(i).kind()
327 )
328 }
329 },
330 proj => bug!("{:?} unexpected because it isn't captured", proj),
331 }
332 }
333
334 curr_string
335}
336
337#[derive(Eq, Clone, PartialEq, Debug, TyEncodable, TyDecodable, Copy, HashStable, Hash)]
338#[derive(TypeFoldable, TypeVisitable)]
339pub enum BorrowKind {
340 /// Data must be immutable and is aliasable.
341 Immutable,
342
343 /// Data must be immutable but not aliasable. This kind of borrow
344 /// cannot currently be expressed by the user and is used only in
345 /// implicit closure bindings. It is needed when the closure
346 /// is borrowing or mutating a mutable referent, e.g.:
347 ///
348 /// ```
349 /// let mut z = 3;
350 /// let x: &mut isize = &mut z;
351 /// let y = || *x += 5;
352 /// ```
353 ///
354 /// If we were to try to translate this closure into a more explicit
355 /// form, we'd encounter an error with the code as written:
356 ///
357 /// ```compile_fail,E0594
358 /// struct Env<'a> { x: &'a &'a mut isize }
359 /// let mut z = 3;
360 /// let x: &mut isize = &mut z;
361 /// let y = (&mut Env { x: &x }, fn_ptr); // Closure is pair of env and fn
362 /// fn fn_ptr(env: &mut Env) { **env.x += 5; }
363 /// ```
364 ///
365 /// This is then illegal because you cannot mutate a `&mut` found
366 /// in an aliasable location. To solve, you'd have to translate with
367 /// an `&mut` borrow:
368 ///
369 /// ```compile_fail,E0596
370 /// struct Env<'a> { x: &'a mut &'a mut isize }
371 /// let mut z = 3;
372 /// let x: &mut isize = &mut z;
373 /// let y = (&mut Env { x: &mut x }, fn_ptr); // changed from &x to &mut x
374 /// fn fn_ptr(env: &mut Env) { **env.x += 5; }
375 /// ```
376 ///
377 /// Now the assignment to `**env.x` is legal, but creating a
378 /// mutable pointer to `x` is not because `x` is not mutable. We
379 /// could fix this by declaring `x` as `let mut x`. This is ok in
380 /// user code, if awkward, but extra weird for closures, since the
381 /// borrow is hidden.
382 ///
383 /// So we introduce a "unique imm" borrow -- the referent is
384 /// immutable, but not aliasable. This solves the problem. For
385 /// simplicity, we don't give users the way to express this
386 /// borrow, it's just used when translating closures.
387 ///
388 /// FIXME: Rename this to indicate the borrow is actually not immutable.
389 UniqueImmutable,
390
391 /// Data is mutable and not aliasable.
392 Mutable,
393}
394
395impl BorrowKind {
396 pub fn from_mutbl(m: hir::Mutability) -> BorrowKind {
397 match m {
398 hir::Mutability::Mut => BorrowKind::Mutable,
399 hir::Mutability::Not => BorrowKind::Immutable,
400 }
401 }
402
403 /// Returns a mutability `m` such that an `&m T` pointer could be used to obtain this borrow
404 /// kind. Because borrow kinds are richer than mutabilities, we sometimes have to pick a
405 /// mutability that is stronger than necessary so that it at least *would permit* the borrow in
406 /// question.
407 pub fn to_mutbl_lossy(self) -> hir::Mutability {
408 match self {
409 BorrowKind::Mutable => hir::Mutability::Mut,
410 BorrowKind::Immutable => hir::Mutability::Not,
411
412 // We have no type corresponding to a unique imm borrow, so
413 // use `&mut`. It gives all the capabilities of a `&uniq`
414 // and hence is a safe "over approximation".
415 BorrowKind::UniqueImmutable => hir::Mutability::Mut,
416 }
417 }
418}
419
420pub fn analyze_coroutine_closure_captures<'a, 'tcx: 'a, T>(
421 parent_captures: impl IntoIterator<Item = &'a CapturedPlace<'tcx>>,
422 child_captures: impl IntoIterator<Item = &'a CapturedPlace<'tcx>>,
423 mut for_each: impl FnMut((usize, &'a CapturedPlace<'tcx>), (usize, &'a CapturedPlace<'tcx>)) -> T,
424) -> impl Iterator<Item = T> {
425 std::iter::from_coroutine(
426 #[coroutine]
427 move || {
428 let mut child_captures = child_captures.into_iter().enumerate().peekable();
429
430 // One parent capture may correspond to several child captures if we end up
431 // refining the set of captures via edition-2021 precise captures. We want to
432 // match up any number of child captures with one parent capture, so we keep
433 // peeking off this `Peekable` until the child doesn't match anymore.
434 for (parent_field_idx, parent_capture) in parent_captures.into_iter().enumerate() {
435 // Make sure we use every field at least once, b/c why are we capturing something
436 // if it's not used in the inner coroutine.
437 let mut field_used_at_least_once = false;
438
439 // A parent matches a child if they share the same prefix of projections.
440 // The child may have more, if it is capturing sub-fields out of
441 // something that is captured by-move in the parent closure.
442 while child_captures.peek().is_some_and(|(_, child_capture)| {
443 child_prefix_matches_parent_projections(parent_capture, child_capture)
444 }) {
445 let (child_field_idx, child_capture) = child_captures.next().unwrap();
446 // This analysis only makes sense if the parent capture is a
447 // prefix of the child capture.
448 assert!(
449 child_capture.place.projections.len()
450 >= parent_capture.place.projections.len(),
451 "parent capture ({parent_capture:#?}) expected to be prefix of \
452 child capture ({child_capture:#?})"
453 );
454
455 yield for_each(
456 (parent_field_idx, parent_capture),
457 (child_field_idx, child_capture),
458 );
459
460 field_used_at_least_once = true;
461 }
462
463 // Make sure the field was used at least once.
464 assert!(
465 field_used_at_least_once,
466 "we captured {parent_capture:#?} but it was not used in the child coroutine?"
467 );
468 }
469 assert_eq!(child_captures.next(), None, "leftover child captures?");
470 },
471 )
472}
473
474fn child_prefix_matches_parent_projections(
475 parent_capture: &ty::CapturedPlace<'_>,
476 child_capture: &ty::CapturedPlace<'_>,
477) -> bool {
478 let HirPlaceBase::Upvar(parent_base) = parent_capture.place.base else {
479 bug!("expected capture to be an upvar");
480 };
481 let HirPlaceBase::Upvar(child_base) = child_capture.place.base else {
482 bug!("expected capture to be an upvar");
483 };
484
485 parent_base.var_path.hir_id == child_base.var_path.hir_id
486 && std::iter::zip(&child_capture.place.projections, &parent_capture.place.projections)
487 .all(|(child, parent)| child.kind == parent.kind)
488}
489
490pub fn provide(providers: &mut Providers) {
491 *providers = Providers { closure_typeinfo, ..*providers }
492}