compiler/rustc_ast_lowering/src/expr/closure.rs RUST 378 lines View on github.com → Search inside
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;78use super::{LoweringContext, MoveExprInitializerFinder, MoveExprState};9use crate::FnDeclKind;10use crate::diagnostics::{ClosureCannotBeStatic, CoroutineTooManyParameters};1112impl<'hir> LoweringContext<'_, 'hir> {13    // Entry point for `ExprKind::Closure`. Plain closures go through14    // `lower_expr_plain_closure_with_move_exprs`, which can wrap the lowered15    // closure in `let` initializers for `move(...)`. Coroutine closures keep the16    // 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));2425        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                ),42                span: self.lower_span(e.span),43            },44            None => self.lower_expr_plain_closure_with_move_exprs(45                expr_hir_id,46                attrs,47                &closure.binder,48                closure.capture_clause,49                e.id,50                closure.constness,51                closure.movability,52                &closure.fn_decl,53                &closure.body,54                closure.fn_decl_span,55                closure.fn_arg_span,56                e.span,57            ),58        }59    }6061    /// Lowers a plain closure expression and wraps it in an outer block if the62    /// closure body used `move(...)`.63    ///64    /// The lowering is split this way because `move(...)` initializers must be65    /// evaluated before the closure is created, but the closure body must still66    /// lower each `move(...)` occurrence as a use of the synthetic local that67    /// will be introduced by that outer block. For example:68    ///69    /// ```ignore (illustrative)70    /// || (move(move(foo.clone()))).len()71    /// ```72    ///73    /// first lowers the closure body roughly as `|| __move_expr_1.len()` while74    /// recording two occurrences:75    ///76    /// ```ignore (illustrative)77    /// move(foo.clone()) -> __move_expr_078    /// move(move(foo.clone())) -> __move_expr_179    /// ```80    ///81    /// This method then lowers the recorded initializers in order and builds the82    /// surrounding block:83    ///84    /// ```ignore (illustrative)85    /// {86    ///     let __move_expr_0 = foo.clone();87    ///     let __move_expr_1 = __move_expr_0;88    ///     || __move_expr_1.len()89    /// }90    /// ```91    fn lower_expr_plain_closure_with_move_exprs(92        &mut self,93        expr_hir_id: HirId,94        attrs: &[hir::Attribute],95        binder: &ClosureBinder,96        capture_clause: CaptureBy,97        closure_id: NodeId,98        constness: Const,99        movability: Movability,100        decl: &FnDecl,101        body: &Expr,102        fn_decl_span: Span,103        fn_arg_span: Span,104        whole_span: Span,105    ) -> hir::Expr<'hir> {106        let (closure_kind, move_expr_state) = self.lower_expr_closure(107            attrs,108            binder,109            capture_clause,110            closure_id,111            constness,112            movability,113            decl,114            body,115            fn_decl_span,116            fn_arg_span,117        );118119        if move_expr_state.occurrences.is_empty() {120            return hir::Expr {121                hir_id: expr_hir_id,122                kind: closure_kind,123                span: self.lower_span(whole_span),124            };125        }126127        let initializers = MoveExprInitializerFinder::collect(body)128            .into_iter()129            .map(|initializer| (initializer.id, initializer.expr))130            .collect::<NodeMap<_>>();131        let mut stmts = Vec::with_capacity(move_expr_state.occurrences.len());132        let mut initializer_bindings = NodeMap::default();133        for occurrence in &move_expr_state.occurrences {134            // Evaluate the expression inside `move(...)` before creating the135            // closure and store it in a synthetic local:136            // `|| move(foo).bar` becomes roughly137            // `let __move_expr_0 = foo; || __move_expr_0.bar`.138            let expr = initializers[&occurrence.id];139            let init = if initializer_bindings.is_empty() {140                self.lower_expr(expr)141            } else {142                // Earlier entries cover nested `move(...)` expressions that143                // appear inside this initializer, as in144                // `move(move(foo.clone()))`.145                let (init, _) = self.with_move_expr_bindings(146                    Some(MoveExprState {147                        bindings: initializer_bindings.clone(),148                        occurrences: Vec::new(),149                    }),150                    |this| this.lower_expr(expr),151                );152                init153            };154            stmts.push(self.stmt_let_pat(155                None,156                expr.span,157                Some(init),158                occurrence.pat,159                hir::LocalSource::Normal,160            ));161            initializer_bindings.insert(occurrence.id, (occurrence.ident, occurrence.binding));162        }163164        let closure_expr = self.arena.alloc(hir::Expr {165            hir_id: expr_hir_id,166            kind: closure_kind,167            span: self.lower_span(whole_span),168        });169170        let stmts = self.arena.alloc_from_iter(stmts);171        let block = self.block_all(whole_span, stmts, Some(closure_expr));172        self.expr(whole_span, hir::ExprKind::Block(block, None))173    }174175    // Lowers the actual plain closure node and body. The body is lowered while a176    // `MoveExprState` is active, so `move(...)` occurrences become synthetic177    // local uses and the caller can later add the matching initializers.178    fn lower_expr_closure(179        &mut self,180        attrs: &[hir::Attribute],181        binder: &ClosureBinder,182        capture_clause: CaptureBy,183        closure_id: NodeId,184        constness: Const,185        movability: Movability,186        decl: &FnDecl,187        body: &Expr,188        fn_decl_span: Span,189        fn_arg_span: Span,190    ) -> (hir::ExprKind<'hir>, MoveExprState<'hir>) {191        let closure_def_id = self.local_def_id(closure_id);192        let (binder_clause, generic_params) = self.lower_closure_binder(binder);193194        let ((body_id, closure_kind), move_expr_state) =195            self.with_new_scopes(fn_decl_span, move |this| {196                let mut coroutine_kind = find_attr!(197                    attrs,198                    Coroutine => hir::CoroutineKind::Coroutine(Movability::Movable)199                );200201                this.with_move_expr_bindings(Some(MoveExprState::default()), |this| {202                    // FIXME(contracts): Support contracts on closures?203                    let body_id = this.lower_fn_body(decl, None, |this| {204                        this.coroutine_kind = coroutine_kind;205                        let e = this.lower_expr_mut(body);206                        coroutine_kind = this.coroutine_kind;207                        e208                    });209                    let coroutine_option = this.closure_movability_for_fn(210                        decl,211                        fn_decl_span,212                        coroutine_kind,213                        movability,214                    );215                    (body_id, coroutine_option)216                })217            });218        let Some(move_expr_state) = move_expr_state else {219            span_bug!(fn_decl_span, "plain closure lowering did not return `move(...)` state");220        };221        let explicit_captures: &'hir [hir::ExplicitCapture] = self.arena.alloc_from_iter(222            move_expr_state.occurrences.iter().filter_map(|occurrence| {223                occurrence224                    .explicit_capture225                    .then_some(hir::ExplicitCapture { var_hir_id: occurrence.binding })226            }),227        );228229        let bound_generic_params = self.lower_lifetime_binder(closure_id, generic_params);230        // Lower outside new scope to preserve `is_in_loop_condition`.231        let fn_decl = self.lower_fn_decl(decl, closure_id, fn_decl_span, FnDeclKind::Closure, None);232233        let c = self.arena.alloc(hir::Closure {234            def_id: closure_def_id,235            binder: binder_clause,236            capture_clause: self.lower_capture_clause(capture_clause),237            bound_generic_params,238            fn_decl,239            body: body_id,240            fn_decl_span: self.lower_span(fn_decl_span),241            fn_arg_span: Some(self.lower_span(fn_arg_span)),242            kind: closure_kind,243            constness: self.lower_constness(constness),244            explicit_captures,245        });246247        (hir::ExprKind::Closure(c), move_expr_state)248    }249250    fn closure_movability_for_fn(251        &mut self,252        decl: &FnDecl,253        fn_decl_span: Span,254        coroutine_kind: Option<hir::CoroutineKind>,255        movability: Movability,256    ) -> hir::ClosureKind {257        match coroutine_kind {258            Some(hir::CoroutineKind::Coroutine(_)) => {259                if decl.inputs.len() > 1 {260                    self.dcx().emit_err(CoroutineTooManyParameters { fn_decl_span });261                }262                hir::ClosureKind::Coroutine(hir::CoroutineKind::Coroutine(movability))263            }264            Some(265                hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Gen, _)266                | hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Async, _)267                | hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::AsyncGen, _),268            ) => {269                panic!("non-`async`/`gen` closure body turned `async`/`gen` during lowering");270            }271            None => {272                if movability == Movability::Static {273                    self.dcx().emit_err(ClosureCannotBeStatic { fn_decl_span });274                }275                hir::ClosureKind::Closure276            }277        }278    }279280    fn lower_closure_binder<'c>(281        &mut self,282        binder: &'c ClosureBinder,283    ) -> (hir::ClosureBinder, &'c [GenericParam]) {284        let (binder, params) = match binder {285            ClosureBinder::NotPresent => (hir::ClosureBinder::Default, &[][..]),286            ClosureBinder::For { span, generic_params } => {287                let span = self.lower_span(*span);288                (hir::ClosureBinder::For { span }, &**generic_params)289            }290        };291292        (binder, params)293    }294295    // Coroutine closures are lowered separately because they build a different296    // body shape. This path pushes `None` for `move_expr_bindings`, so any297    // `move(...)` in the coroutine body gets a targeted unsupported-position298    // error instead of being collected like a plain closure occurrence.299    fn lower_expr_coroutine_closure(300        &mut self,301        binder: &ClosureBinder,302        capture_clause: CaptureBy,303        closure_id: NodeId,304        closure_hir_id: HirId,305        coroutine_kind: CoroutineKind,306        constness: Const,307        decl: &FnDecl,308        body: &Expr,309        fn_decl_span: Span,310        fn_arg_span: Span,311    ) -> hir::ExprKind<'hir> {312        let closure_def_id = self.local_def_id(closure_id);313        let (binder_clause, generic_params) = self.lower_closure_binder(binder);314315        let coroutine_desugaring = match coroutine_kind {316            CoroutineKind::Async { .. } => hir::CoroutineDesugaring::Async,317            CoroutineKind::Gen { .. } => hir::CoroutineDesugaring::Gen,318            CoroutineKind::AsyncGen { span, .. } => {319                span_bug!(span, "only async closures and `iter!` closures are supported currently")320            }321        };322323        let body = self.with_new_scopes(fn_decl_span, |this| {324            let inner_decl =325                FnDecl { inputs: decl.inputs.clone(), output: FnRetTy::Default(fn_decl_span) };326327            // Transform `async |x: u8| -> X { ... }` into328            // `|x: u8| || -> X { ... }`.329            let body_id = this.lower_body(|this| {330                let ((parameters, expr), _) = this.with_move_expr_bindings(None, |this| {331                    this.lower_coroutine_body_with_moved_arguments(332                        &inner_decl,333                        |this| this.with_new_scopes(fn_decl_span, |this| this.lower_expr_mut(body)),334                        fn_decl_span,335                        body.span,336                        coroutine_kind,337                        hir::CoroutineSource::Closure,338                    )339                });340341                this.maybe_forward_track_caller(body.span, closure_hir_id, expr.hir_id);342343                (parameters, expr)344            });345            body_id346        });347348        let bound_generic_params = self.lower_lifetime_binder(closure_id, generic_params);349        // We need to lower the declaration outside the new scope, because we350        // have to conserve the state of being inside a loop condition for the351        // closure argument types.352        let fn_decl =353            self.lower_fn_decl(&decl, closure_id, fn_decl_span, FnDeclKind::Closure, None);354355        if let Const::Yes(span) = constness {356            self.dcx().span_err(span, "const coroutines are not supported");357        }358359        let c = self.arena.alloc(hir::Closure {360            def_id: closure_def_id,361            binder: binder_clause,362            capture_clause: self.lower_capture_clause(capture_clause),363            bound_generic_params,364            fn_decl,365            body,366            fn_decl_span: self.lower_span(fn_decl_span),367            fn_arg_span: Some(self.lower_span(fn_arg_span)),368            // Lower this as a `CoroutineClosure`. That will ensure that HIR typeck369            // knows that a `FnDecl` output type like `-> &str` actually means370            // "coroutine that returns &str", rather than directly returning a `&str`.371            kind: hir::ClosureKind::CoroutineClosure(coroutine_desugaring),372            constness: self.lower_constness(constness),373            explicit_captures: &[],374        });375        hir::ExprKind::Closure(c)376    }377}

Code quality findings 5

Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let expr = initializers[&occurrence.id];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let explicit_captures: &'hir [hir::ExplicitCapture] = self.arena.alloc_from_iter(
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
) -> (hir::ClosureBinder, &'c [GenericParam]) {
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
ClosureBinder::NotPresent => (hir::ClosureBinder::Default, &[][..]),
Info: Wildcard imports (`use some::path::*;`) can obscure the origin of names and lead to conflicts. Prefer importing specific items explicitly.
info maintainability wildcard-import
use rustc_ast::*;

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.