diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yaml similarity index 100% rename from .github/workflows/tests.yml rename to .github/workflows/tests.yaml diff --git a/.readthedocs.yaml b/.readthedocs.yaml deleted file mode 100644 index ede665a04..000000000 --- a/.readthedocs.yaml +++ /dev/null @@ -1,16 +0,0 @@ -version: 2 - -build: - os: ubuntu-22.04 - tools: - python: "3.11" - -sphinx: - builder: html - configuration: docs/conf.py - -python: - install: - - method: pip - path: . - - requirements: requirements-dev.txt diff --git a/NEWS.rst b/NEWS.rst index bec831328..e82b571da 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -5,6 +5,10 @@ Unreleased Supports Python 3.9 – Python 3.x +New Features +------------------------------ +* New pragma `hy`. + 1.0.0 ("Afternoon Review", released 2024-09-22) ====================================================================== diff --git a/docs/api.rst b/docs/api.rst index 8193ebba8..54ab7ac86 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -169,9 +169,17 @@ Fundamentals The effect of each pragma is locally scoped to its containing function, class, or comprehension form (other than ``for``), if there is one. - Only one pragma is currently implemented: + These pragmata are currently implemented: -.. _warn-on-core-shadow: + - ``:hy``: Set this to a string giving a Hy version number or prefix thereof, + such as "1.1.0" or "1", to raise a compile-time error if the currently + executing version of Hy isn't at least this new. If you're writing a + package, you should still declare the required version of Hy in ``setup.py`` + or ``pyproject.toml`` or whatever, because ``pip`` won't look for ``(pragma + :hy …)`` calls. In the future, this pragma may also switch on features of Hy + that were introduced in or before the given version. + + .. _warn-on-core-shadow: - ``:warn-on-core-shadow``: If true (the default), :hy:func:`defmacro` and :hy:func:`require` will raise a warning at compile-time if you define a macro diff --git a/docs/versioning.rst b/docs/versioning.rst index 78e6060f1..23060275a 100644 --- a/docs/versioning.rst +++ b/docs/versioning.rst @@ -4,6 +4,6 @@ Versioning and compatibility Starting with Hy 1.0.0, Hy is `semantically versioned `_. Refer to `the NEWS file `_ 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. -Hy is tested on `all released and currently maintained versions of CPython `_ (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. +Hy is tested on `all released and currently maintained versions of CPython `_ (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. 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``. diff --git a/hy/__init__.py b/hy/__init__.py index 3b81e5a6b..4d0b68515 100644 --- a/hy/__init__.py +++ b/hy/__init__.py @@ -1,5 +1,9 @@ __version__ = 'unreleased' nickname = None +last_version = '1.0.0' + # This is used by `(pragma :hy …)` to guess whether an unreleased + # version of Hy is new enough. In a released version, it's simply + # equal to `__version__`. def _initialize_env_var(env_var, default_val): diff --git a/hy/core/result_macros.py b/hy/core/result_macros.py index 1636f177e..0b308037c 100644 --- a/hy/core/result_macros.py +++ b/hy/core/result_macros.py @@ -11,10 +11,11 @@ import ast import textwrap from contextlib import nullcontext -from itertools import dropwhile +from itertools import dropwhile, zip_longest from funcparserlib.parser import finished, forward_decl, many, maybe, oneplus, some +from hy import last_version from hy.compat import PY3_11, PY3_12 from hy.compiler import Result, asty, mkexpr from hy.errors import HyEvalError, HyInternalError, HyTypeError @@ -165,11 +166,31 @@ def compile_inline_python(compiler, expr, root, code): @pattern_macro("pragma", [many(KEYWORD + FORM)]) def compile_pragma(compiler, expr, root, kwargs): for kw, value in kwargs: - if kw == Keyword("warn-on-core-shadow"): + + if kw == Keyword("hy"): + min_version = compiler.eval(value) + if not isinstance(min_version, str): + raise compiler._syntax_error(value, "The version given to the pragma `:hy` must be a string") + parts = min_version.split('.') + if not all(p.isdigit() for p in parts): + raise compiler._syntax_error(value, "The string given to the pragma `:hy` must be a dot-separated sequence of integers") + for have, need in zip_longest( + map(int, last_version.split('.')), + map(int, parts)): + if need is None: + break + if have is None or have < need: + raise compiler._syntax_error(kw, f"Hy version {min_version} or later required") + if have > need: + break + + elif kw == Keyword("warn-on-core-shadow"): compiler.local_state_stack[-1]['warn_on_core_shadow'] = ( bool(compiler.eval(value))) + else: raise compiler._syntax_error(kw, f"Unknown pragma `{kw}`. Perhaps it's implemented by a newer version of Hy.") + return Result() @@ -565,17 +586,16 @@ def compile_global_or_nonlocal(compiler, expr, root, syms): return asty.Pass(expr) names = [mangle(s) for s in syms] - if root == "global": - ret = asty.Global(expr, names=names) - else: - ret = OuterVar(expr, compiler.scope, names) + ret = (asty.Global(expr, names = names) + if root == "global" else + OuterVar(expr, compiler.scope, names)) try: compiler.scope.define_nonlocal(ret, root) except SyntaxError as e: raise compiler._syntax_error(expr, e.msg) - return ret if syms else Result() + return ret @pattern_macro("del", [many(FORM)]) diff --git a/tests/native_tests/other.hy b/tests/native_tests/other.hy index 853dcebc8..223224b1f 100644 --- a/tests/native_tests/other.hy +++ b/tests/native_tests/other.hy @@ -6,7 +6,27 @@ importlib pydoc pytest - hy.errors [HyLanguageError]) + hy.errors [HyLanguageError HySyntaxError]) + + +(defn test-pragma-hy [] + + (pragma :hy "1") + (pragma :hy "1.0") + (pragma :hy "1.0.0") + (pragma :hy "0.28.0") + + (eval-when-compile (setv a-version "1.0")) + (pragma :hy a-version) + + (defn bad [v msg] + (with [e (pytest.raises HySyntaxError)] + (hy.eval `(pragma :hy ~v))) + (assert (in msg e.value.msg))) + (bad "5" "version 5 or later required") + (bad "1.99.1" "version 1.99.1 or later required") + (bad 5 "must be a string") + (bad "Afternoon Review" "must be a dot-separated sequence of integers")) (defn test-illegal-assignments []