diff --git a/NEWS.rst b/NEWS.rst index 4d6eacd08..33142287b 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -11,6 +11,8 @@ Bug Fixes ------------------------------ * Fixed a crash on Python 3.12.6. * Keyword objects can now be compared to each other with `<` etc. +* The order of evaluation in multi-item `with`\s now matches that of + nested one-item `with`\s. * Fixed a bug in which the REPL misinterpreted the symbol `pass`. 0.29.0 (released 2024-05-20) diff --git a/hy/core/result_macros.py b/hy/core/result_macros.py index 573e8167f..1636f177e 100644 --- a/hy/core/result_macros.py +++ b/hy/core/result_macros.py @@ -1116,11 +1116,12 @@ def compile_with_expression(compiler, expr, root, args, body): expr, targets=[name], value=asty.Constant(expr, value=None) ) + [args] = args ret = Result(stmts=[initial_assign]) items = [] was_async = None cbody = None - for i, (is_async, variable, ctx) in enumerate(args[0]): + for i, (is_async, variable, ctx) in enumerate(args): is_async = bool(is_async) if was_async is None: was_async = is_async @@ -1128,10 +1129,26 @@ def compile_with_expression(compiler, expr, root, args, body): # We're compiling a `with` that mixes synchronous and # asynchronous context managers. Python doesn't support # this directly, so start a new `with` inside the body. - cbody = compile_with_expression(compiler, expr, root, [args[0][i:]], body) + cbody = compile_with_expression(compiler, expr, root, [args[i:]], body) break - ctx = compiler.compile(ctx) - ret += ctx + if not isinstance(ctx, Result): + # In a non-recursive call, `ctx` has not yet been compiled, + # and thus is not yet a `Result`. + ctx = compiler.compile(ctx) + if i == 0: + ret += ctx + elif ctx.stmts: + # We need to include some statements as part of this + # context manager, but this `with` already has at + # least one prior context manager. So, put our + # statements in the body and then start a new `with`. + cbody = ctx + compile_with_expression( + compiler, + expr, + root, + [((is_async, variable, ctx), *args[i + 1:])], + body) + break variable = ( None if variable == Symbol("_") diff --git a/tests/native_tests/with.hy b/tests/native_tests/with.hy index 2c4f281bd..94d26bce9 100644 --- a/tests/native_tests/with.hy +++ b/tests/native_tests/with.hy @@ -1,5 +1,6 @@ (import asyncio + unittest.mock [Mock] pytest tests.resources [async-test AsyncWithTest async-exits]) @@ -106,3 +107,17 @@ (setv w (with [(SuppressZDE)] (.append l w) (/ 1 0) 5)) (assert (is w None)) (assert (= l [7]))) + +(defn test-statements [] + + (setv m (Mock)) + (with [t (do (m) (WithTest 2))] + (setv out t)) + (assert (= m.call-count 1)) + (assert (= out 2)) + + ; https://github.com/hylang/hy/issues/2605 + (with [t1 (WithTest 1) t2 (do (setv foo t1) (WithTest 2))] + (setv out [t1 t2])) + (assert (= out [1 2])) + (assert (= foo 1)))