Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 14 additions & 17 deletions NEWS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,32 @@

Hy is `semantically versioned <https://semver.org/>`__ since 1.0.0.

Unreleased
1.3.0 ("Dogs Should Be Raw", released 2026-05-24)
======================================================================

Supports Python 3.x – Python 3.y
Supports Python 3.9 – Python 3.15

New Features
------------------------------
* Added t-strings, and a pragma `bracketed-templates`.
* Unpacking is now supported in comprehensions (PEP 798), even on
Pythons < 3.15.
* ``(import :lazy …)`` is allowed on Pythons ≥ 3.15 (PEP 810).
* ``hyc`` now supports ``-q``/``--quiet`` to suppress progress messages.
* Added support for t-strings from Python 3.14.
* Added new pragma `bracketed-templates` to allow parsing `#[t[...]t]` and `#[t-<ident>[...]t-<ident>]` as template strings (analogous to bracketed f-strings).
* Added ``hy-repr``\s for ``Template`` and ``Interpolation``.
* `(import :lazy …)` is allowed on Pythons ≥ 3.15 (PEP 810).
* Added `hy-repr`\s for `Template`, `Interpolation`, and `frozendict`.
* `hyc` has a new command-line option `--quiet`.

Bug Fixes
------------------------------

* Fixed `_could_be_hy_src` in `importer.py` incorrectly returning
`False` for valid Hy source paths that do not exist on disk, such
as ``dfile`` override paths passed to `py_compile.compile`; Closes #2701.
* Captured exception variables are now properly scoped.
* Invalid conversion characters in f-strings now properly raise a
syntax error.
* Variables set by `(except …)` are now properly scoped.
* Fixed a crash when using `require` in `hy.eval`.
* Fixed some compilation failures for asynchronous comprehension forms.
* More model types (`String`, `Bytes`, `Symbol`, `Integer`, `Float`,
`Complex`) now work properly with `match`.
* Fixed a bug that could cause `py_compile.compile` to read Hy as Python.
* Fixed bugs in `hy.model-patterns.whole` (and other combinators that
use it, like `brackets`) in which tuples were not always produced.
* Added pattern matching support to String, Bytes, Symbol, Integer, Float,
and Complex models.
* Fixed an importlib exception when using `hy.eval` on code that
required other modules.
* Invalid conversion chars in f-strings now properly raise a syntax error.

1.2.0 ("Crackers and Snacks", released 2026-01-14)
======================================================================
Expand Down
51 changes: 29 additions & 22 deletions hy/core/result_macros.py
Original file line number Diff line number Diff line change
Expand Up @@ -916,10 +916,11 @@ def compile_comprehension(compiler, expr, root, parts, final):
# The desired comprehension can't be expressed as a
# real Python comprehension. We'll write it as a nested
# loop in a function instead.
any_async = False
def f(parts):
# This function is called recursively to construct
# the nested loop.
nonlocal elt, ends_with_unpack
nonlocal elt, ends_with_unpack, any_async
if not parts:
if is_for:
if body:
Expand Down Expand Up @@ -958,6 +959,7 @@ def f(parts):
if tagname in ("for", "afor"):
orelse = orel and orel.pop().stmts
node = asty.AsyncFor if tagname == "afor" else asty.For
any_async = any_async or tagname == "afor"
return v[1] + node(
v[1],
target=v[0],
Expand Down Expand Up @@ -1022,7 +1024,10 @@ def f(parts):
expr, test=asty.Constant(expr, value=False), body=if_body, orelse=[]
)

ret += asty.FunctionDef(
body = f(parts).stmts
# `f` needs to be called before the next line so
# `any_async` is set early enough.
ret += (asty.AsyncFunctionDef if any_async else asty.FunctionDef)(
expr,
name=fname,
args=ast.arguments(
Expand All @@ -1034,31 +1039,33 @@ def f(parts):
kw_defaults=[],
defaults=[],
),
body=stmts + f(parts).stmts,
body=stmts + body,
decorator_list=[],
**({"type_params": []} if PY3_12 else {}),
)
# Immediately call the new function. Unless the user asked
# for a generator, wrap the call in `[].__class__(...)` or
# `{}.__class__(...)` or `{1}.__class__(...)` to get the
# right type. We don't want to just use e.g. `list(...)`
# because the name `list` might be rebound.
return ret + Result(
expr=asty.parse(
expr,
"{}({}())".format(
{
asty.ListComp: "[].__class__",
asty.DictComp: "{}.__class__",
asty.SetComp: "{1}.__class__",
asty.GeneratorExp: "",
}[node_class],
# for a generator, wrap the call in another comprehension
# to get the right type. We don't want to just use e.g.
# `list(...)` because the name `list` might be rebound, and it
# doesn't work with async generators.
brackets = "[]" if node_class is asty.ListComp else "{}"
if node_class is asty.DictComp:
v1, v2 = compiler.get_anon_var(), compiler.get_anon_var()
v1, v2 = f"{v1}: {v2}", f"{v1}, {v2}"
else:
v1 = v2 = compiler.get_anon_var()
return ret + Result(expr =
asty.parse(expr,
f"{fname}()"
if node_class is asty.GeneratorExp else
"{}{} {} for {} in {}(){}".format(
brackets[0],
v1,
"async" if any_async else "",
v2,
fname,
),
)
.body[0]
.value
)
brackets[1]))
.body[0].value)

# We can produce a real comprehension.
generators = []
Expand Down
16 changes: 16 additions & 0 deletions tests/native_tests/comprehensions.hy
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,22 @@
(assert (= (next g) "last"))))))


(defn test-async-lfor []
; https://github.com/hylang/hy/issues/2712
(do-mac (lfor statementize [False True] `(do
(defn :async top []
(defn :async numbers []
(for [i [0 1 2]]
(yield i)))
(setv g (lfor
:async i (numbers)
~@(when statementize '[:do (print "hi")])
(+ i 10)))
(print g)
g)
(assert (= (asyncio.run (top)) [10 11 12]))))))


(defn test-raise-in-comp []
(defclass E [Exception] [])
(setv l [])
Expand Down
Loading