Merge pull request #1575 from Kodiologist/fix-macroexpand-nan

Fix bug in macro-expanding NaN
This commit is contained in:
Kodi Arfer 2018-04-21 12:34:51 -07:00 committed by GitHub
commit e3d21118c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 61 additions and 59 deletions

View File

@ -42,6 +42,7 @@ Bug Fixes
* Fixed a case where `->` and `->>` duplicated an argument * Fixed a case where `->` and `->>` duplicated an argument
* Fixed bugs that caused `defclass` to drop statements or crash * Fixed bugs that caused `defclass` to drop statements or crash
* Fixed a REPL crash caused by illegle unicode escape string inputs * Fixed a REPL crash caused by illegle unicode escape string inputs
* `NaN` can no longer create an infinite loop during macro-expansion
Misc. Improvements Misc. Improvements
---------------------------- ----------------------------

View File

@ -423,6 +423,8 @@ class HyASTCompiler(object):
# pass in `atom_type`. # pass in `atom_type`.
atom_compiler = _compile_table[atom_type] atom_compiler = _compile_table[atom_type]
arity = hy.inspect.get_arity(atom_compiler) 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) ret = (atom_compiler(self, atom, atom_type)
if arity == 3 if arity == 3
else atom_compiler(self, atom)) else atom_compiler(self, atom))

View File

@ -41,6 +41,7 @@
(if ~@(cut args 2)))))) (if ~@(cut args 2))))))
(defmacro deftag [tag-name lambda-list &rest body] (defmacro deftag [tag-name lambda-list &rest body]
(import hy.models)
(if (and (not (isinstance tag-name hy.models.HySymbol)) (if (and (not (isinstance tag-name hy.models.HySymbol))
(not (isinstance tag-name hy.models.HyString))) (not (isinstance tag-name hy.models.HyString)))
(raise (hy.errors.HyTypeError (raise (hy.errors.HyTypeError

View File

@ -297,16 +297,12 @@ Return series of accumulated sums (or other binary function results)."
(defn macroexpand [form] (defn macroexpand [form]
"Return the full macro expansion of `form`." "Return the full macro expansion of `form`."
(import hy.macros) (import hy.macros)
(hy.macros.macroexpand form (HyASTCompiler (calling-module-name))))
(setv name (calling-module-name))
(hy.macros.macroexpand form (HyASTCompiler name)))
(defn macroexpand-1 [form] (defn macroexpand-1 [form]
"Return the single step macro expansion of `form`." "Return the single step macro expansion of `form`."
(import hy.macros) (import hy.macros)
(hy.macros.macroexpand-1 form (HyASTCompiler (calling-module-name))))
(setv name (calling-module-name))
(hy.macros.macroexpand-1 form (HyASTCompiler name)))
(defn merge-with [f &rest maps] (defn merge-with [f &rest maps]
"Return the map of `maps` joined onto the first via the function `f`. "Return the map of `maps` joined onto the first via the function `f`.

View File

@ -156,66 +156,59 @@ def make_empty_fn_copy(fn):
return empty_fn return empty_fn
def macroexpand(tree, compiler): def macroexpand(tree, compiler, once=False):
"""Expand the toplevel macros for the `tree`. """Expand the toplevel macros for the `tree`.
Load the macros from the given `module_name`, then expand the (top-level) 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) load_macros(compiler.module_name)
old = None while True:
while old != tree:
old = tree if not isinstance(tree, HyExpression) or tree == []:
tree = macroexpand_1(tree, compiler) return 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("<lambda>()", "", 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): def macroexpand_1(tree, compiler):
"""Expand the toplevel macro from `tree` once, in the context of """Expand the toplevel macro from `tree` once, in the context of
`module_name`.""" `compiler`."""
if isinstance(tree, HyExpression): return macroexpand(tree, compiler, once=True)
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("<lambda>()", "", 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
def tag_macroexpand(tag, tree, compiler): def tag_macroexpand(tag, tree, compiler):

View File

@ -5,7 +5,7 @@
from hy.macros import macro, macroexpand from hy.macros import macro, macroexpand
from hy.lex import tokenize 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.errors import HyMacroExpansionError
from hy.compiler import HyASTCompiler from hy.compiler import HyASTCompiler
@ -53,3 +53,12 @@ def test_preprocessor_exceptions():
except HyMacroExpansionError as e: except HyMacroExpansionError as e:
assert "_hy_anon_fn_" not in str(e) assert "_hy_anon_fn_" not in str(e)
assert "TypeError" 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)

View File

@ -2,7 +2,7 @@
;; This file is part of Hy, which is free software licensed under the Expat ;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE. ;; license. See the LICENSE.
(import [hy [HyExpression HySymbol HyString HyBytes]]) (import [hy [HyExpression HySymbol HyString HyBytes HyDict]])
(defn test-quote [] (defn test-quote []
@ -43,7 +43,7 @@
(assert (= (get q 1) (quote bar))) (assert (= (get q 1) (quote bar)))
(assert (= (get q 2) (quote baz))) (assert (= (get q 2) (quote baz)))
(assert (= (get q 3) (quote quux))) (assert (= (get q 3) (quote quux)))
(assert (= (type q) hy.HyDict))) (assert (= (type q) HyDict)))
(defn test-quote-expr-in-dict [] (defn test-quote-expr-in-dict []