Streamline auto-promotion and position spoofing

Auto-promotion now occurs in only two cases: when we start the compiler and when we expand a macro. It's fully recursive so even a non-model nested in a model will be promoted.

This change fixes some regressions induced by the stricter type checks of the pattern-matching compiler.
This commit is contained in:
Kodi Arfer 2018-05-21 11:37:46 -07:00
parent aae1f8718c
commit 3204a9e8a3
8 changed files with 48 additions and 70 deletions

View File

@ -106,19 +106,6 @@ def builds_model(*model_types):
return _dec
def spoof_positions(obj):
if not isinstance(obj, HyObject):
return
if not hasattr(obj, "start_column"):
obj.start_column = 0
if not hasattr(obj, "start_line"):
obj.start_line = 0
if (hasattr(obj, "__iter__") and
not isinstance(obj, (string_types, bytes_type))):
for x in obj:
spoof_positions(x)
# Provide asty.Foo(x, ...) as shorthand for
# ast.Foo(..., lineno=x.start_line, col_offset=x.start_column) or
# ast.Foo(..., lineno=x.lineno, col_offset=x.col_offset)
@ -382,7 +369,6 @@ class HyASTCompiler(object):
HySymbol("import"),
HySymbol(module),
]).replace(expr)
spoof_positions(e)
ret += self.compile(e)
names = sorted(name for name in names if name)
if names:
@ -393,24 +379,18 @@ class HyASTCompiler(object):
HyList([HySymbol(name) for name in names])
])
]).replace(expr)
spoof_positions(e)
ret += self.compile(e)
self.imports = defaultdict(set)
return ret.stmts
def compile_atom(self, atom):
if not isinstance(atom, HyObject):
atom = wrap_value(atom)
if not isinstance(atom, HyObject):
return
spoof_positions(atom)
if type(atom) not in _model_compilers:
return
# Compilation methods may mutate the atom, so copy it first.
atom = copy.copy(atom)
return Result() + _model_compilers[type(atom)](self, atom)
def compile(self, tree):
if tree is None:
return Result()
try:
ret = self.compile_atom(tree)
if ret:
@ -1744,15 +1724,10 @@ def hy_compile(tree, module_name, root=ast.Module, get_expr=False):
`last_expression` is the.
"""
body = []
expr = None
tree = wrap_value(tree)
if not isinstance(tree, HyObject):
tree = wrap_value(tree)
if not isinstance(tree, HyObject):
raise HyCompileError("`tree` must be a HyObject or capable of "
"being promoted to one")
spoof_positions(tree)
raise HyCompileError("`tree` must be a HyObject or capable of "
"being promoted to one")
compiler = HyASTCompiler(module_name)
result = compiler.compile(tree)

View File

@ -21,7 +21,7 @@
(import [hy.models [HySymbol HyKeyword]])
(import [hy.lex [LexException PrematureEndOfInput tokenize]])
(import [hy.lex.parser [mangle unmangle]])
(import [hy.compiler [HyASTCompiler spoof-positions]])
(import [hy.compiler [HyASTCompiler]])
(import [hy.importer [hy-eval :as eval]])
(defn butlast [coll]
@ -70,7 +70,6 @@ If the second argument `codegen` is true, generate python code instead."
(import astor)
(import hy.compiler)
(spoof-positions tree)
(setv compiled (hy.compiler.hy-compile tree (calling-module-name)))
((if codegen
astor.code-gen.to-source

View File

@ -5,7 +5,7 @@
from __future__ import absolute_import
from hy.compiler import hy_compile, HyTypeError
from hy.models import HyObject, HyExpression, HySymbol, replace_hy_obj
from hy.models import HyObject, HyExpression, HySymbol
from hy.lex import tokenize, LexException
from hy.errors import HyIOError
@ -172,15 +172,8 @@ def hy_eval(hytree, namespace=None, module_name=None, ast_callback=None):
m = inspect.getmodule(inspect.stack()[1][0])
module_name = '__eval__' if m is None else m.__name__
foo = HyObject()
foo.start_line = 0
foo.end_line = 0
foo.start_column = 0
foo.end_column = 0
replace_hy_obj(hytree, foo)
if not isinstance(module_name, string_types):
raise HyTypeError(foo, "Module name must be a string")
raise TypeError("Module name must be a string")
_ast, expr = hy_compile(hytree, module_name, get_expr=True)
@ -197,7 +190,7 @@ def hy_eval(hytree, namespace=None, module_name=None, ast_callback=None):
ast_callback(_ast, expr)
if not isinstance(namespace, dict):
raise HyTypeError(foo, "Globals must be a dictionary")
raise TypeError("Globals must be a dictionary")
# Two-step eval: eval() the body of the exec call
eval(ast_compile(_ast, "<eval_body>", "exec"), namespace)

View File

@ -4,7 +4,7 @@
from hy._compat import PY3
import hy.inspect
from hy.models import replace_hy_obj, HyExpression, HySymbol
from hy.models import replace_hy_obj, HyExpression, HySymbol, wrap_value
from hy.lex.parser import mangle
from hy._compat import str_type
@ -167,16 +167,16 @@ def macroexpand(tree, compiler, once=False):
while True:
if not isinstance(tree, HyExpression) or tree == []:
return tree
break
fn = tree[0]
if fn in ("quote", "quasiquote") or not isinstance(fn, HySymbol):
return tree
break
fn = mangle(fn)
m = _hy_macros[compiler.module_name].get(fn) or _hy_macros[None].get(fn)
if not m:
return tree
break
opts = {}
if m._hy_macro_pass_compiler:
@ -202,8 +202,10 @@ def macroexpand(tree, compiler, once=False):
tree = replace_hy_obj(obj, tree)
if once:
return tree
break
tree = wrap_value(tree)
return tree
def macroexpand_1(tree, compiler):
"""Expand the toplevel macro from `tree` once, in the context of

