Make async closures directly lower to ClosureKind::CoroutineClosure

This commit is contained in:
Michael Goulet 2024-01-24 17:30:02 +00:00
parent b2bb51734c
commit a20421734b
12 changed files with 93 additions and 88 deletions

View File

@ -117,9 +117,6 @@ ast_lowering_never_pattern_with_guard =
a guard on a never pattern will never be run a guard on a never pattern will never be run
.suggestion = remove this guard .suggestion = remove this guard
ast_lowering_not_supported_for_lifetime_binder_async_closure =
`for<...>` binders on `async` closures are not currently supported
ast_lowering_previously_used_here = previously used here ast_lowering_previously_used_here = previously used here
ast_lowering_register1 = register `{$reg1_name}` ast_lowering_register1 = register `{$reg1_name}`

View File

@ -326,13 +326,6 @@ pub struct MisplacedRelaxTraitBound {
pub span: Span, pub span: Span,
} }
#[derive(Diagnostic, Clone, Copy)]
#[diag(ast_lowering_not_supported_for_lifetime_binder_async_closure)]
pub struct NotSupportedForLifetimeBinderAsyncClosure {
#[primary_span]
pub span: Span,
}
#[derive(Diagnostic)] #[derive(Diagnostic)]
#[diag(ast_lowering_match_arm_with_no_body)] #[diag(ast_lowering_match_arm_with_no_body)]
pub struct MatchArmWithNoBody { pub struct MatchArmWithNoBody {

View File

@ -2,8 +2,7 @@ use super::errors::{
AsyncCoroutinesNotSupported, AwaitOnlyInAsyncFnAndBlocks, BaseExpressionDoubleDot, AsyncCoroutinesNotSupported, AwaitOnlyInAsyncFnAndBlocks, BaseExpressionDoubleDot,
ClosureCannotBeStatic, CoroutineTooManyParameters, ClosureCannotBeStatic, CoroutineTooManyParameters,
FunctionalRecordUpdateDestructuringAssignment, InclusiveRangeWithNoEnd, MatchArmWithNoBody, FunctionalRecordUpdateDestructuringAssignment, InclusiveRangeWithNoEnd, MatchArmWithNoBody,
NeverPatternWithBody, NeverPatternWithGuard, NotSupportedForLifetimeBinderAsyncClosure, NeverPatternWithBody, NeverPatternWithGuard, UnderscoreExprLhsAssign,
UnderscoreExprLhsAssign,
}; };
use super::ResolverAstLoweringExt; use super::ResolverAstLoweringExt;
use super::{ImplTraitContext, LoweringContext, ParamMode, ParenthesizedGenericArgs}; use super::{ImplTraitContext, LoweringContext, ParamMode, ParenthesizedGenericArgs};
@ -1026,30 +1025,21 @@ impl<'hir> LoweringContext<'_, 'hir> {
fn_decl_span: Span, fn_decl_span: Span,
fn_arg_span: Span, fn_arg_span: Span,
) -> hir::ExprKind<'hir> { ) -> hir::ExprKind<'hir> {
if let &ClosureBinder::For { span, .. } = binder {
self.dcx().emit_err(NotSupportedForLifetimeBinderAsyncClosure { span });
}
let (binder_clause, generic_params) = self.lower_closure_binder(binder); let (binder_clause, generic_params) = self.lower_closure_binder(binder);
let body = self.with_new_scopes(fn_decl_span, |this| { let body = self.with_new_scopes(fn_decl_span, |this| {
let inner_decl =
FnDecl { inputs: decl.inputs.clone(), output: FnRetTy::Default(fn_decl_span) };
// Transform `async |x: u8| -> X { ... }` into // Transform `async |x: u8| -> X { ... }` into
// `|x: u8| || -> X { ... }`. // `|x: u8| || -> X { ... }`.
let body_id = this.lower_body(|this| { let body_id = this.lower_body(|this| {
let async_ret_ty = if let FnRetTy::Ty(ty) = &decl.output {
let itctx = ImplTraitContext::Disallowed(ImplTraitPosition::AsyncBlock);
Some(hir::FnRetTy::Return(this.lower_ty(ty, &itctx)))
} else {
None
};
let (parameters, expr) = this.lower_coroutine_body_with_moved_arguments( let (parameters, expr) = this.lower_coroutine_body_with_moved_arguments(
decl, &inner_decl,
|this| this.with_new_scopes(fn_decl_span, |this| this.lower_expr_mut(body)), |this| this.with_new_scopes(fn_decl_span, |this| this.lower_expr_mut(body)),
body.span, body.span,
coroutine_kind, coroutine_kind,
hir::CoroutineSource::Closure, hir::CoroutineSource::Closure,
async_ret_ty,
); );
let hir_id = this.lower_node_id(coroutine_kind.closure_id()); let hir_id = this.lower_node_id(coroutine_kind.closure_id());
@ -1060,15 +1050,12 @@ impl<'hir> LoweringContext<'_, 'hir> {
body_id body_id
}); });
let outer_decl =
FnDecl { inputs: decl.inputs.clone(), output: FnRetTy::Default(fn_decl_span) };
let bound_generic_params = self.lower_lifetime_binder(closure_id, generic_params); let bound_generic_params = self.lower_lifetime_binder(closure_id, generic_params);
// We need to lower the declaration outside the new scope, because we // We need to lower the declaration outside the new scope, because we
// have to conserve the state of being inside a loop condition for the // have to conserve the state of being inside a loop condition for the
// closure argument types. // closure argument types.
let fn_decl = let fn_decl =
self.lower_fn_decl(&outer_decl, closure_id, fn_decl_span, FnDeclKind::Closure, None); self.lower_fn_decl(&decl, closure_id, fn_decl_span, FnDeclKind::Closure, None);
let c = self.arena.alloc(hir::Closure { let c = self.arena.alloc(hir::Closure {
def_id: self.local_def_id(closure_id), def_id: self.local_def_id(closure_id),
@ -1079,7 +1066,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
body, body,
fn_decl_span: self.lower_span(fn_decl_span), fn_decl_span: self.lower_span(fn_decl_span),
fn_arg_span: Some(self.lower_span(fn_arg_span)), fn_arg_span: Some(self.lower_span(fn_arg_span)),
kind: hir::ClosureKind::Closure, kind: hir::ClosureKind::CoroutineClosure(hir::CoroutineDesugaring::Async),
constness: hir::Constness::NotConst, constness: hir::Constness::NotConst,
}); });
hir::ExprKind::Closure(c) hir::ExprKind::Closure(c)

View File

@ -1086,7 +1086,6 @@ impl<'hir> LoweringContext<'_, 'hir> {
body.span, body.span,
coroutine_kind, coroutine_kind,
hir::CoroutineSource::Fn, hir::CoroutineSource::Fn,
None,
); );
// FIXME(async_fn_track_caller): Can this be moved above? // FIXME(async_fn_track_caller): Can this be moved above?
@ -1108,7 +1107,6 @@ impl<'hir> LoweringContext<'_, 'hir> {
body_span: Span, body_span: Span,
coroutine_kind: CoroutineKind, coroutine_kind: CoroutineKind,
coroutine_source: hir::CoroutineSource, coroutine_source: hir::CoroutineSource,
return_type_hint: Option<hir::FnRetTy<'hir>>,
) -> (&'hir [hir::Param<'hir>], hir::Expr<'hir>) { ) -> (&'hir [hir::Param<'hir>], hir::Expr<'hir>) {
let mut parameters: Vec<hir::Param<'_>> = Vec::new(); let mut parameters: Vec<hir::Param<'_>> = Vec::new();
let mut statements: Vec<hir::Stmt<'_>> = Vec::new(); let mut statements: Vec<hir::Stmt<'_>> = Vec::new();
@ -1284,7 +1282,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
// all async closures would default to `FnOnce` as their calling mode. // all async closures would default to `FnOnce` as their calling mode.
CaptureBy::Ref, CaptureBy::Ref,
closure_id, closure_id,
return_type_hint, None,
body_span, body_span,
desugaring_kind, desugaring_kind,
coroutine_source, coroutine_source,

View File

@ -33,6 +33,7 @@
#![allow(internal_features)] #![allow(internal_features)]
#![feature(rustdoc_internals)] #![feature(rustdoc_internals)]
#![doc(rust_logo)] #![doc(rust_logo)]
#![feature(assert_matches)]
#![feature(box_patterns)] #![feature(box_patterns)]
#![feature(let_chains)] #![feature(let_chains)]
#![deny(rustc::untranslatable_diagnostic)] #![deny(rustc::untranslatable_diagnostic)]
@ -296,7 +297,6 @@ enum ImplTraitPosition {
Path, Path,
Variable, Variable,
Trait, Trait,
AsyncBlock,
Bound, Bound,
Generic, Generic,
ExternFnParam, ExternFnParam,
@ -323,7 +323,6 @@ impl std::fmt::Display for ImplTraitPosition {
ImplTraitPosition::Path => "paths", ImplTraitPosition::Path => "paths",
ImplTraitPosition::Variable => "the type of variable bindings", ImplTraitPosition::Variable => "the type of variable bindings",
ImplTraitPosition::Trait => "traits", ImplTraitPosition::Trait => "traits",
ImplTraitPosition::AsyncBlock => "async blocks",
ImplTraitPosition::Bound => "bounds", ImplTraitPosition::Bound => "bounds",
ImplTraitPosition::Generic => "generics", ImplTraitPosition::Generic => "generics",
ImplTraitPosition::ExternFnParam => "`extern fn` parameters", ImplTraitPosition::ExternFnParam => "`extern fn` parameters",

View File

@ -858,7 +858,9 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
use crate::session_diagnostics::CaptureVarCause::*; use crate::session_diagnostics::CaptureVarCause::*;
match kind { match kind {
hir::ClosureKind::Coroutine(_) => MoveUseInCoroutine { var_span }, hir::ClosureKind::Coroutine(_) => MoveUseInCoroutine { var_span },
hir::ClosureKind::Closure => MoveUseInClosure { var_span }, hir::ClosureKind::Closure | hir::ClosureKind::CoroutineClosure(_) => {
MoveUseInClosure { var_span }
}
} }
}); });
@ -905,7 +907,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
hir::ClosureKind::Coroutine(_) => { hir::ClosureKind::Coroutine(_) => {
BorrowUsePlaceCoroutine { place: desc_place, var_span, is_single_var: true } BorrowUsePlaceCoroutine { place: desc_place, var_span, is_single_var: true }
} }
hir::ClosureKind::Closure => { hir::ClosureKind::Closure | hir::ClosureKind::CoroutineClosure(_) => {
BorrowUsePlaceClosure { place: desc_place, var_span, is_single_var: true } BorrowUsePlaceClosure { place: desc_place, var_span, is_single_var: true }
} }
} }
@ -1056,7 +1058,8 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
var_span, var_span,
is_single_var: true, is_single_var: true,
}, },
hir::ClosureKind::Closure => BorrowUsePlaceClosure { hir::ClosureKind::Closure
| hir::ClosureKind::CoroutineClosure(_) => BorrowUsePlaceClosure {
place: desc_place, place: desc_place,
var_span, var_span,
is_single_var: true, is_single_var: true,
@ -1140,7 +1143,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
var_span, var_span,
is_single_var: false, is_single_var: false,
}, },
hir::ClosureKind::Closure => { hir::ClosureKind::Closure | hir::ClosureKind::CoroutineClosure(_) => {
BorrowUsePlaceClosure { place: desc_place, var_span, is_single_var: false } BorrowUsePlaceClosure { place: desc_place, var_span, is_single_var: false }
} }
} }
@ -1158,7 +1161,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
hir::ClosureKind::Coroutine(_) => { hir::ClosureKind::Coroutine(_) => {
FirstBorrowUsePlaceCoroutine { place: borrow_place_desc, var_span } FirstBorrowUsePlaceCoroutine { place: borrow_place_desc, var_span }
} }
hir::ClosureKind::Closure => { hir::ClosureKind::Closure | hir::ClosureKind::CoroutineClosure(_) => {
FirstBorrowUsePlaceClosure { place: borrow_place_desc, var_span } FirstBorrowUsePlaceClosure { place: borrow_place_desc, var_span }
} }
} }
@ -1175,7 +1178,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
hir::ClosureKind::Coroutine(_) => { hir::ClosureKind::Coroutine(_) => {
SecondBorrowUsePlaceCoroutine { place: desc_place, var_span } SecondBorrowUsePlaceCoroutine { place: desc_place, var_span }
} }
hir::ClosureKind::Closure => { hir::ClosureKind::Closure | hir::ClosureKind::CoroutineClosure(_) => {
SecondBorrowUsePlaceClosure { place: desc_place, var_span } SecondBorrowUsePlaceClosure { place: desc_place, var_span }
} }
} }
@ -2942,7 +2945,9 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
use crate::session_diagnostics::CaptureVarCause::*; use crate::session_diagnostics::CaptureVarCause::*;
match kind { match kind {
hir::ClosureKind::Coroutine(_) => BorrowUseInCoroutine { var_span }, hir::ClosureKind::Coroutine(_) => BorrowUseInCoroutine { var_span },
hir::ClosureKind::Closure => BorrowUseInClosure { var_span }, hir::ClosureKind::Closure | hir::ClosureKind::CoroutineClosure(_) => {
BorrowUseInClosure { var_span }
}
} }
}); });
@ -2958,7 +2963,9 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
use crate::session_diagnostics::CaptureVarCause::*; use crate::session_diagnostics::CaptureVarCause::*;
match kind { match kind {
hir::ClosureKind::Coroutine(_) => BorrowUseInCoroutine { var_span }, hir::ClosureKind::Coroutine(_) => BorrowUseInCoroutine { var_span },
hir::ClosureKind::Closure => BorrowUseInClosure { var_span }, hir::ClosureKind::Closure | hir::ClosureKind::CoroutineClosure(_) => {
BorrowUseInClosure { var_span }
}
} }
}); });

