@@ -123,6 +123,19 @@ internal override int KwOnlyArgCount {
123123 // through the generator state machine, so IsAsync no longer implies
124124 // generator-shaped emission.
125125 internal override bool IsGeneratorMethod => IsGenerator ;
126+
127+ // Async-generator (PEP 525) channels, one StrongBox per async-generator instance (per stack frame
128+ // at runtime — not static). Created when lowering the async generator; declared/assigned in the
129+ // function body, captured by the generator (the body's yields read them through Parent), and handed
130+ // to the PythonAsyncGenerator wrapper, which writes them before each resume:
131+ // AsyncSendSlot — the value of `x = yield z` (asend(v); None for __anext__/async for).
132+ // AsyncThrowSlot — an exception to rethrow at the yield resume point (athrow/aclose).
133+ private MSAst . ParameterExpression _asyncSendSlot ;
134+ private MSAst . ParameterExpression _asyncThrowSlot ;
135+ internal MSAst . ParameterExpression AsyncSendSlot
136+ => _asyncSendSlot ??= MSAst . Expression . Variable ( typeof ( System . Runtime . CompilerServices . StrongBox < object > ) , "$asyncSend" ) ;
137+ internal MSAst . ParameterExpression AsyncThrowSlot
138+ => _asyncThrowSlot ??= MSAst . Expression . Variable ( typeof ( System . Runtime . CompilerServices . StrongBox < Exception > ) , "$asyncThrow" ) ;
126139#else
127140 internal override bool IsGeneratorMethod => IsGenerator || IsAsync ;
128141#endif
@@ -372,7 +385,9 @@ internal MSAst.Expression MakeFunctionExpression() {
372385 )
373386 ) ,
374387#if FEATURE_NET_ASYNC
375- IsGenerator ?
388+ // Async generators are lowered via AsyncEnumerableExpression in the body, so they must not
389+ // be wrapped as a PythonGenerator here — only plain (non-async) generators are.
390+ ( IsGenerator && ! IsAsync ) ?
376391 ( MSAst . Expression ) new PythonGeneratorExpression ( code , GlobalParent . PyContext . Options . CompilationThreshold , IsAsync ) :
377392 ( MSAst . Expression ) code
378393#else
@@ -680,7 +695,9 @@ private LightLambdaExpression CreateFunctionLambda() {
680695 // The exception traceback needs to come from the generator's method body, and so we must do the check and throw
681696 // from inside the generator.
682697#if FEATURE_NET_ASYNC
683- if ( IsGenerator ) {
698+ // Async generators have no backing PythonGenerator (they lower to IAsyncEnumerable via
699+ // AsyncEnumerableExpression), so skip the $generator.CheckThrowable() prologue for them.
700+ if ( IsGenerator && ! IsAsync ) {
684701 MSAst . Expression s1 = YieldExpression . CreateCheckThrowExpression ( SourceSpan . None ) ;
685702 statements . Add ( s1 ) ;
686703 }
@@ -718,18 +735,42 @@ private LightLambdaExpression CreateFunctionLambda() {
718735 if ( IsAsync ) {
719736 var cts = MSAst . Expression . Variable ( typeof ( System . Threading . CancellationTokenSource ) , "$cts" ) ;
720737 var excBox = MSAst . Expression . Variable ( typeof ( System . Runtime . CompilerServices . StrongBox < Exception > ) , "$cancelExc" ) ;
721- body = MSAst . Expression . Block (
722- new [ ] { cts , excBox } ,
723- MSAst . Expression . Assign ( cts , MSAst . Expression . New ( typeof ( System . Threading . CancellationTokenSource ) ) ) ,
724- MSAst . Expression . Assign ( excBox , MSAst . Expression . New ( typeof ( System . Runtime . CompilerServices . StrongBox < Exception > ) ) ) ,
725- Ast . Call (
726- AstMethods . MakeAsyncCoroutine ,
727- _functionParam ,
728- AstUtils . Async ( Name , body ,
729- MSAst . Expression . Property ( cts , nameof ( System . Threading . CancellationTokenSource . Token ) ) ,
730- excBox ) ,
731- cts ,
732- excBox ) ) ;
738+ var ctToken = MSAst . Expression . Property ( cts , nameof ( System . Threading . CancellationTokenSource . Token ) ) ;
739+ if ( IsGenerator ) {
740+ // Async generator: the body has both `await` and `yield`. Lower it to an
741+ // IAsyncEnumerable<object?> via AsyncEnumerableExpression, sharing the generator label so
742+ // the body's yields and the rewritten awaits land in one generator, then wrap it in a
743+ // PythonAsyncGenerator. The send/throw slots are per-generator StrongBoxes captured by the
744+ // generator (the body's yields read them) AND handed to the wrapper, which writes them
745+ // before each resume — see AsyncSendSlot / AsyncThrowSlot.
746+ var sendSlot = AsyncSendSlot ;
747+ var throwSlot = AsyncThrowSlot ;
748+ body = MSAst . Expression . Block (
749+ new [ ] { cts , excBox , sendSlot , throwSlot } ,
750+ MSAst . Expression . Assign ( cts , MSAst . Expression . New ( typeof ( System . Threading . CancellationTokenSource ) ) ) ,
751+ MSAst . Expression . Assign ( excBox , MSAst . Expression . New ( typeof ( System . Runtime . CompilerServices . StrongBox < Exception > ) ) ) ,
752+ MSAst . Expression . Assign ( sendSlot , MSAst . Expression . New ( typeof ( System . Runtime . CompilerServices . StrongBox < object > ) ) ) ,
753+ MSAst . Expression . Assign ( throwSlot , MSAst . Expression . New ( typeof ( System . Runtime . CompilerServices . StrongBox < Exception > ) ) ) ,
754+ Ast . Call (
755+ AstMethods . MakeAsyncGenerator ,
756+ _functionParam ,
757+ AstUtils . AsyncEnumerable ( Name , body , GeneratorLabel , ctToken , excBox ) ,
758+ sendSlot ,
759+ throwSlot ,
760+ cts ) ) ;
761+ } else {
762+ // Plain async def: the body returns a PythonCoroutine wrapping a Task<object?>.
763+ body = MSAst . Expression . Block (
764+ new [ ] { cts , excBox } ,
765+ MSAst . Expression . Assign ( cts , MSAst . Expression . New ( typeof ( System . Threading . CancellationTokenSource ) ) ) ,
766+ MSAst . Expression . Assign ( excBox , MSAst . Expression . New ( typeof ( System . Runtime . CompilerServices . StrongBox < Exception > ) ) ) ,
767+ Ast . Call (
768+ AstMethods . MakeAsyncCoroutine ,
769+ _functionParam ,
770+ AstUtils . Async ( Name , body , ctToken , excBox ) ,
771+ cts ,
772+ excBox ) ) ;
773+ }
733774 }
734775#endif
735776
0 commit comments