View File

@ -33,7 +33,7 @@ class HyObject(object):
Hy lexing Objects at once.
"""
def replace(self, other):
def replace(self, other, recursive=False):
if isinstance(other, HyObject):
for attr in ["start_line", "end_line",
"start_column", "end_column"]:
@ -60,25 +60,20 @@ def wrap_value(x):
"""
wrapper = _wrappers.get(type(x))
if wrapper is None:
return x
else:
return wrapper(x)
new = _wrappers.get(type(x), lambda y: y)(x)
if not isinstance(new, HyObject):
raise TypeError("Don't know how to wrap {!r}: {!r}".format(type(x), x))
if isinstance(x, HyObject):
new = new.replace(x, recursive=False)
if not hasattr(new, "start_column"):
new.start_column = 0
if not hasattr(new, "start_line"):
new.start_line = 0
return new
def replace_hy_obj(obj, other):
if isinstance(obj, HyObject):
return obj.replace(other)
wrapped_obj = wrap_value(obj)
if isinstance(wrapped_obj, HyObject):
return wrapped_obj.replace(other)
else:
raise TypeError("Don't know how to wrap a %s object to a HyObject"
% type(obj))
return wrap_value(obj).replace(other)
def repr_indent(obj):
@ -280,8 +275,12 @@ class HySequence(HyObject, list):
class HyList(HySequence):
color = staticmethod(colored.cyan)
_wrappers[list] = lambda l: HyList(wrap_value(x) for x in l)
_wrappers[tuple] = lambda t: HyList(wrap_value(x) for x in t)
def recwrap(f):
return lambda l: f(wrap_value(x) for x in l)
_wrappers[HyList] = recwrap(HyList)
_wrappers[list] = recwrap(HyList)
_wrappers[tuple] = recwrap(HyList)
class HyDict(HySequence):
@ -317,6 +316,7 @@ class HyDict(HySequence):
def items(self):
return list(zip(self.keys(), self.values()))
_wrappers[HyDict] = recwrap(HyDict)
_wrappers[dict] = lambda d: HyDict(wrap_value(x) for x in sum(d.items(), ()))
@ -326,7 +326,7 @@ class HyExpression(HySequence):
"""
color = staticmethod(colored.yellow)
_wrappers[HyExpression] = lambda e: HyExpression(wrap_value(x) for x in e)
_wrappers[HyExpression] = recwrap(HyExpression)
_wrappers[Fraction] = lambda e: HyExpression(
[HySymbol("fraction"), wrap_value(e.numerator), wrap_value(e.denominator)])
@ -337,4 +337,5 @@ class HySet(HySequence):
"""
color = staticmethod(colored.red)
_wrappers[set] = lambda s: HySet(wrap_value(x) for x in s)
_wrappers[HySet] = recwrap(HySet)
_wrappers[set] = recwrap(HySet)

View File

@ -63,7 +63,7 @@ def test_ast_bad_type():
try:
hy_compile(C(), "__main__")
assert True is False
except HyCompileError:
except TypeError:
pass

View File

@ -1327,7 +1327,7 @@
(try (eval '(eval)) (except [e TypeError]) (else (assert False)))
(defclass C)
(try (eval (C)) (except [e TypeError]) (else (assert False)))
(try (eval 'False []) (except [e HyTypeError]) (else (assert False)))
(try (eval 'False []) (except [e TypeError]) (else (assert False)))
(try (eval 'False {} 1) (except [e TypeError]) (else (assert False))))

View File

@ -86,6 +86,14 @@
`(f #* [~x]))
(assert (= (mac) "f:(None,)")))
(defn test-macro-autoboxing-docstring []
(defmacro m []
(setv mystring "hello world")
`(fn [] ~mystring (+ 1 2)))
(setv f (m))
(assert (= (f) 3))
(assert (= f.__doc__ "hello world")))
(defn test-midtree-yield []
"NATIVE: test yielding with a returnable"
(defn kruft [] (yield) (+ 1 1)))