Skip to content

Commit 7bdc5a8

Browse files
authored
Merge pull request #2616 from Kodiologist/pragma-hy
Add the `:hy` pragma
2 parents 5828ce0 + d2e2885 commit 7bdc5a8

File tree

8 files changed

+67
-27
lines changed

8 files changed

+67
-27
lines changed
File renamed without changes.

.readthedocs.yaml

Lines changed: 0 additions & 16 deletions
This file was deleted.

NEWS.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ Unreleased
55

66
Supports Python 3.9 – Python 3.x
77

8+
New Features
9+
------------------------------
10+
* New pragma `hy`.
11+
812
1.0.0 ("Afternoon Review", released 2024-09-22)
913
======================================================================
1014

docs/api.rst

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,17 @@ Fundamentals
169169
The effect of each pragma is locally scoped to its containing function,
170170
class, or comprehension form (other than ``for``), if there is one.
171171

172-
Only one pragma is currently implemented:
172+
These pragmata are currently implemented:
173173

174-
.. _warn-on-core-shadow:
174+
- ``:hy``: Set this to a string giving a Hy version number or prefix thereof,
175+
such as "1.1.0" or "1", to raise a compile-time error if the currently
176+
executing version of Hy isn't at least this new. If you're writing a
177+
package, you should still declare the required version of Hy in ``setup.py``
178+
or ``pyproject.toml`` or whatever, because ``pip`` won't look for ``(pragma
179+
:hy …)`` calls. In the future, this pragma may also switch on features of Hy
180+
that were introduced in or before the given version.
181+
182+
.. _warn-on-core-shadow:
175183

176184
- ``:warn-on-core-shadow``: If true (the default), :hy:func:`defmacro` and
177185
:hy:func:`require` will raise a warning at compile-time if you define a macro

docs/versioning.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ Versioning and compatibility
44

55
Starting with Hy 1.0.0, Hy is `semantically versioned <https://semver.org>`_. Refer to `the NEWS file <https://github.com/hylang/hy/blob/master/NEWS.rst>`_ for a summary of user-visible changes brought on by each version, and how to update your code in case of breaking changes. Be sure you're reading the version of this manual (shown at the top of each page) that matches the version of Hy you're running.
66

7-
Hy is tested on `all released and currently maintained versions of CPython <https://devguide.python.org/versions>`_ (on Linux, Windows, and Mac OS), and on recent versions of PyPy and Pyodide. We usually find that for Hy, unlike most Python packages, we need to change things to fully support each new 3.x release of Python. We may drop compatibility with a version of Python after the CPython guys cease maintaining it. Note that we construe such a change as non-breaking, so we won't bump Hy's major version for it. But we will at least bump the minor version, and ``python_requires`` in Hy's ``setup.py`` should prevent you from installing a Hy version that won't work with your Python version.
7+
Hy is tested on `all released and currently maintained versions of CPython <https://devguide.python.org/versions>`_ (on Linux, Windows, and Mac OS), and on recent versions of `PyPy <https://pypy.org>`_ and `Pyodide <https://pyodide.org>`_. We usually find that for Hy, unlike most Python packages, we need to change things to fully support each new 3.x release of Python. We may drop compatibility with a version of Python after the CPython guys cease maintaining it. Note that we construe such a change as non-breaking, so we won't bump Hy's major version for it. But we will at least bump the minor version, and ``python_requires`` in Hy's ``setup.py`` should prevent you from installing a Hy version that won't work with your Python version.
88

99
Starting with Hy 1.0.0, each version of Hy also has a nickname, such as "Afternoon Review". Nicknames are used in alphabetical order, with a nickname starting with "Z" then wrapping around to "A". Nicknames are provided mostly for the amusement of the maintainer, but can be useful as a conspicuous sign that you're not using the version you expected. In code, you can get the current nickname as a string (or ``None``, for unreleased commits of Hy) with ``hy.nickname``.

hy/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
__version__ = 'unreleased'
22
nickname = None
3+
last_version = '1.0.0'
4+
# This is used by `(pragma :hy …)` to guess whether an unreleased
5+
# version of Hy is new enough. In a released version, it's simply
6+
# equal to `__version__`.
37