View File

@ -614,7 +614,7 @@ impl UseSpans<'_> {
PartialAssignment => AssignPartInCoroutine { path_span }, PartialAssignment => AssignPartInCoroutine { path_span },
}); });
} }
hir::ClosureKind::Closure => { hir::ClosureKind::Closure | hir::ClosureKind::CoroutineClosure(_) => {
err.subdiagnostic(match action { err.subdiagnostic(match action {
Borrow => BorrowInClosure { path_span }, Borrow => BorrowInClosure { path_span },
MatchOn | Use => UseInClosure { path_span }, MatchOn | Use => UseInClosure { path_span },
@ -1253,7 +1253,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
hir::ClosureKind::Coroutine(_) => { hir::ClosureKind::Coroutine(_) => {
CaptureVarCause::PartialMoveUseInCoroutine { var_span, is_partial } CaptureVarCause::PartialMoveUseInCoroutine { var_span, is_partial }
} }
hir::ClosureKind::Closure => { hir::ClosureKind::Closure | hir::ClosureKind::CoroutineClosure(_) => {
CaptureVarCause::PartialMoveUseInClosure { var_span, is_partial } CaptureVarCause::PartialMoveUseInClosure { var_span, is_partial }
} }
}) })

View File

@ -692,7 +692,10 @@ impl<'tcx> MirBorrowckCtxt<'_, 'tcx> {
hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared( hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(
hir::CoroutineDesugaring::Async, hir::CoroutineDesugaring::Async,
hir::CoroutineSource::Closure, hir::CoroutineSource::Closure,
)) => " of async closure", ))
| hir::ClosureKind::CoroutineClosure(hir::CoroutineDesugaring::Async) => {
" of async closure"
}
hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared( hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(
hir::CoroutineDesugaring::Async, hir::CoroutineDesugaring::Async,
@ -719,7 +722,10 @@ impl<'tcx> MirBorrowckCtxt<'_, 'tcx> {
hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared( hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(
hir::CoroutineDesugaring::Gen, hir::CoroutineDesugaring::Gen,
hir::CoroutineSource::Closure, hir::CoroutineSource::Closure,
)) => " of gen closure", ))
| hir::ClosureKind::CoroutineClosure(hir::CoroutineDesugaring::Gen) => {
" of gen closure"
}
hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared( hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(
hir::CoroutineDesugaring::Gen, hir::CoroutineDesugaring::Gen,
@ -743,7 +749,10 @@ impl<'tcx> MirBorrowckCtxt<'_, 'tcx> {
hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared( hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(
hir::CoroutineDesugaring::AsyncGen, hir::CoroutineDesugaring::AsyncGen,
hir::CoroutineSource::Closure, hir::CoroutineSource::Closure,
)) => " of async gen closure", ))
| hir::ClosureKind::CoroutineClosure(hir::CoroutineDesugaring::AsyncGen) => {
" of async gen closure"
}
hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared( hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(
hir::CoroutineDesugaring::AsyncGen, hir::CoroutineDesugaring::AsyncGen,

View File

@ -952,6 +952,11 @@ pub enum ClosureKind {
/// usage (e.g. `let x = || { yield (); }`) or from a desugared expression /// usage (e.g. `let x = || { yield (); }`) or from a desugared expression
/// (e.g. `async` and `gen` blocks). /// (e.g. `async` and `gen` blocks).
Coroutine(CoroutineKind), Coroutine(CoroutineKind),
/// This is a coroutine-closure, which is a special sugared closure that
/// returns one of the sugared coroutine (`async`/`gen`/`async gen`). It
/// additionally allows capturing the coroutine's upvars by ref, and therefore
/// needs to be specially treated during analysis and borrowck.
CoroutineClosure(CoroutineDesugaring),
} }
/// A block of statements `{ .. }`, which may have a label (in this case the /// A block of statements `{ .. }`, which may have a label (in this case the

View File

@ -349,6 +349,7 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics {
ClosureKind::Coroutine(_) => { ClosureKind::Coroutine(_) => {
&["<resume_ty>", "<yield_ty>", "<return_ty>", "<witness>", "<upvars>"][..] &["<resume_ty>", "<yield_ty>", "<return_ty>", "<witness>", "<upvars>"][..]
} }
ClosureKind::CoroutineClosure(_) => todo!(),
}; };
params.extend(dummy_args.iter().map(|&arg| ty::GenericParamDef { params.extend(dummy_args.iter().map(|&arg| ty::GenericParamDef {

View File

@ -192,6 +192,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
Some(CoroutineTypes { resume_ty, yield_ty }), Some(CoroutineTypes { resume_ty, yield_ty }),
) )
} }
hir::ClosureKind::CoroutineClosure(_) => todo!(),
}; };
check_fn( check_fn(
@ -690,7 +691,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
_, _,
)) ))
| hir::ClosureKind::Coroutine(hir::CoroutineKind::Coroutine(_)) | hir::ClosureKind::Coroutine(hir::CoroutineKind::Coroutine(_))
| hir::ClosureKind::Closure => astconv.ty_infer(None, decl.output.span()), | hir::ClosureKind::Closure
| hir::ClosureKind::CoroutineClosure(_) => {
astconv.ty_infer(None, decl.output.span())
}
}, },
}; };

