Skip to content

Commit

Permalink
Merge pull request #2603 from Kodiologist/you-shall-not-pass
Browse files Browse the repository at this point in the history
Replace `_hy_maybe_compile` with a one-liner
  • Loading branch information
Kodiologist authored Sep 12, 2024
2 parents 0aa9370 + 755c6c1 commit 03ca15a
Show file tree
Hide file tree
Showing 4 changed files with 18 additions and 30 deletions.
1 change: 1 addition & 0 deletions NEWS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
=============================
Expand Down
8 changes: 7 additions & 1 deletion docs/macros.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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! <hyrule.defmacro!>` and :hy:func:`with-gensyms <hyrule.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 <https://en.wikipedia.org/wiki/Anaphoric_macro>`_ :hy:func:`ap-if <hyrule.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
~~~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -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 <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 <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:

Expand Down
33 changes: 4 additions & 29 deletions hy/repl.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -149,10 +128,6 @@ def _update_exc_info(self):

def __call__(self, source, filename="<input>", 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)

Expand Down
6 changes: 6 additions & 0 deletions tests/native_tests/repl.hy
Original file line number Diff line number Diff line change
Expand Up @@ -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")))

0 comments on commit 03ca15a

Please sign in to comment.