48

59
def _initialize_env_var(env_var, default_val):

hy/core/result_macros.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@
1111
import ast
1212
import textwrap
1313
from contextlib import nullcontext
14-
from itertools import dropwhile
14+
from itertools import dropwhile, zip_longest
1515

1616
from funcparserlib.parser import finished, forward_decl, many, maybe, oneplus, some
1717

18+
from hy import last_version
1819
from hy.compat import PY3_11, PY3_12
1920
from hy.compiler import Result, asty, mkexpr
2021
from hy.errors import HyEvalError, HyInternalError, HyTypeError
@@ -165,11 +166,31 @@ def compile_inline_python(compiler, expr, root, code):
165166
@pattern_macro("pragma", [many(KEYWORD + FORM)])
166167
def compile_pragma(compiler, expr, root, kwargs):
167168
for kw, value in kwargs:
168-
if kw == Keyword("warn-on-core-shadow"):
169+
170+
if kw == Keyword("hy"):
171+
min_version = compiler.eval(value)
172+
if not isinstance(min_version, str):
173+
raise compiler._syntax_error(value, "The version given to the pragma `:hy` must be a string")
174+
parts = min_version.split('.')
175+
if not all(p.isdigit() for p in parts):
176+
raise compiler._syntax_error(value, "The string given to the pragma `:hy` must be a dot-separated sequence of integers")
177+
for have, need in zip_longest(
178+
map(int, last_version.split('.')),
179+
map(int, parts)):
180+
if need is None:
181+
break
182+
if have is None or have < need:
183+
raise compiler._syntax_error(kw, f"Hy version {min_version} or later required")
184+
if have > need:
185+
break
186+
187+
elif kw == Keyword("warn-on-core-shadow"):
169188
compiler.local_state_stack[-1]['warn_on_core_shadow'] = (
170189
bool(compiler.eval(value)))
190+
171191
else:
172192
raise compiler._syntax_error(kw, f"Unknown pragma `{kw}`. Perhaps it's implemented by a newer version of Hy.")
193+
173194
return Result()
174195

175196

@@ -565,17 +586,16 @@ def compile_global_or_nonlocal(compiler, expr, root, syms):
565586
return asty.Pass(expr)
566587

567588
names = [mangle(s) for s in syms]
568-
if root == "global":
569-
ret = asty.Global(expr, names=names)
570-
else:
571-
ret = OuterVar(expr, compiler.scope, names)
589+
ret = (asty.Global(expr, names = names)
590+
if root == "global" else
591+
OuterVar(expr, compiler.scope, names))
572592

573593
try:
574594
compiler.scope.define_nonlocal(ret, root)
575595
except SyntaxError as e:
576596
raise compiler._syntax_error(expr, e.msg)
577597

578-
return ret if syms else Result()
598+
return ret
579599

580600

581601
@pattern_macro("del", [many(FORM)])

tests/native_tests/other.hy

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,27 @@
66
importlib
77
pydoc
88
pytest
9-
hy.errors [HyLanguageError])
9+
hy.errors [HyLanguageError HySyntaxError])
10+
11+
12+
(defn test-pragma-hy []
13+
14+
(pragma :hy "1")
15+
(pragma :hy "1.0")
16+
(pragma :hy "1.0.0")
17+
(pragma :hy "0.28.0")
18+
19+
(eval-when-compile (setv a-version "1.0"))
20+
(pragma :hy a-version)
21+
22+
(defn bad [v msg]
23+
(with [e (pytest.raises HySyntaxError)]
24+
(hy.eval `(pragma :hy ~v)))
25+
(assert (in msg e.value.msg)))
26+
(bad "5" "version 5 or later required")
27+
(bad "1.99.1" "version 1.99.1 or later required")
28+
(bad 5 "must be a string")
29+
(bad "Afternoon Review" "must be a dot-separated sequence of integers"))
1030

1131

1232
(defn test-illegal-assignments []

0 commit comments

Comments
 (0)