-
Notifications
You must be signed in to change notification settings - Fork 310
Implement PEP 492: async/await support #2004
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
09f335b
a82d993
826dfeb
a1c1605
04b48e5
5a09b4d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the Apache 2.0 License. | ||
| // See the LICENSE file in the project root for more information. | ||
|
|
||
| #nullable enable | ||
|
|
||
| using System.Threading; | ||
|
|
||
| using Microsoft.Scripting; | ||
| using MSAst = System.Linq.Expressions; | ||
|
|
||
| namespace IronPython.Compiler.Ast { | ||
|
|
||
| /// <summary> | ||
| /// Represents an async for statement. | ||
| /// Desugared to Python AST that uses __aiter__ and await __anext__(). | ||
| /// </summary> | ||
| public class AsyncForStatement : Statement, ILoopStatement { | ||
| private static int _counter; | ||
| private Statement? _desugared; | ||
|
|
||
| public AsyncForStatement(Expression left, Expression list, Statement body, Statement? @else) { | ||
| Left = left; | ||
| List = list; | ||
| Body = body; | ||
| Else = @else; | ||
| } | ||
|
|
||
| public int HeaderIndex { private get; set; } | ||
|
|
||
| public Expression Left { get; } | ||
|
|
||
| public Expression List { get; set; } | ||
|
|
||
| public Statement Body { get; set; } | ||
|
|
||
| public Statement? Else { get; } | ||
|
|
||
| MSAst.LabelTarget ILoopStatement.BreakLabel { get; set; } = null!; | ||
|
|
||
| MSAst.LabelTarget ILoopStatement.ContinueLabel { get; set; } = null!; | ||
|
|
||
| /// <summary> | ||
| /// Build the desugared tree. Called during Walk when Parent and IndexSpan are available. | ||
| /// </summary> | ||
| private Statement BuildDesugared() { | ||
| var parent = Parent; | ||
| var span = IndexSpan; | ||
| var id = Interlocked.Increment(ref _counter); | ||
|
|
||
| // async for TARGET in ITER: | ||
| // BLOCK | ||
| // else: | ||
| // ELSE_BLOCK | ||
| // | ||
| // desugars to: | ||
| // | ||
| // __aiter = ITER.__aiter__() | ||
| // __running = True | ||
| // while __running: | ||
| // try: | ||
| // TARGET = await __aiter.__anext__() | ||
| // except StopAsyncIteration: | ||
| // __running = False | ||
| // else: | ||
| // BLOCK | ||
| // else: | ||
| // ELSE_BLOCK | ||
|
|
||
| var iterName = $"__asyncfor_iter{id}"; | ||
| var runningName = $"__asyncfor_running{id}"; | ||
|
|
||
| // Helper to create nodes with proper parent and span | ||
| NameExpression MakeName(string name) { | ||
| var n = new NameExpression(name) { Parent = parent }; | ||
| n.IndexSpan = span; | ||
| return n; | ||
| } | ||
|
|
||
| T WithSpan<T>(T node) where T : Node { | ||
| node.IndexSpan = span; | ||
| return node; | ||
| } | ||
|
|
||
| // _iter = ITER.__aiter__() | ||
| var aiterAttr = WithSpan(new MemberExpression(List, "__aiter__") { Parent = parent }); | ||
| var aiterCall = WithSpan(new CallExpression(aiterAttr, null, null) { Parent = parent }); | ||
| var assignIter = WithSpan(new AssignmentStatement(new Expression[] { MakeName(iterName) }, aiterCall) { Parent = parent }); | ||
|
|
||
| // running = True | ||
| var trueConst = new ConstantExpression(true) { Parent = parent }; trueConst.IndexSpan = span; | ||
| var assignRunning = WithSpan(new AssignmentStatement(new Expression[] { MakeName(runningName) }, trueConst) { Parent = parent }); | ||
|
|
||
| // TARGET = await __aiter.__anext__() | ||
| var anextAttr = WithSpan(new MemberExpression(MakeName(iterName), "__anext__") { Parent = parent }); | ||
| var anextCall = WithSpan(new CallExpression(anextAttr, null, null) { Parent = parent }); | ||
| var awaitNext = new AwaitExpression(anextCall); | ||
| var assignTarget = WithSpan(new AssignmentStatement(new Expression[] { Left }, awaitNext) { Parent = parent }); | ||
|
|
||
| // except StopAsyncIteration: __running = False | ||
| var falseConst = new ConstantExpression(false) { Parent = parent }; falseConst.IndexSpan = span; | ||
| var stopRunning = WithSpan(new AssignmentStatement( | ||
| new Expression[] { MakeName(runningName) }, falseConst) { Parent = parent }); | ||
| var handler = WithSpan(new TryStatementHandler( | ||
| MakeName("StopAsyncIteration"), | ||
| null!, | ||
| WithSpan(new SuiteStatement(new Statement[] { stopRunning }) { Parent = parent }) | ||
| ) { Parent = parent }); | ||
| handler.HeaderIndex = span.End; | ||
|
|
||
| // try/except/else block | ||
| var tryExcept = WithSpan(new TryStatement( | ||
| assignTarget, | ||
| new[] { handler }, | ||
| WithSpan(new SuiteStatement(new Statement[] { Body }) { Parent = parent }), | ||
| null! | ||
| ) { Parent = parent }); | ||
| tryExcept.HeaderIndex = span.End; | ||
|
|
||
| // while __running: try/except/else | ||
| var whileStmt = new WhileStatement(MakeName(runningName), tryExcept, Else); | ||
| whileStmt.SetLoc(GlobalParent, span.Start, span.End, span.End); | ||
| whileStmt.Parent = parent; | ||
|
|
||
| var suite = WithSpan(new SuiteStatement(new Statement[] { assignIter, assignRunning, whileStmt }) { Parent = parent }); | ||
| return suite; | ||
| } | ||
|
|
||
| public override MSAst.Expression Reduce() { | ||
| return _desugared!.Reduce(); | ||
| } | ||
|
|
||
| public override void Walk(PythonWalker walker) { | ||
| if (walker.Walk(this)) { | ||
| // Build the desugared tree on first walk (when Parent and IndexSpan are set) | ||
| if (_desugared == null) { | ||
| _desugared = BuildDesugared(); | ||
| } | ||
| _desugared.Walk(walker); | ||
| } | ||
| walker.PostWalk(this); | ||
| } | ||
|
|
||
| internal override bool CanThrow => true; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the Apache 2.0 License. | ||
| // See the LICENSE file in the project root for more information. | ||
|
|
||
| #nullable enable | ||
|
|
||
| using Microsoft.Scripting; | ||
| using MSAst = System.Linq.Expressions; | ||
|
|
||
| using AstUtils = Microsoft.Scripting.Ast.Utils; | ||
|
|
||
| namespace IronPython.Compiler.Ast { | ||
| using Ast = MSAst.Expression; | ||
|
|
||
| /// <summary> | ||
| /// Represents an async with statement. | ||
| /// Desugared to Python AST that uses await on __aenter__ and __aexit__. | ||
| /// </summary> | ||
| public class AsyncWithStatement : Statement { | ||
| private Statement? _desugared; | ||
|
|
||
| public AsyncWithStatement(Expression contextManager, Expression? var, Statement body) { | ||
| ContextManager = contextManager; | ||
| Variable = var; | ||
| Body = body; | ||
| } | ||
|
|
||
| public int HeaderIndex { private get; set; } | ||
|
|
||
| public Expression ContextManager { get; } | ||
|
|
||
| public new Expression? Variable { get; } | ||
|
|
||
| public Statement Body { get; } | ||
|
|
||
| /// <summary> | ||
| /// Build the desugared tree. Called during Walk when Parent and IndexSpan are available. | ||
| /// </summary> | ||
| private Statement BuildDesugared() { | ||
| var parent = Parent; | ||
| var span = IndexSpan; | ||
|
|
||
| // async with EXPR as VAR: | ||
| // BLOCK | ||
| // | ||
| // desugars to: | ||
| // | ||
| // mgr = EXPR | ||
| // try: | ||
| // VAR = await mgr.__aenter__() (or just await mgr.__aenter__()) | ||
| // BLOCK | ||
| // finally: | ||
| // await mgr.__aexit__(None, None, None) | ||
|
|
||
| // Helper to create nodes with proper parent and span | ||
| NameExpression MakeName(string name) { | ||
| var n = new NameExpression(name) { Parent = parent }; | ||
| n.IndexSpan = span; | ||
| return n; | ||
| } | ||
|
|
||
| // mgr = EXPR | ||
| var assignMgr = new AssignmentStatement(new Expression[] { MakeName("__asyncwith_mgr") }, ContextManager) { Parent = parent }; | ||
| assignMgr.IndexSpan = span; | ||
|
|
||
| // await mgr.__aenter__() | ||
| var aenterAttr = new MemberExpression(MakeName("__asyncwith_mgr"), "__aenter__") { Parent = parent }; | ||
| aenterAttr.IndexSpan = span; | ||
| var aenterCall = new CallExpression(aenterAttr, null, null) { Parent = parent }; | ||
| aenterCall.IndexSpan = span; | ||
| var awaitEnter = new AwaitExpression(aenterCall); | ||
|
|
||
| Statement bodyStmt; | ||
| if (Variable != null) { | ||
| // VAR = await value; BLOCK | ||
| var assignVar = new AssignmentStatement(new Expression[] { Variable }, awaitEnter) { Parent = parent }; | ||
| assignVar.IndexSpan = span; | ||
| bodyStmt = new SuiteStatement(new Statement[] { assignVar, Body }) { Parent = parent }; | ||
| } else { | ||
| var exprStmt = new ExpressionStatement(awaitEnter) { Parent = parent }; | ||
| exprStmt.IndexSpan = span; | ||
| bodyStmt = new SuiteStatement(new Statement[] { exprStmt, Body }) { Parent = parent }; | ||
| } | ||
|
|
||
| // await mgr.__aexit__(None, None, None) | ||
| var aexitAttr = new MemberExpression(MakeName("__asyncwith_mgr"), "__aexit__") { Parent = parent }; | ||
| aexitAttr.IndexSpan = span; | ||
| var none1 = new ConstantExpression(null) { Parent = parent }; none1.IndexSpan = span; | ||
| var none2 = new ConstantExpression(null) { Parent = parent }; none2.IndexSpan = span; | ||
| var none3 = new ConstantExpression(null) { Parent = parent }; none3.IndexSpan = span; | ||
| var aexitCallNormal = new CallExpression(aexitAttr, | ||
| new Expression[] { none1, none2, none3 }, null) { Parent = parent }; | ||
| aexitCallNormal.IndexSpan = span; | ||
| var awaitExitNormal = new AwaitExpression(aexitCallNormal); | ||
|
|
||
| // try/finally: await __aexit__ on normal exit | ||
| var finallyExprStmt = new ExpressionStatement(awaitExitNormal) { Parent = parent }; | ||
| finallyExprStmt.IndexSpan = span; | ||
| var tryFinally = new TryStatement(bodyStmt, null, null, finallyExprStmt) { Parent = parent }; | ||
| tryFinally.IndexSpan = span; | ||
|
Comment on lines
+85
to
+100
|
||
| tryFinally.HeaderIndex = span.End; | ||
|
|
||
| var suite = new SuiteStatement(new Statement[] { assignMgr, tryFinally }) { Parent = parent }; | ||
| suite.IndexSpan = span; | ||
| return suite; | ||
| } | ||
|
|
||
| public override MSAst.Expression Reduce() { | ||
| return _desugared!.Reduce(); | ||
| } | ||
|
|
||
| public override void Walk(PythonWalker walker) { | ||
| if (walker.Walk(this)) { | ||
| // Build the desugared tree on first walk (when Parent and IndexSpan are set) | ||
| if (_desugared == null) { | ||
| _desugared = BuildDesugared(); | ||
| } | ||
| _desugared.Walk(walker); | ||
| } | ||
| walker.PostWalk(this); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the Apache 2.0 License. | ||
| // See the LICENSE file in the project root for more information. | ||
|
|
||
| #nullable enable | ||
|
|
||
| using MSAst = System.Linq.Expressions; | ||
|
|
||
| using AstUtils = Microsoft.Scripting.Ast.Utils; | ||
|
|
||
| namespace IronPython.Compiler.Ast { | ||
| using Ast = MSAst.Expression; | ||
|
|
||
| /// <summary> | ||
| /// Represents an await expression. Implemented as yield from expr.__await__(). | ||
| /// </summary> | ||
| public class AwaitExpression : Expression { | ||
| private readonly Statement _statement; | ||
| private readonly NameExpression _result; | ||
|
|
||
| public AwaitExpression(Expression expression) { | ||
| Expression = expression; | ||
|
|
||
| // await expr is equivalent to yield from expr.__await__() | ||
| // We build: __awaitprefix_EXPR = expr; yield from __awaitprefix_EXPR.__await__(); __awaitprefix_r = __yieldfromprefix_r | ||
| var parent = expression.Parent; | ||
|
|
||
| var awaitableExpr = new NameExpression("__awaitprefix_EXPR") { Parent = parent }; | ||
| var getAwait = new MemberExpression(awaitableExpr, "__await__") { Parent = parent }; | ||
| var callAwait = new CallExpression(getAwait, null, null) { Parent = parent }; | ||
| var yieldFrom = new YieldFromExpression(callAwait); | ||
|
|
||
| Statement s1 = new AssignmentStatement(new Expression[] { new NameExpression("__awaitprefix_EXPR") { Parent = parent } }, expression) { Parent = parent }; | ||
| Statement s2 = new ExpressionStatement(yieldFrom) { Parent = parent }; | ||
| Statement s3 = new AssignmentStatement( | ||
| new Expression[] { new NameExpression("__awaitprefix_r") { Parent = parent } }, | ||
| new NameExpression("__yieldfromprefix_r") { Parent = parent } | ||
| ) { Parent = parent }; | ||
|
|
||
| _statement = new SuiteStatement(new Statement[] { s1, s2, s3 }) { Parent = parent }; | ||
|
|
||
| _result = new NameExpression("__awaitprefix_r") { Parent = parent }; | ||
| } | ||
|
|
||
| public Expression Expression { get; } | ||
|
|
||
| public override MSAst.Expression Reduce() { | ||
| return Ast.Block( | ||
| typeof(object), | ||
| _statement, | ||
| AstUtils.Convert(_result, typeof(object)) | ||
| ).Reduce(); | ||
| } | ||
|
|
||
| public override void Walk(PythonWalker walker) { | ||
| if (walker.Walk(this)) { | ||
| Expression?.Walk(walker); | ||
| _statement.Walk(walker); | ||
| _result.Walk(walker); | ||
| } | ||
| walker.PostWalk(this); | ||
| } | ||
|
|
||
| public override string NodeName => "await expression"; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
async fordesugaring doesn't perform iterator finalization on early loop exit (e.g.,break,return, or exception). In CPython,async forensuresaclose()is awaited for async generators / async iterators that provide it, to avoid leaking resources. Consider wrapping the loop in atry/finallythat conditionally awaits__asyncfor_iter?.aclose()when the loop is exited prematurely.