From c9796af071afda018fadc1e820585bb39122dbb0 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Tue, 8 Aug 2023 14:30:07 -0400 Subject: [PATCH 1/4] Small documentation improvements --- docs/api.rst | 6 +++--- docs/syntax.rst | 4 +++- hy/models.py | 6 +++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 240dc355e..2de2cba55 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -67,17 +67,17 @@ base names, such that ``hy.core.macros.foo`` can be called as just ``foo``. As a convenience, ``.`` supports two other kinds of arguments in place of a plain attribute. A parenthesized expression is understood as a method call: - ``(. foo (bar a b))`` compiles to ``x.foo.bar(a, b)``. A bracketed form is + ``(. foo (bar a b))`` compiles to ``foo.bar(a, b)``. A bracketed form is understood as a subscript: ``(. foo ["bar"])`` compiles to ``foo["bar"]``. All these options can be mixed and matched in a single ``.`` call, so :: - (. a (b 1 2) c [d] [(e)]) + (. a (b 1 2) c [d] [(e 3 4)]) compiles to .. code-block:: python - a.b(1, 2).c[d][e()] + a.b(1, 2).c[d][e(3, 4)] :ref:`Dotted identifiers ` provide syntactic sugar for common uses of this macro. In particular, syntax like ``foo.bar`` ends up diff --git a/docs/syntax.rst b/docs/syntax.rst index 5773cbb73..6b051878d 100644 --- a/docs/syntax.rst +++ b/docs/syntax.rst @@ -189,7 +189,9 @@ equivalent to ``{"a" 1 "b" 2}``, which is different from ``{:a 1 :b 2}`` (see :ref:`dict-literals`). The empty keyword ``:`` is syntactically legal, but you can't compile a -function call with an empty keyword argument. +function call with an empty keyword argument due to Python limitations. Thus +``(foo : 3)`` must be rewritten to use runtime unpacking, as in +``(foo #** {"" 3})``. .. autoclass:: hy.models.Keyword :members: __bool__, __call__ diff --git a/hy/models.py b/hy/models.py index abe9701d8..4ddf06812 100644 --- a/hy/models.py +++ b/hy/models.py @@ -203,7 +203,7 @@ class Symbol(Object, str): """ Represents a symbol. - Symbol objects behave like strings under operations like :hy:func:`get`, + Symbol objects behave like strings under operations like :hy:func:`get `, :func:`len`, and :class:`bool`; in particular, ``(bool (hy.models.Symbol "False"))`` is true. Use :hy:func:`hy.eval` to evaluate a symbol. """ @@ -283,8 +283,8 @@ def __call__(self, data, default=_sentinel): :class:`hy.models.Keyword` objects). The optional second parameter is a default value; if provided, any - :class:`KeyError` from :hy:func:`get` will be caught, and the default returned - instead.""" + :class:`KeyError` from :hy:func:`get ` will be caught, + and the default returned instead.""" from hy.reader import mangle From 9b162c5de9c6c34c04e105927acbde04b6706b29 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Tue, 8 Aug 2023 14:59:23 -0400 Subject: [PATCH 2/4] Simplify some gensym tests --- tests/native_tests/hy_misc.hy | 8 ++++---- tests/native_tests/macros.hy | 35 +++++++++++------------------------ 2 files changed, 15 insertions(+), 28 deletions(-) diff --git a/tests/native_tests/hy_misc.hy b/tests/native_tests/hy_misc.hy index 63c0492b1..124aad571 100644 --- a/tests/native_tests/hy_misc.hy +++ b/tests/native_tests/hy_misc.hy @@ -8,12 +8,12 @@ (defn test-gensym [] (setv s1 (hy.gensym)) (assert (isinstance s1 hy.models.Symbol)) - (assert (= 0 (.find s1 "_G\uffff"))) + (assert (.startswith s1 "_G\uffff")) (setv s2 (hy.gensym "xx")) (setv s3 (hy.gensym "xx")) - (assert (= 0 (.find s2 "_xx\uffff"))) - (assert (not (= s2 s3))) - (assert (not (= (str s2) (str s3))))) + (assert (.startswith s2 "_xx\uffff")) + (assert (!= s2 s3)) + (assert (!= (str s2) (str s3)))) (defmacro mac [x expr] diff --git a/tests/native_tests/macros.hy b/tests/native_tests/macros.hy index 01bc44365..f35789988 100644 --- a/tests/native_tests/macros.hy +++ b/tests/native_tests/macros.hy @@ -120,31 +120,18 @@ (assert initialized) (assert (test-initialized)) + +(defmacro gensym-example [] + `(setv ~(hy.gensym) 1)) + (defn test-gensym-in-macros [] - (import ast) - (import hy.compiler [hy-compile]) - (import hy.reader [read-many]) - (setv macro1 "(defmacro nif [expr pos zero neg] - (setv g (hy.gensym)) - `(do - (setv ~g ~expr) - (cond (> ~g 0) ~pos - (= ~g 0) ~zero - (< ~g 0) ~neg))) - - (print (nif (inc -1) 1 0 -1)) - ") - ;; expand the macro twice, should use a different - ;; gensym each time - (setv _ast1 (hy-compile (read-many macro1) __name__)) - (setv _ast2 (hy-compile (read-many macro1) __name__)) - (setv s1 (ast.unparse _ast1)) - (setv s2 (ast.unparse _ast2)) - ;; and make sure there is something new that starts with _G\uffff - (assert (in (hy.mangle "_G\uffff") s1)) - (assert (in (hy.mangle "_G\uffff") s2)) - ;; but make sure the two don't match each other - (assert (not (= s1 s2)))) + ; Call `gensym-example` twice, getting a distinct gensym each time. + (defclass C [] + (gensym-example) + (gensym-example)) + (assert (= + (len (sfor a (dir C) :if (not (.startswith a "__")) a)) + 2))) (defn test-macro-errors [] From ebc95dddd3f252c87da7567e54f5b100cfff5d9f Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Thu, 10 Aug 2023 14:10:44 -0400 Subject: [PATCH 3/4] Change the gensym format The new format uses the `_hy_` prefix the we've already officially reserved for a while, and it doesn't need mangling. Since I construe the gensym format as an internal matter, it remains undocumented. --- hy/core/util.hy | 20 ++++++++++++-------- tests/native_tests/hy_misc.hy | 7 ++++--- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/hy/core/util.hy b/hy/core/util.hy index 27aba123d..a8002b438 100644 --- a/hy/core/util.hy +++ b/hy/core/util.hy @@ -39,7 +39,7 @@ (setv _gensym_counter 0) (setv _gensym_lock (Lock)) -(defn gensym [[g "G"]] +(defn gensym [[g ""]] #[[Generate a symbol with a unique name. The argument will be included in the generated symbol, as an aid to debugging. Typically one calls ``hy.gensym`` without an argument. @@ -65,14 +65,18 @@ 4) (print (selfadd (f)))]] - (setv new_symbol None) - (global _gensym_counter) - (global _gensym_lock) (.acquire _gensym_lock) - (try (do (setv _gensym_counter (+ _gensym_counter 1)) - (setv new_symbol (Symbol (.format "_{}\uffff{}" g _gensym_counter)))) - (finally (.release _gensym_lock))) - new_symbol) + (try + (global _gensym_counter) + (+= _gensym_counter 1) + (setv n _gensym_counter) + (finally (.release _gensym_lock))) + (setv g (hy.mangle (.format "_hy_gensym_{}_{}" g n))) + (Symbol (if (.startswith g "_hyx_") + ; Remove the mangle prefix, if it's there, so the result always + ; starts with our reserved prefix `_hy_`. + (+ "_" (cut g (len "_hyx_") None)) + g))) (defn _calling-module-name [[n 1]] "Get the name of the module calling `n` levels up the stack from the diff --git a/tests/native_tests/hy_misc.hy b/tests/native_tests/hy_misc.hy index 124aad571..35f62f609 100644 --- a/tests/native_tests/hy_misc.hy +++ b/tests/native_tests/hy_misc.hy @@ -8,12 +8,13 @@ (defn test-gensym [] (setv s1 (hy.gensym)) (assert (isinstance s1 hy.models.Symbol)) - (assert (.startswith s1 "_G\uffff")) + (assert (.startswith s1 "_hy_gensym__")) (setv s2 (hy.gensym "xx")) (setv s3 (hy.gensym "xx")) - (assert (.startswith s2 "_xx\uffff")) + (assert (.startswith s2 "_hy_gensym_xx_")) (assert (!= s2 s3)) - (assert (!= (str s2) (str s3)))) + (assert (!= (str s2) (str s3))) + (assert (.startswith (hy.gensym "•ab") "_hy_gensym_XbulletXab_"))) (defmacro mac [x expr] From 3206396a5c04b5b9338a6955b1d09994d74b598a Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Tue, 8 Aug 2023 16:05:15 -0400 Subject: [PATCH 4/4] Forbid `~@ #*` --- NEWS.rst | 1 + hy/core/result_macros.py | 2 ++ tests/native_tests/quote.hy | 10 ++++++++++ 3 files changed, 13 insertions(+) diff --git a/NEWS.rst b/NEWS.rst index c91665a85..184314f8d 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -27,6 +27,7 @@ Bug Fixes * Double quotes inside of bracketed f-strings are now properly handled. * Fixed incomplete recognition of macro calls with a unary dotted head like `((. defn) f [])`. +* `~@ #*` now produces a syntax error instead of a nonsensical result. 0.27.0 (released 2023-07-06) ============================= diff --git a/hy/core/result_macros.py b/hy/core/result_macros.py index 2db198d50..c0849b87e 100644 --- a/hy/core/result_macros.py +++ b/hy/core/result_macros.py @@ -222,6 +222,8 @@ def render_quoted_form(compiler, form, level): for x in form: f_contents, splice = render_quoted_form(compiler, x, level) if splice: + if is_unpack("iterable", f_contents): + raise compiler._syntax_error(f_contents, "`unpack-iterable` is not allowed here") f_contents = Expression( [ Symbol("unpack-iterable"), diff --git a/tests/native_tests/quote.hy b/tests/native_tests/quote.hy index 0f874c691..156a4161f 100644 --- a/tests/native_tests/quote.hy +++ b/tests/native_tests/quote.hy @@ -1,3 +1,7 @@ +(import + pytest) + + (defn test-quote [] (setv q (quote (a b c))) (assert (= (len q) 3)) @@ -83,3 +87,9 @@ [1 2 3] [4 5 6]) [4 5 6]))) + + +(defn test-unquote-splice-unpack [] + ; https://github.com/hylang/hy/issues/2336 + (with [(pytest.raises hy.errors.HySyntaxError)] + (hy.eval '`[~@ #* [[1]]])))