View File

@ -1924,45 +1924,50 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
fn describe_closure(&self, kind: hir::ClosureKind) -> &'static str { fn describe_closure(&self, kind: hir::ClosureKind) -> &'static str {
match kind { match kind {
hir::ClosureKind::Closure => "a closure", hir::ClosureKind::Closure => "a closure",
hir::ClosureKind::Coroutine(kind) => match kind { hir::ClosureKind::Coroutine(hir::CoroutineKind::Coroutine(_)) => "a coroutine",
hir::CoroutineKind::Coroutine(_) => "a coroutine", hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(
hir::CoroutineKind::Desugared( hir::CoroutineDesugaring::Async,
hir::CoroutineDesugaring::Async, hir::CoroutineSource::Block,
hir::CoroutineSource::Block, )) => "an async block",
) => "an async block", hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(
hir::CoroutineKind::Desugared( hir::CoroutineDesugaring::Async,
hir::CoroutineDesugaring::Async, hir::CoroutineSource::Fn,
hir::CoroutineSource::Fn, )) => "an async function",
) => "an async function", hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(
hir::CoroutineKind::Desugared( hir::CoroutineDesugaring::Async,
hir::CoroutineDesugaring::Async, hir::CoroutineSource::Closure,
hir::CoroutineSource::Closure, ))
) => "an async closure", | hir::ClosureKind::CoroutineClosure(hir::CoroutineDesugaring::Async) => {
hir::CoroutineKind::Desugared( "an async closure"
hir::CoroutineDesugaring::AsyncGen, }
hir::CoroutineSource::Block, hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(
) => "an async gen block", hir::CoroutineDesugaring::AsyncGen,
hir::CoroutineKind::Desugared( hir::CoroutineSource::Block,
hir::CoroutineDesugaring::AsyncGen, )) => "an async gen block",
hir::CoroutineSource::Fn, hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(
) => "an async gen function", hir::CoroutineDesugaring::AsyncGen,
hir::CoroutineKind::Desugared( hir::CoroutineSource::Fn,
hir::CoroutineDesugaring::AsyncGen, )) => "an async gen function",
hir::CoroutineSource::Closure, hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(
) => "an async gen closure", hir::CoroutineDesugaring::AsyncGen,
hir::CoroutineKind::Desugared( hir::CoroutineSource::Closure,
hir::CoroutineDesugaring::Gen, ))
hir::CoroutineSource::Block, | hir::ClosureKind::CoroutineClosure(hir::CoroutineDesugaring::AsyncGen) => {
) => "a gen block", "an async gen closure"
hir::CoroutineKind::Desugared( }
hir::CoroutineDesugaring::Gen, hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(
hir::CoroutineSource::Fn, hir::CoroutineDesugaring::Gen,
) => "a gen function", hir::CoroutineSource::Block,
hir::CoroutineKind::Desugared( )) => "a gen block",
hir::CoroutineDesugaring::Gen, hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(
hir::CoroutineSource::Closure, hir::CoroutineDesugaring::Gen,
) => "a gen closure", hir::CoroutineSource::Fn,
}, )) => "a gen function",
hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(
hir::CoroutineDesugaring::Gen,
hir::CoroutineSource::Closure,
))
| hir::ClosureKind::CoroutineClosure(hir::CoroutineDesugaring::Gen) => "a gen closure",
} }
} }