diff --git a/NEWS.rst b/NEWS.rst index 71a10467d..2f425a42b 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -7,6 +7,7 @@ Bug Fixes ------------------------------ * Fixed a crash on Python 3.12.6. * Keyword objects can now be compared to each other with `<` etc. +* Fixed a bug in which the REPL misinterpreted the symbol `pass`. 0.29.0 (released 2024-05-20) ============================= diff --git a/docs/macros.rst b/docs/macros.rst index 6c00016a2..16105f1d1 100644 --- a/docs/macros.rst +++ b/docs/macros.rst @@ -121,6 +121,12 @@ A better approach is to use :hy:func:`hy.gensym` to choose your variable name:: Hyrule provides some macros that make using gensyms more convenient, like :hy:func:`defmacro! ` and :hy:func:`with-gensyms `. +On the other hand, you can write a macro that advertises a specific name (or set of names) as part of its interface. For example, Hyrule's `anaphoric macro `_ :hy:func:`ap-if ` assigns the result of a test form to ``it``, and allows the caller to include forms that refer to ``it``:: + + (import os) + (ap-if (.get os.environ "PYTHONPATH") + (print "Your PYTHONPATH is" it)) + Macro subroutines ~~~~~~~~~~~~~~~~~ @@ -154,7 +160,7 @@ By the way, despite the need for ``eval-and-compile``, extracting a lot of compl The important take-home big fat WARNING ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Ultimately it's wisest to use only four kinds of names in macro expansions: gensyms, core macros, objects that Python puts in scope by default (like its built-in functions), and ``hy`` and its attributes. It's possible to rebind nearly all these names, so surprise shadowing is still theoretically possible. Unfortunately, the only way to prevent these pathological rebindings from coming about is… don't do that. Don't make a new macro named ``setv`` or name a function argument ``type`` unless you're ready for every macro you call to break, the same way you wouldn't monkey-patch a built-in Python module without thinking carefully. This kind of thing is the responsibility of the macro caller; the macro writer can't do much to defend against it. There is at least a pragma :ref:`warn-on-core-shadow `, enabled by default, that causes ``defmacro`` and ``require`` to warn you if you give your new macro the same name as a core macro. +A typical macro should use only four kinds of names in its expansion: gensyms, core macros, objects that Python puts in scope by default (like its built-in functions), and ``hy`` and its attributes. It's possible to rebind nearly all these names, so surprise shadowing is still theoretically possible. Unfortunately, the only way to prevent these pathological rebindings from coming about is… don't do that. Don't make a new macro named ``setv`` or name a function argument ``type`` unless you're ready for every macro you call to break, the same way you wouldn't monkey-patch a built-in Python module without thinking carefully. This kind of thing is the responsibility of the macro caller; the macro writer can't do much to defend against it. There is at least a pragma :ref:`warn-on-core-shadow `, enabled by default, that causes ``defmacro`` and ``require`` to warn you if you give your new macro the same name as a core macro. .. _reader-macros: diff --git a/hy/repl.py b/hy/repl.py index 8f771e12c..c11c66642 100644 --- a/hy/repl.py +++ b/hy/repl.py @@ -78,31 +78,10 @@ def _cmdline_checkcache(*args): _codeop_maybe_compile = codeop._maybe_compile - - -def _hy_maybe_compile(compiler, source, filename, symbol): - """The `codeop` version of this will compile the same source multiple - times, and, since we have macros and things like `eval-and-compile`, we - can't allow that. - """ - if not isinstance(compiler, HyCompile): - return _codeop_maybe_compile(compiler, source, filename, symbol) - - for line in source.split("\n"): - line = line.strip() - if line and line[0] != ";": - # Leave it alone (could do more with Hy syntax) - break - else: - if symbol != "eval": - # Replace it with a 'pass' statement (i.e. tell the compiler to do - # nothing) - source = "pass" - - return compiler(source, filename, symbol) - - -codeop._maybe_compile = _hy_maybe_compile +codeop._maybe_compile = (lambda compiler, source, filename, symbol: + compiler(source, filename, symbol) + if isinstance(compiler, HyCompile) else + _codeop_maybe_compile(compiler, source, filename, symbol)) class HyCompile(codeop.Compile): @@ -149,10 +128,6 @@ def _update_exc_info(self): def __call__(self, source, filename="", symbol="single"): - if source == "pass": - # We need to return a no-op to signal that no more input is needed. - return (compile(source, filename, symbol),) * 2 - hash_digest = hashlib.sha1(source.encode("utf-8").strip()).hexdigest() name = "{}-{}".format(filename.strip("<>"), hash_digest) diff --git a/tests/native_tests/repl.hy b/tests/native_tests/repl.hy index 570f2e565..7169720b3 100644 --- a/tests/native_tests/repl.hy +++ b/tests/native_tests/repl.hy @@ -184,3 +184,9 @@ (assert (has (rt "#!/usr/bin/env hy\n" 'err) "hy.reader.exceptions.LexException"))) + +(defn test-pass [rt] + ; https://github.com/hylang/hy/issues/2601 + (assert (has + (rt "pass\n" 'err) + "NameError")))