{"id":"8422fad60f379f3a","repo":"rust-lang/rust","slug":"non-async-gen-closure-body-turned-async-gen","errorCode":null,"errorMessage":"non-`async`/`gen` closure body turned `async`/`gen` during lowering","messagePattern":"non-`async`/`gen` closure body turned `async`/`gen` during lowering","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"compiler/rustc_ast_lowering/src/expr/closure.rs","lineNumber":270,"sourceCode":"        &mut self,\n        decl: &FnDecl,\n        fn_decl_span: Span,\n        coroutine_kind: Option<hir::CoroutineKind>,\n        movability: Movability,\n    ) -> hir::ClosureKind {\n        match coroutine_kind {\n            Some(hir::CoroutineKind::Coroutine(_)) => {\n                if decl.inputs.len() > 1 {\n                    self.dcx().emit_err(CoroutineTooManyParameters { fn_decl_span });\n                }\n                hir::ClosureKind::Coroutine(hir::CoroutineKind::Coroutine(movability))\n            }\n            Some(\n                hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Gen, _)\n                | hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Async, _)\n                | hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::AsyncGen, _),\n            ) => {\n                panic!(\"non-`async`/`gen` closure body turned `async`/`gen` during lowering\");\n            }\n            None => {\n                if movability == Movability::Static {\n                    self.dcx().emit_err(ClosureCannotBeStatic { fn_decl_span });\n                }\n                hir::ClosureKind::Closure\n            }\n        }\n    }\n\n    fn lower_closure_binder<'c>(\n        &mut self,\n        binder: &'c ClosureBinder,\n    ) -> (hir::ClosureBinder, &'c [GenericParam]) {\n        let (binder, params) = match binder {\n            ClosureBinder::NotPresent => (hir::ClosureBinder::Default, &[][..]),\n            ClosureBinder::For { span, generic_params } => {\n                let span = self.lower_span(*span);","sourceCodeStart":252,"sourceCodeEnd":288,"githubUrl":"https://github.com/rust-lang/rust/blob/22057b88b091743bc0fd8d592a9264f0a6951403/compiler/rustc_ast_lowering/src/expr/closure.rs#L252-L288","documentation":"This `panic!` fires in `closure_movability_for_fn` when a closure's `coroutine_kind` is one of the `CoroutineKind::Desugared` variants (Gen/Async/AsyncGen) but the closure is being lowered through the plain-closure lowering path that is only valid for `Coroutine` or `None`. Desugared coroutine kinds (`async { }`, `gen { }`) are routed through `lower_expr_coroutine_closure`; if one reaches `closure_movability_for_fn`, the dispatch in the caller is wrong. It is a hard ICE indicating a logic error in the lowering dispatcher.","triggerScenarios":"Triggered when `lower_expr_closure` (or equivalent) calls `closure_movability_for_fn` with a `coroutine_kind` that is `CoroutineKind::Desugared(Gen|Async|AsyncGen, _)` — i.e. the closure had an `async move` / `gen move` body but was not redirected to `lower_expr_coroutine_closure` at the earlier dispatch site. The offending `match` arm at closure.rs:265-271 catches exactly the three Desugared variants.","commonSituations":"Surfaces during rustc development when refactoring closure/coroutine dispatch (e.g. changing which closures go through `lower_expr_coroutine_closure`), or in nightlies with regressions around `async`/`gen` closures combined with `move` or explicit binders (`for<'a> async |x| ...`). The `gen`/`async gen` closures feature is nightly-only and under active work.","solutions":["Report to the rustc ICE tracker with a reproducer using `#![feature(gen_blocks)]` / `async` closures — this is a compiler bug.","Avoid the combination that triggers it (e.g. `for<'a>` binder on an `async`/`gen` closure) until the nightly is fixed.","Bisect nightlies to find when the regression entered and pin to the last working nightly.","If hacking on rustc: ensure the dispatch in `lower_expr_closure` routes any `CoroutineKind::Desugared` to `lower_expr_coroutine_closure` before reaching `closure_movability_for_fn`."],"exampleFix":"// before — explicit binder on an async closure that bypasses coroutine-closure lowering\nlet f = for<'a> async |x: &'a u8| { x };\n\n// after — drop the explicit binder or use a plain async closure\nlet f = async |x: &u8| { x };","handlingStrategy":"fallback","validationCode":"// A non-async/gen closure became async/gen during lowering — a compiler\n// invariant violation, usually triggered by macros that synthesize closures.\n// No pre-API check exists; the defense is to WRITE qualifiers literally.\n//\n// Good (literal):  let f = async move || { .. };\n// Risky (generated): proc-macro emits `|| { .. }` then rewrites it async.\n//\n// If a macro builds closures, assert the async-ness flag before emitting:\n//   assert_eq!(generated_closure_is_async, intended_async);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Write `async`/`gen` closure qualifiers literally in source; do not generate them via proc-macros","If a macro synthesizes closures, set the coroutine kind explicitly before emitting the node","Avoid macros that post-process a closure to flip its async-ness","Bisect the macro expansion that triggers it and file an ICE with the reduced output"],"tags":["rustc","ast-lowering","closure","coroutine","async","ice"],"analyzedSha":"22057b88b091743bc0fd8d592a9264f0a6951403","analyzedAt":"2026-08-03T08:09:25.915Z","schemaVersion":2}