Skip to content

Commit

Permalink
Streamline auto-promotion and position spoofing
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Kodiologist committed Jun 5, 2018
1 parent aae1f87 commit 3204a9e
Show file tree
Hide file tree
Showing 8 changed files with 48 additions and 70 deletions.
35 changes: 5 additions & 30 deletions hy/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,19 +106,6 @@ def _dec(fn):
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)
Expand Down Expand Up @@ -382,7 +369,6 @@ def imports_as_stmts(self, expr):
HySymbol("import"),
HySymbol(module),
]).replace(expr)
spoof_positions(e)
ret += self.compile(e)
names = sorted(name for name in names if name)
if names:
Expand All @@ -393,24 +379,18 @@ def imports_as_stmts(self, expr):
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:
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 1 addition & 2 deletions hy/core/language.hy
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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
Expand Down
13 changes: 3 additions & 10 deletions hy/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)

Expand All @@ -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)
Expand Down
12 changes: 7 additions & 5 deletions hy/macros.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand Down
43 changes: 22 additions & 21 deletions hy/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]:
Expand All @@ -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):
Expand Down Expand Up @@ -280,8 +275,12 @@ def __str__(self):
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):
Expand Down Expand Up @@ -317,6 +316,7 @@ def values(self):
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(), ()))


Expand All @@ -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)])

Expand All @@ -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)
2 changes: 1 addition & 1 deletion tests/compilers/test_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class C:
try:
hy_compile(C(), "__main__")
assert True is False
except HyCompileError:
except TypeError:
pass


Expand Down
2 changes: 1 addition & 1 deletion tests/native_tests/language.hy
Original file line number Diff line number Diff line change
Expand Up @@ -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))))


Expand Down
8 changes: 8 additions & 0 deletions tests/native_tests/native_macros.hy
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
Expand Down

0 comments on commit 3204a9e

Please sign in to comment.