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