Move logic from macroexpand_1 to macroexpand

By ending macro-expansion immediately when appropriate, this change fixes a bug arising from the fact that NaN != NaN.
This commit is contained in:
Kodi Arfer 2018-04-09 12:01:12 -07:00
parent 026316ebef
commit c1a487cdf7
3 changed files with 54 additions and 43 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

@ -156,58 +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 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)
return replace_hy_obj(obj, 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)