From 3204a9e8a3e877a87d90c5f7a7bcca5871cc0be7 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Mon, 21 May 2018 11:37:46 -0700 Subject: [PATCH] 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. --- hy/compiler.py | 35 ++++------------------- hy/core/language.hy | 3 +- hy/importer.py | 13 ++------- hy/macros.py | 12 ++++---- hy/models.py | 43 +++++++++++++++-------------- tests/compilers/test_ast.py | 2 +- tests/native_tests/language.hy | 2 +- tests/native_tests/native_macros.hy | 8 ++++++ 8 files changed, 48 insertions(+), 70 deletions(-) diff --git a/hy/compiler.py b/hy/compiler.py index be5a7a6..5bdc025 100755 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -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) diff --git a/hy/core/language.hy b/hy/core/language.hy index 17d0d8a..3481bf1 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -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 diff --git a/hy/importer.py b/hy/importer.py index 1fd8c6d..92c95b0 100644 --- a/hy/importer.py +++ b/hy/importer.py @@ -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, "", "exec"), namespace) diff --git a/hy/macros.py b/hy/macros.py index 040b2f9..52702ee 100644 --- a/hy/macros.py +++ b/hy/macros.py @@ -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 diff --git a/hy/models.py b/hy/models.py index d9991f2..a0002b6 100644 --- a/hy/models.py +++ b/hy/models.py @@ -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) diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index 04b4d15..d232649 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -63,7 +63,7 @@ def test_ast_bad_type(): try: hy_compile(C(), "__main__") assert True is False - except HyCompileError: + except TypeError: pass diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 70a60eb..98e9978 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -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)))) diff --git a/tests/native_tests/native_macros.hy b/tests/native_tests/native_macros.hy index 2b4bad6..afacee9 100644 --- a/tests/native_tests/native_macros.hy +++ b/tests/native_tests/native_macros.hy @@ -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)))