Repository: hylang/hy

Issue:
Support `(require module :readers *)`
Following up from https://github.com/hylang/hy/issues/2242#issuecomment-1103133136 just so we don't lose track.

I can take a swing at this pretty soon, doesn't seem like it should be too difficult.


Discussion:

Resolving patch:
diff --git a/hy/core/result_macros.py b/hy/core/result_macros.py
--- a/hy/core/result_macros.py
+++ b/hy/core/result_macros.py
@@ -1688,7 +1688,7 @@ def assignment_shape(module, rest):
                 0,
                 2,
                 (maybe(sym(":macros")) + importlike(Symbol))
-                | (keepsym(":readers") + brackets(many(SYM))),
+                | (keepsym(":readers") + (keepsym("*") | brackets(many(SYM)))),
             )
         )
     ],
@@ -1742,30 +1742,34 @@ def compile_require(compiler, expr, root, entries):
             )
             ret += ret.expr_as_stmt()
 
-        if readers and require_reader(ast_module, compiler.module, readers[0]):
-            reader_assignments = [String(reader) for reader in readers[0]]
-            ret += compiler.compile(
-                mkexpr(
-                    "do",
-                    mkexpr(
-                        "hy.macros.require-reader",
-                        String(ast_module),
-                        "None",
-                        Keyword("assignments"),
-                        [reader_assignments],
-                    ),
+        if readers:
+            reader_assignments = (
+                "ALL"
+                if readers == Symbol("*")
+                else ["#" + reader for reader in readers[0]]
+            )
+            if require_reader(ast_module, compiler.module, reader_assignments):
+                ret += compiler.compile(
                     mkexpr(
-                        "eval-when-compile",
+                        "do",
                         mkexpr(
-                            "hy.macros.enable-readers",
+                            "hy.macros.require-reader",
+                            String(ast_module),
                             "None",
-                            "hy.&reader",
                             [reader_assignments],
                         ),
-                    ),
-                ).replace(expr)
-            )
-            ret += ret.expr_as_stmt()
+                        mkexpr(
+                            "eval-when-compile",
+                            mkexpr(
+                                "hy.macros.enable-readers",
+                                "None",
+                                "hy.&reader",
+                                [reader_assignments],
+                            ),
+                        ),
+                    ).replace(expr)
+                )
+                ret += ret.expr_as_stmt()
     return ret
 
 
diff --git a/hy/macros.py b/hy/macros.py
--- a/hy/macros.py
+++ b/hy/macros.py
@@ -167,7 +167,7 @@ def import_module_from_string(module_name, package_module):
         raise HyRequireError(e.args[0]).with_traceback(None)
 
 
-def require_reader(source_module, target_module, assignments=None):
+def require_reader(source_module, target_module, assignments):
     target_module, target_namespace = derive_target_module(
         target_module, inspect.stack()[1][0]
     )
@@ -179,30 +179,15 @@ def require_reader(source_module, target_module, assignments=None):
         source_module = import_module_from_string(source_module, target_module)
 
     source_macros = source_module.__dict__.setdefault("__reader_macros__", {})
-
-    if not source_module.__reader_macros__:
-        if assignments:
-            for name in assignments:
-                try:
-                    require_reader(
-                        f"{source_module.__name__}.{mangle(name)}", target_module
-                    )
-                except HyRequireError as e:
-                    raise HyRequireError(
-                        f"Cannot import reader '{name}'"
-                        f" from '{source_module.__name__}'"
-                        f" ({source_module.__file__})"
-                    )
-            return True
-        else:
-            return False
-
     target_macros = target_namespace.setdefault("__reader_macros__", {})
 
-    for name in assignments or list(source_macros.keys()):
-        _name = mangle("#" + name)
-        if _name in source_module.__reader_macros__:
-            target_macros[_name] = source_macros[_name]
+    assignments = (
+        source_macros.keys() if assignments == "ALL" else map(mangle, assignments)
+    )
+
+    for name in assignments:
+        if name in source_module.__reader_macros__:
+            target_macros[name] = source_macros[name]
         else:
             raise HyRequireError(
                 "Could not require name {} from {}".format(_name, source_module)
@@ -213,11 +198,13 @@ def require_reader(source_module, target_module, assignments=None):
 
 def enable_readers(module, reader, names):
     _, namespace = derive_target_module(module, inspect.stack()[1][0])
+    names = (
+        namespace["__reader_macros__"].keys() if names == "ALL" else map(mangle, names)
+    )
     for name in names:
-        _name = mangle("#" + name)
-        if _name not in namespace["__reader_macros__"]:
+        if name not in namespace["__reader_macros__"]:
             raise NameError(f"reader {name} is not defined")
-        reader.reader_table[_name] = namespace["__reader_macros__"][_name]
+        reader.reader_table[name] = namespace["__reader_macros__"][name]
 
 
 def require(source_module, target_module, assignments, prefix=""):


Test patch:
diff --git a/tests/native_tests/reader_macros.hy b/tests/native_tests/reader_macros.hy
--- a/tests/native_tests/reader_macros.hy
+++ b/tests/native_tests/reader_macros.hy
@@ -63,6 +63,13 @@
         [(qplah 1) #upper "hello"]]f])
       [[8 1] "HELLO"])))
 
+  ;; test require :readers *
+  (assert (=
+      (eval-module #[=[
+        (require tests.resources.tlib :readers *)
+        [#upper "eVeRy" #lower "ReAdEr"]]=])
+      ["EVERY" "reader"]))
+
   ;; test can't redefine :macros or :readers assignment brackets
   (with [(pytest.raises hy.errors.HySyntaxError)]
     (eval-module #[[(require tests.resources.tlib [taggart] [upper])]]))
diff --git a/tests/resources/tlib.hy b/tests/resources/tlib.hy
--- a/tests/resources/tlib.hy
+++ b/tests/resources/tlib.hy
@@ -14,3 +14,9 @@
     (if (isinstance node #(hy.models.Symbol hy.models.String))
         (.__class__ node (.upper node))
         (raise (TypeError f"Cannot uppercase {(type node)}")))))
+
+(defreader lower
+  (setv node (&reader.parse-one-form))
+  (if (isinstance node #(hy.models.Symbol hy.models.String))
+    (.__class__ node (.lower node))
+    (raise (TypeError f"Cannot lowercase {(type node)}"))))