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 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
----------------------------

View File

@ -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))

View File

@ -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

View File

@ -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`.

View File

@ -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("<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):
"""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("<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
`compiler`."""
return macroexpand(tree, compiler, once=True)
def tag_macroexpand(tag, tree, compiler):

View File

@ -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)

View File

@ -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 []