diff --git a/NEWS.rst b/NEWS.rst index a52a2ae..9f48e13 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -42,6 +42,7 @@ Bug Fixes * Fixed a case where `->` and `->>` duplicated an argument * Fixed bugs that caused `defclass` to drop statements or crash * Fixed a REPL crash caused by illegle unicode escape string inputs +* `NaN` can no longer create an infinite loop during macro-expansion Misc. Improvements ---------------------------- diff --git a/hy/compiler.py b/hy/compiler.py index d94e94b..0dc28b7 100755 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -423,6 +423,8 @@ class HyASTCompiler(object): # pass in `atom_type`. atom_compiler = _compile_table[atom_type] arity = hy.inspect.get_arity(atom_compiler) + # Compliation methods may mutate the atom, so copy it first. + atom = copy.copy(atom) ret = (atom_compiler(self, atom, atom_type) if arity == 3 else atom_compiler(self, atom)) diff --git a/hy/core/bootstrap.hy b/hy/core/bootstrap.hy index 675df9c..e061ef0 100644 --- a/hy/core/bootstrap.hy +++ b/hy/core/bootstrap.hy @@ -41,6 +41,7 @@ (if ~@(cut args 2)))))) (defmacro deftag [tag-name lambda-list &rest body] + (import hy.models) (if (and (not (isinstance tag-name hy.models.HySymbol)) (not (isinstance tag-name hy.models.HyString))) (raise (hy.errors.HyTypeError diff --git a/hy/core/language.hy b/hy/core/language.hy index 4cba9a3..17d0d8a 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -297,16 +297,12 @@ Return series of accumulated sums (or other binary function results)." (defn macroexpand [form] "Return the full macro expansion of `form`." (import hy.macros) - - (setv name (calling-module-name)) - (hy.macros.macroexpand form (HyASTCompiler name))) + (hy.macros.macroexpand form (HyASTCompiler (calling-module-name)))) (defn macroexpand-1 [form] "Return the single step macro expansion of `form`." (import hy.macros) - - (setv name (calling-module-name)) - (hy.macros.macroexpand-1 form (HyASTCompiler name))) + (hy.macros.macroexpand-1 form (HyASTCompiler (calling-module-name)))) (defn merge-with [f &rest maps] "Return the map of `maps` joined onto the first via the function `f`. diff --git a/hy/macros.py b/hy/macros.py index 110c37d..e4cafee 100644 --- a/hy/macros.py +++ b/hy/macros.py @@ -156,66 +156,59 @@ def make_empty_fn_copy(fn): return empty_fn -def macroexpand(tree, compiler): +def macroexpand(tree, compiler, once=False): """Expand the toplevel macros for the `tree`. Load the macros from the given `module_name`, then expand the (top-level) - macros in `tree` until it stops changing. + macros in `tree` until we no longer can. """ load_macros(compiler.module_name) - old = None - while old != tree: - old = tree - tree = macroexpand_1(tree, compiler) - return tree + while True: + + if not isinstance(tree, HyExpression) or tree == []: + return tree + + fn = tree[0] + if fn in ("quote", "quasiquote") or not isinstance(fn, HySymbol): + return tree + + fn = mangle(fn) + m = _hy_macros[compiler.module_name].get(fn) or _hy_macros[None].get(fn) + if not m: + return tree + + opts = {} + if m._hy_macro_pass_compiler: + opts['compiler'] = compiler + + try: + m_copy = make_empty_fn_copy(m) + m_copy(compiler.module_name, *tree[1:], **opts) + except TypeError as e: + msg = "expanding `" + str(tree[0]) + "': " + msg += str(e).replace("()", "", 1).strip() + raise HyMacroExpansionError(tree, msg) + + try: + obj = m(compiler.module_name, *tree[1:], **opts) + except HyTypeError as e: + if e.expression is None: + e.expression = tree + raise + except Exception as e: + msg = "expanding `" + str(tree[0]) + "': " + repr(e) + raise HyMacroExpansionError(tree, msg) + tree = replace_hy_obj(obj, tree) + + if once: + return tree def macroexpand_1(tree, compiler): """Expand the toplevel macro from `tree` once, in the context of - `module_name`.""" - if isinstance(tree, HyExpression): - if tree == []: - return tree - - fn = tree[0] - if fn in ("quote", "quasiquote"): - return tree - ntree = HyExpression(tree[:]) - ntree.replace(tree) - - opts = {} - - if isinstance(fn, HySymbol): - fn = mangle(str_type(fn)) - m = _hy_macros[compiler.module_name].get(fn) - if m is None: - m = _hy_macros[None].get(fn) - if m is not None: - if m._hy_macro_pass_compiler: - opts['compiler'] = compiler - - try: - m_copy = make_empty_fn_copy(m) - m_copy(compiler.module_name, *ntree[1:], **opts) - except TypeError as e: - msg = "expanding `" + str(tree[0]) + "': " - msg += str(e).replace("()", "", 1).strip() - raise HyMacroExpansionError(tree, msg) - - try: - obj = m(compiler.module_name, *ntree[1:], **opts) - except HyTypeError as e: - if e.expression is None: - e.expression = tree - raise - except Exception as e: - msg = "expanding `" + str(tree[0]) + "': " + repr(e) - raise HyMacroExpansionError(tree, msg) - replace_hy_obj(obj, tree) - return obj - return ntree - return tree + `compiler`.""" + return macroexpand(tree, compiler, once=True) def tag_macroexpand(tag, tree, compiler): diff --git a/tests/macros/test_macro_processor.py b/tests/macros/test_macro_processor.py index dc14fec..cae39a2 100644 --- a/tests/macros/test_macro_processor.py +++ b/tests/macros/test_macro_processor.py @@ -5,7 +5,7 @@ from hy.macros import macro, macroexpand from hy.lex import tokenize -from hy.models import HyString, HyList, HySymbol, HyExpression +from hy.models import HyString, HyList, HySymbol, HyExpression, HyFloat from hy.errors import HyMacroExpansionError from hy.compiler import HyASTCompiler @@ -53,3 +53,12 @@ def test_preprocessor_exceptions(): except HyMacroExpansionError as e: assert "_hy_anon_fn_" not in str(e) assert "TypeError" not in str(e) + + +def test_macroexpand_nan(): + # https://github.com/hylang/hy/issues/1574 + import math + NaN = float('nan') + x = macroexpand(HyFloat(NaN), HyASTCompiler(__name__)) + assert type(x) is HyFloat + assert math.isnan(x) diff --git a/tests/native_tests/quote.hy b/tests/native_tests/quote.hy index 09aafc5..3b31350 100644 --- a/tests/native_tests/quote.hy +++ b/tests/native_tests/quote.hy @@ -2,7 +2,7 @@ ;; This file is part of Hy, which is free software licensed under the Expat ;; license. See the LICENSE. -(import [hy [HyExpression HySymbol HyString HyBytes]]) +(import [hy [HyExpression HySymbol HyString HyBytes HyDict]]) (defn test-quote [] @@ -43,7 +43,7 @@ (assert (= (get q 1) (quote bar))) (assert (= (get q 2) (quote baz))) (assert (= (get q 3) (quote quux))) - (assert (= (type q) hy.HyDict))) + (assert (= (type q) HyDict))) (defn test-quote-expr-in-dict []