From 65ebd10d996296c7580f118f796d1da4d15202b8 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Fri, 10 May 2024 14:13:21 -0400 Subject: [PATCH 1/4] Fix a docstring --- hy/reader/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/hy/reader/__init__.py b/hy/reader/__init__.py index 3b8af868d..f08248455 100644 --- a/hy/reader/__init__.py +++ b/hy/reader/__init__.py @@ -25,9 +25,7 @@ def read_many(stream, filename="", reader=None, skip_shebang=False): .. warning:: Thanks to reader macros, reading can execute arbitrary code. Don't read untrusted - input. - - """ + input.""" if isinstance(stream, str): stream = StringIO(stream) From ad8b3c2ead187f3da89933791e653d78630eb774 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Fri, 10 May 2024 15:40:49 -0400 Subject: [PATCH 2/4] Fix a regression in `help` --- hy/compat.py | 15 +++++++++++---- tests/native_tests/other.hy | 8 ++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/hy/compat.py b/hy/compat.py index 046033dc2..f81ca7191 100644 --- a/hy/compat.py +++ b/hy/compat.py @@ -55,9 +55,16 @@ def getdoc(object): `inspect.getcomments` for an object defined in Hy code, which would try to parse the Hy as Python. The implementation is based on Python 3.12.3's `getdoc`.""" - result = pydoc._getdoc(object) or ( - None - if inspect.getfile(object).endswith('.hy') - else inspect.getcomments(object)) + result = pydoc._getdoc(object) + if not result: + can_get_comments = True + try: + file_path = inspect.getfile(object) + except TypeError: + None + else: + can_get_comments = not file_path.endswith('.hy') + if can_get_comments: + result = inspect.getcomments(object) return result and re.sub('^ *\n', '', result.rstrip()) or '' pydoc.getdoc = getdoc diff --git a/tests/native_tests/other.hy b/tests/native_tests/other.hy index f1da24be6..853dcebc8 100644 --- a/tests/native_tests/other.hy +++ b/tests/native_tests/other.hy @@ -82,3 +82,11 @@ (assert (= (pydoc.getdoc pydoc-hy.C2) ""))) ; `pydoc` shouldn't try to get a comment from Hy code, since it ; doesn't know how to parse Hy. + + +(defn test-help-class-attr [] + "Our tampering with `pydoc` shouldn't cause `help` to raise + `TypeError` on classes with non-method attributes." + (defclass C [] + (setv attribute 1)) + (help C)) From 168635224691d3ca5c65d01dbe883c37c349867b Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Fri, 10 May 2024 15:46:58 -0400 Subject: [PATCH 3/4] Avoid special-casing `pyops` in JIT imports --- hy/__init__.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/hy/__init__.py b/hy/__init__.py index fcfd2e4ee..23e550473 100644 --- a/hy/__init__.py +++ b/hy/__init__.py @@ -30,6 +30,7 @@ def __getattr__(self, s): # to be loaded if they're not needed. _jit_imports = dict( + pyops=["hy.pyops", None], read="hy.reader", read_many="hy.reader", mangle="hy.reader", @@ -50,18 +51,13 @@ def __getattr__(self, s): def __getattr__(k): - if k == "pyops": - global pyops - import hy.pyops - - pyops = hy.pyops - return pyops - if k not in _jit_imports: raise AttributeError(f"module {__name__!r} has no attribute {k!r}") + v = _jit_imports[k] module, original_name = v if isinstance(v, list) else (v, k) import importlib + module = importlib.import_module(module) - globals()[k] = getattr(importlib.import_module(module), original_name) + globals()[k] = getattr(module, original_name) if original_name else module return globals()[k] From db54faf77c8bd94d3cc586946213e0f1ea8e751d Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Fri, 10 May 2024 15:47:32 -0400 Subject: [PATCH 4/4] Set `__module__` for more JIT imports --- hy/reader/exceptions.py | 2 +- hy/reader/hy_reader.py | 2 ++ hy/reader/reader.py | 2 ++ hy/repl.py | 5 ++--- tests/compilers/test_ast.py | 4 ++-- tests/importer/test_importer.py | 5 ++--- tests/native_tests/reader_macros.hy | 8 +++----- tests/test_bin.py | 2 +- tests/test_reader.py | 13 ++++++++++--- 9 files changed, 25 insertions(+), 18 deletions(-) diff --git a/hy/reader/exceptions.py b/hy/reader/exceptions.py index 1dadf055a..44d9e46fa 100644 --- a/hy/reader/exceptions.py +++ b/hy/reader/exceptions.py @@ -11,4 +11,4 @@ def from_reader(cls, message, reader): class PrematureEndOfInput(LexException): "Raised when input ends unexpectedly during parsing." - pass + __module__ = 'hy' diff --git a/hy/reader/hy_reader.py b/hy/reader/hy_reader.py index 451052e62..14b21bcdb 100644 --- a/hy/reader/hy_reader.py +++ b/hy/reader/hy_reader.py @@ -117,6 +117,8 @@ class HyReader(Reader): When ``use_current_readers`` is true, initialize this reader with all reader macros from the calling module.""" + __module__ = 'hy' + ### # Components necessary for Reader implementation ### diff --git a/hy/reader/reader.py b/hy/reader/reader.py index 21cd0b954..26a9a308b 100644 --- a/hy/reader/reader.py +++ b/hy/reader/reader.py @@ -58,6 +58,8 @@ class Reader(metaclass=ReaderMeta): A read-only (line, column) tuple indicating the current cursor position of the source being read""" + __module__ = 'hy' + def __init__(self): self._source = None self._filename = None diff --git a/hy/repl.py b/hy/repl.py index 22f012592..8f771e12c 100644 --- a/hy/repl.py +++ b/hy/repl.py @@ -247,6 +247,8 @@ class REPL(code.InteractiveConsole): Note that as with :func:`code.interact`, changes to local variables inside the REPL are not propagated back to the original scope.""" + __module__ = 'hy' + def __init__(self, spy=False, spy_delimiter=('-' * 30), output_fn=None, locals=None, filename="", allow_incomplete=True): # Create a proper module for this REPL so that we can obtain it easily @@ -460,6 +462,3 @@ def banner(self): pyversion=platform.python_version(), os=platform.system(), ) - - -REPL.__module__ = "hy" # Print as `hy.REPL` instead of `hy.repl.REPL`. diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index deea00ef0..351467a8d 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -8,7 +8,7 @@ from hy.compiler import hy_compile from hy.errors import HyError, HyLanguageError from hy.reader import read_many -from hy.reader.exceptions import LexException, PrematureEndOfInput +from hy.reader.exceptions import LexException def _ast_spotcheck(arg, root, secondary): @@ -466,7 +466,7 @@ def test_compile_error(): def test_for_compile_error(): """Ensure we get compile error in tricky 'for' cases""" - with pytest.raises(PrematureEndOfInput) as excinfo: + with pytest.raises(hy.PrematureEndOfInput) as excinfo: can_compile("(fn [] (for)") assert excinfo.value.msg.startswith("Premature end of input") diff --git a/tests/importer/test_importer.py b/tests/importer/test_importer.py index 4a7d66c0c..10d2b76f1 100644 --- a/tests/importer/test_importer.py +++ b/tests/importer/test_importer.py @@ -12,7 +12,6 @@ from hy.errors import HyLanguageError, hy_exc_handler from hy.importer import HyLoader from hy.reader import read_many -from hy.reader.exceptions import PrematureEndOfInput def test_basics(): @@ -183,7 +182,7 @@ def unlink(filename): source.write_text("(setv a 11 (setv b (// 20 1))") - with pytest.raises(PrematureEndOfInput): + with pytest.raises(hy.PrematureEndOfInput): reload(mod) mod = sys.modules.get(TESTFN) @@ -266,7 +265,7 @@ def test_filtered_importlib_frames(capsys): spec = importlib.util.spec_from_loader(testLoader.name, testLoader) mod = importlib.util.module_from_spec(spec) - with pytest.raises(PrematureEndOfInput) as execinfo: + with pytest.raises(hy.PrematureEndOfInput) as execinfo: testLoader.exec_module(mod) hy_exc_handler(execinfo.type, execinfo.value, execinfo.tb) diff --git a/tests/native_tests/reader_macros.hy b/tests/native_tests/reader_macros.hy index cabd39dd9..20e0e70dc 100644 --- a/tests/native_tests/reader_macros.hy +++ b/tests/native_tests/reader_macros.hy @@ -4,8 +4,6 @@ types contextlib [contextmanager] hy.errors [HyMacroExpansionError] - hy.reader [HyReader] - hy.reader.exceptions [PrematureEndOfInput] pytest) @@ -55,7 +53,7 @@ (defn test-bad-reader-macro-name [] (with [(pytest.raises HyMacroExpansionError)] (eval-module "(defreader :a-key '1)")) - (with [(pytest.raises PrematureEndOfInput)] + (with [(pytest.raises hy.PrematureEndOfInput)] (eval-module "# _ 3"))) (defn test-get-macro [] @@ -123,7 +121,7 @@ (with [(pytest.raises hy.errors.HySyntaxError)] (hy.read tag))) ;; but they should be installed in the module - (hy.eval '(setv reader (hy.reader.HyReader :use-current-readers True)) :module module) + (hy.eval '(setv reader (hy.HyReader :use-current-readers True)) :module module) (setv reader module.reader) (for [[s val] [["#r" 5] ["#test-read" 4] @@ -132,7 +130,7 @@ ;; passing a reader explicitly should work as expected (with [module (temp-module "")] - (setv reader (HyReader)) + (setv reader (hy.HyReader)) (defn eval1 [s] (hy.eval (hy.read s :reader reader) :module module)) (eval1 "(defreader fbaz 32)") diff --git a/tests/test_bin.py b/tests/test_bin.py index d9a97ea13..5ff39fc2e 100644 --- a/tests/test_bin.py +++ b/tests/test_bin.py @@ -446,7 +446,7 @@ def req_err(x): r' File "(?:|string-[0-9a-f]+)", line 1\n' r' \(print "\n' r" \^\n" - r"hy.reader.exceptions.PrematureEndOfInput" + r"hy.PrematureEndOfInput" ) assert re.search(peoi_re, error) diff --git a/tests/test_reader.py b/tests/test_reader.py index 0ef34dc57..f7b7171ea 100644 --- a/tests/test_reader.py +++ b/tests/test_reader.py @@ -4,6 +4,7 @@ import pytest +from hy import PrematureEndOfInput from hy.errors import hy_exc_handler from hy.models import ( Bytes, @@ -20,7 +21,7 @@ Symbol, ) from hy.reader import read_many -from hy.reader.exceptions import LexException, PrematureEndOfInput +from hy.reader.exceptions import LexException def tokenize(*args, **kwargs): @@ -86,7 +87,7 @@ def test_lex_single_quote_err(): ' File "", line 1', " '", " ^", - "hy.reader.exceptions.PrematureEndOfInput: Premature end of input while attempting to parse one form", + "hy.PrematureEndOfInput: Premature end of input while attempting to parse one form", ], ) @@ -660,7 +661,7 @@ def test_lex_exception_filtering(capsys): ' File "", line 2', " (foo", " ^", - "hy.reader.exceptions.PrematureEndOfInput: Premature end of input while attempting to parse one form", + "hy.PrematureEndOfInput: Premature end of input while attempting to parse one form", ], ) @@ -702,3 +703,9 @@ def test_shebang(): assert tokenize('#!/usr/bin/env hy\n5') assert (tokenize('#!/usr/bin/env hy\n5', skip_shebang = True) == [Integer(5)]) + + +def test_reader_class_reprs(): + "Don't show internal modules in which these classes are defined." + assert repr(hy.Reader) == "" + assert repr(hy.HyReader) == ""