Skip to main content

rustc_ast_lowering/expr/
closure.rs

1use rustc_ast::node_id::NodeMap;
2use rustc_ast::*;
3use rustc_hir as hir;
4use rustc_hir::{HirId, Target, find_attr};
5use rustc_middle::span_bug;
6use rustc_span::Span;
7
8use super::{LoweringContext, MoveExprInitializerFinder, MoveExprState};
9use crate::FnDeclKind;
10use crate::diagnostics::{ClosureCannotBeStatic, CoroutineTooManyParameters};
11
12impl<'hir> LoweringContext<'_, 'hir> {
13    // Entry point for `ExprKind::Closure`. Plain closures go through
14    // `lower_expr_plain_closure_with_move_exprs`, which can wrap the lowered
15    // closure in `let` initializers for `move(...)`. Coroutine closures keep the
16    // existing coroutine-specific path and reject `move(...)` for now.
17    pub(super) fn lower_expr_closure_expr(
18        &mut self,
19        e: &Expr,
20        closure: &Closure,
21    ) -> hir::Expr<'hir> {
22        let expr_hir_id = self.lower_node_id(e.id);
23        let attrs = self.lower_attrs(expr_hir_id, &e.attrs, e.span, Target::from_expr(e));
24
25        match closure.coroutine_kind {
26            // FIXME(TaKO8Ki): Support `move(expr)` in coroutine closures too.
27            // For the first step, we only support plain closures.
28            Some(coroutine_kind) => hir::Expr {
29                hir_id: expr_hir_id,
30                kind: self.lower_expr_coroutine_closure(
31                    &closure.binder,
32                    closure.capture_clause,
33                    e.id,
34                    expr_hir_id,
35                    coroutine_kind,
36                    closure.constness,
37                    &closure.fn_decl,
38                    &closure.body,
39                    closure.fn_decl_span,
40                    closure.fn_arg_span,
41                    attrs,
42                ),
43                span: self.lower_span(e.span),
44            },
45            None => self.lower_expr_plain_closure_with_move_exprs(
46                expr_hir_id,
47                attrs,
48                &closure.binder,
49                closure.capture_clause,
50                e.id,
51                closure.constness,
52                closure.movability,
53                &closure.fn_decl,
54                &closure.body,
55                closure.fn_decl_span,
56                closure.fn_arg_span,
57                e.span,
58            ),
59        }
60    }
61
62    /// Lowers a plain closure expression and wraps it in an outer block if the
63    /// closure body used `move(...)`.
64    ///
65    /// The lowering is split this way because `move(...)` initializers must be
66    /// evaluated before the closure is created, but the closure body must still
67    /// lower each `move(...)` occurrence as a use of the synthetic local that
68    /// will be introduced by that outer block. For example:
69    ///
70    /// ```ignore (illustrative)
71    /// || (move(move(foo.clone()))).len()
72    /// ```
73    ///
74    /// first lowers the closure body roughly as `|| __move_expr_1.len()` while
75    /// recording two occurrences:
76    ///
77    /// ```ignore (illustrative)
78    /// move(foo.clone()) -> __move_expr_0
79    /// move(move(foo.clone())) -> __move_expr_1
80    /// ```
81    ///
82    /// This method then lowers the recorded initializers in order and builds the
83    /// surrounding block:
84    ///
85    /// ```ignore (illustrative)
86    /// {
87    ///     let __move_expr_0 = foo.clone();
88    ///     let __move_expr_1 = __move_expr_0;
89    ///     || __move_expr_1.len()
90    /// }
91    /// ```
92    fn lower_expr_plain_closure_with_move_exprs(
93        &mut self,
94        expr_hir_id: HirId,
95        attrs: &[hir::Attribute],
96        binder: &ClosureBinder,
97        capture_clause: CaptureBy,
98        closure_id: NodeId,
99        constness: Const,
100        movability: Movability,
101        decl: &FnDecl,
102        body: &Expr,
103        fn_decl_span: Span,
104        fn_arg_span: Span,
105        whole_span: Span,
106    ) -> hir::Expr<'hir> {
107        let (closure_kind, move_expr_state) = self.lower_expr_closure(
108            attrs,
109            binder,
110            capture_clause,
111            closure_id,
112            constness,
113            movability,
114            decl,
115            body,
116            fn_decl_span,
117            fn_arg_span,
118        );
119
120        if move_expr_state.occurrences.is_empty() {
121            return hir::Expr {
122                hir_id: expr_hir_id,
123                kind: closure_kind,
124                span: self.lower_span(whole_span),
125            };
126        }
127
128        let initializers = MoveExprInitializerFinder::collect(body)
129            .into_iter()
130            .map(|initializer| (initializer.id, initializer.expr))
131            .collect::<NodeMap<_>>();
132        let mut stmts = Vec::with_capacity(move_expr_state.occurrences.len());
133        let mut initializer_bindings = NodeMap::default();
134        for occurrence in &move_expr_state.occurrences {
135            // Evaluate the expression inside `move(...)` before creating the
136            // closure and store it in a synthetic local:
137            // `|| move(foo).bar` becomes roughly
138            // `let __move_expr_0 = foo; || __move_expr_0.bar`.
139            let expr = initializers[&occurrence.id];
140            let init = if initializer_bindings.is_empty() {
141                self.lower_expr(expr)
142            } else {
143                // Earlier entries cover nested `move(...)` expressions that
144                // appear inside this initializer, as in
145                // `move(move(foo.clone()))`.
146                let (init, _) = self.with_move_expr_bindings(
147                    Some(MoveExprState {
148                        bindings: initializer_bindings.clone(),
149                        occurrences: Vec::new(),
150                    }),
151                    |this| this.lower_expr(expr),
152                );
153                init
154            };
155            stmts.push(self.stmt_let_pat(
156                None,
157                expr.span,
158                Some(init),
159                occurrence.pat,
160                hir::LocalSource::Normal,
161            ));
162            initializer_bindings.insert(occurrence.id, (occurrence.ident, occurrence.binding));
163        }
164
165        let closure_expr = self.arena.alloc(hir::Expr {
166            hir_id: expr_hir_id,
167            kind: closure_kind,
168            span: self.lower_span(whole_span),
169        });
170
171        let stmts = self.arena.alloc_from_iter(stmts);
172        let block = self.block_all(whole_span, stmts, Some(closure_expr));
173        self.expr(whole_span, hir::ExprKind::Block(block, None))
174    }
175
176    // Lowers the actual plain closure node and body. The body is lowered while a
177    // `MoveExprState` is active, so `move(...)` occurrences become synthetic
178    // local uses and the caller can later add the matching initializers.
179    fn lower_expr_closure(
180        &mut self,
181        attrs: &[hir::Attribute],
182        binder: &ClosureBinder,
183        capture_clause: CaptureBy,
184        closure_id: NodeId,
185        constness: Const,
186        movability: Movability,
187        decl: &FnDecl,
188        body: &Expr,
189        fn_decl_span: Span,
190        fn_arg_span: Span,
191    ) -> (hir::ExprKind<'hir>, MoveExprState<'hir>) {
192        let closure_def_id = self.local_def_id(closure_id);
193        let (binder_clause, generic_params) = self.lower_closure_binder(binder);
194
195        let ((body_id, closure_kind), move_expr_state) =
196            self.with_new_scopes(fn_decl_span, move |this| {
197                let mut coroutine_kind = {
    'done:
        {
        for i in attrs {
            #[allow(unused_imports)]
            use rustc_hir::attrs::AttributeKind::*;
            let i: &rustc_hir::Attribute = i;
            match i {
                rustc_hir::Attribute::Parsed(Coroutine) => {
                    break 'done
                        Some(hir::CoroutineKind::Coroutine(Movability::Movable));
                }
                rustc_hir::Attribute::Unparsed(..) =>
                    {}
                    #[deny(unreachable_patterns)]
                    _ => {}
            }
        }
        None
    }
}find_attr!(
198                    attrs,
199                    Coroutine => hir::CoroutineKind::Coroutine(Movability::Movable)
200                );
201
202                this.with_move_expr_bindings(Some(MoveExprState::default()), |this| {
203                    // FIXME(contracts): Support contracts on closures?
204                    let body_id = this.lower_fn_body(decl, None, |this| {
205                        this.coroutine_kind = coroutine_kind;
206                        let e = this.lower_expr_mut(body);
207                        coroutine_kind = this.coroutine_kind;
208                        e
209                    });
210                    let coroutine_option = this.closure_movability_for_fn(
211                        decl,
212                        fn_decl_span,
213                        coroutine_kind,
214                        movability,
215                    );
216                    (body_id, coroutine_option)
217                })
218            });
219        let Some(move_expr_state) = move_expr_state else {
220            ::rustc_middle::util::bug::span_bug_fmt(fn_decl_span,
    format_args!("plain closure lowering did not return `move(...)` state"));span_bug!(fn_decl_span, "plain closure lowering did not return `move(...)` state");
221        };
222        let explicit_captures: &'hir [hir::ExplicitCapture] = self.arena.alloc_from_iter(
223            move_expr_state.occurrences.iter().filter_map(|occurrence| {
224                occurrence
225                    .explicit_capture
226                    .then_some(hir::ExplicitCapture { var_hir_id: occurrence.binding })
227            }),
228        );
229
230        let bound_generic_params = self.lower_lifetime_binder(closure_id, generic_params);
231        // Lower outside new scope to preserve `is_in_loop_condition`.
232        let fn_decl = self.lower_fn_decl(decl, closure_id, fn_decl_span, FnDeclKind::Closure, None);
233
234        let c = self.arena.alloc(hir::Closure {
235            def_id: closure_def_id,
236            binder: binder_clause,
237            capture_clause: self.lower_capture_clause(capture_clause),
238            bound_generic_params,
239            fn_decl,
240            body: body_id,
241            fn_decl_span: self.lower_span(fn_decl_span),
242            fn_arg_span: Some(self.lower_span(fn_arg_span)),
243            kind: closure_kind,
244            constness: self.lower_constness(attrs, constness),
245            explicit_captures,
246        });
247
248        (hir::ExprKind::Closure(c), move_expr_state)
249    }
250
251    fn closure_movability_for_fn(
252        &mut self,
253        decl: &FnDecl,
254        fn_decl_span: Span,
255        coroutine_kind: Option<hir::CoroutineKind>,
256        movability: Movability,
257    ) -> hir::ClosureKind {
258        match coroutine_kind {
259            Some(hir::CoroutineKind::Coroutine(_)) => {
260                if decl.inputs.len() > 1 {
261                    self.dcx().emit_err(CoroutineTooManyParameters { fn_decl_span });
262                }
263                hir::ClosureKind::Coroutine(hir::CoroutineKind::Coroutine(movability))
264            }
265            Some(
266                hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Gen, _)
267                | hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Async, _)
268                | hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::AsyncGen, _),
269            ) => {
270                {
    ::core::panicking::panic_fmt(format_args!("non-`async`/`gen` closure body turned `async`/`gen` during lowering"));
};panic!("non-`async`/`gen` closure body turned `async`/`gen` during lowering");
271            }
272            None => {
273                if movability == Movability::Static {
274                    self.dcx().emit_err(ClosureCannotBeStatic { fn_decl_span });
275                }
276                hir::ClosureKind::Closure
277            }
278        }
279    }
280
281    fn lower_closure_binder<'c>(
282        &mut self,
283        binder: &'c ClosureBinder,
284    ) -> (hir::ClosureBinder, &'c [GenericParam]) {
285        let (binder, params) = match binder {
286            ClosureBinder::NotPresent => (hir::ClosureBinder::Default, &[][..]),
287            ClosureBinder::For { span, generic_params } => {
288                let span = self.lower_span(*span);
289                (hir::ClosureBinder::For { span }, &**generic_params)
290            }
291        };
292
293        (binder, params)
294    }
295
296    // Coroutine closures are lowered separately because they build a different
297    // body shape. This path pushes `None` for `move_expr_bindings`, so any
298    // `move(...)` in the coroutine body gets a targeted unsupported-position
299    // error instead of being collected like a plain closure occurrence.
300    fn lower_expr_coroutine_closure(
301        &mut self,
302        binder: &ClosureBinder,
303        capture_clause: CaptureBy,
304        closure_id: NodeId,
305        closure_hir_id: HirId,
306        coroutine_kind: CoroutineKind,
307        constness: Const,
308        decl: &FnDecl,
309        body: &Expr,
310        fn_decl_span: Span,
311        fn_arg_span: Span,
312        attrs: &[hir::Attribute],
313    ) -> hir::ExprKind<'hir> {
314        let closure_def_id = self.local_def_id(closure_id);
315        let (binder_clause, generic_params) = self.lower_closure_binder(binder);
316
317        let coroutine_desugaring = match coroutine_kind {
318            CoroutineKind::Async { .. } => hir::CoroutineDesugaring::Async,
319            CoroutineKind::Gen { .. } => hir::CoroutineDesugaring::Gen,
320            CoroutineKind::AsyncGen { span, .. } => {
321                ::rustc_middle::util::bug::span_bug_fmt(span,
    format_args!("only async closures and `iter!` closures are supported currently"))span_bug!(span, "only async closures and `iter!` closures are supported currently")
322            }
323        };
324
325        let body = self.with_new_scopes(fn_decl_span, |this| {
326            let inner_decl =
327                FnDecl { inputs: decl.inputs.clone(), output: FnRetTy::Default(fn_decl_span) };
328
329            // Transform `async |x: u8| -> X { ... }` into
330            // `|x: u8| || -> X { ... }`.
331            let body_id = this.lower_body(|this| {
332                let ((parameters, expr), _) = this.with_move_expr_bindings(None, |this| {
333                    this.lower_coroutine_body_with_moved_arguments(
334                        &inner_decl,
335                        |this| this.with_new_scopes(fn_decl_span, |this| this.lower_expr_mut(body)),
336                        fn_decl_span,
337                        body.span,
338                        coroutine_kind,
339                        hir::CoroutineSource::Closure,
340                    )
341                });
342
343                this.maybe_forward_track_caller(body.span, closure_hir_id, expr.hir_id);
344
345                (parameters, expr)
346            });
347            body_id
348        });
349
350        let bound_generic_params = self.lower_lifetime_binder(closure_id, generic_params);
351        // We need to lower the declaration outside the new scope, because we
352        // have to conserve the state of being inside a loop condition for the
353        // closure argument types.
354        let fn_decl =
355            self.lower_fn_decl(&decl, closure_id, fn_decl_span, FnDeclKind::Closure, None);
356
357        if let Const::Yes(span) = constness {
358            self.dcx().span_err(span, "const coroutines are not supported");
359        }
360
361        let c = self.arena.alloc(hir::Closure {
362            def_id: closure_def_id,
363            binder: binder_clause,
364            capture_clause: self.lower_capture_clause(capture_clause),
365            bound_generic_params,
366            fn_decl,
367            body,
368            fn_decl_span: self.lower_span(fn_decl_span),
369            fn_arg_span: Some(self.lower_span(fn_arg_span)),
370            // Lower this as a `CoroutineClosure`. That will ensure that HIR typeck
371            // knows that a `FnDecl` output type like `-> &str` actually means
372            // "coroutine that returns &str", rather than directly returning a `&str`.
373            kind: hir::ClosureKind::CoroutineClosure(coroutine_desugaring),
374            constness: self.lower_constness(attrs, constness),
375            explicit_captures: &[],
376        });
377        hir::ExprKind::Closure(c)
378    }
379}