Fix handling of unpacking in method calls and attribute lookups
This commit is contained in:
parent
0f85331c81
commit
081a710b0f
5
NEWS.rst
5
NEWS.rst
@ -13,6 +13,11 @@ New Features
|
||||
shorthand for `(get obj :key)`, and they accept a default value
|
||||
as a second argument.
|
||||
|
||||
Bug Fixes
|
||||
------------------------------
|
||||
* Fixed bugs in the handling of unpacking forms in method calls and
|
||||
attribute access.
|
||||
|
||||
0.15.0
|
||||
==============================
|
||||
|
||||
|
@ -6,8 +6,8 @@
|
||||
from hy.models import (HyObject, HyExpression, HyKeyword, HyInteger, HyComplex,
|
||||
HyString, HyBytes, HySymbol, HyFloat, HyList, HySet,
|
||||
HyDict, HySequence, wrap_value)
|
||||
from hy.model_patterns import (FORM, SYM, STR, sym, brackets, whole, notpexpr,
|
||||
dolike, pexpr, times, Tag, tag)
|
||||
from hy.model_patterns import (FORM, SYM, KEYWORD, STR, sym, brackets, whole,
|
||||
notpexpr, dolike, pexpr, times, Tag, tag, unpack)
|
||||
from funcparserlib.parser import some, many, oneplus, maybe, NoParseError
|
||||
from hy.errors import HyCompileError, HyTypeError
|
||||
|
||||
@ -1550,7 +1550,8 @@ class HyASTCompiler(object):
|
||||
raise HyTypeError(
|
||||
expr, "empty expressions are not allowed at top level")
|
||||
|
||||
root = expr[0]
|
||||
args = list(expr)
|
||||
root = args.pop(0)
|
||||
func = None
|
||||
|
||||
if isinstance(root, HySymbol):
|
||||
@ -1558,11 +1559,12 @@ class HyASTCompiler(object):
|
||||
# First check if `root` is a special operator, unless it has an
|
||||
# `unpack-iterable` in it, since Python's operators (`+`,
|
||||
# etc.) can't unpack. An exception to this exception is that
|
||||
# tuple literals (`,`) can unpack.
|
||||
# tuple literals (`,`) can unpack. Finally, we allow unpacking in
|
||||
# `.` forms here so the user gets a better error message.
|
||||
sroot = ast_str(root)
|
||||
if (sroot in _special_form_compilers or sroot in _bad_roots) and (
|
||||
sroot == mangle(",") or
|
||||
not any(is_unpack("iterable", x) for x in expr[1:])):
|
||||
sroot in (mangle(","), mangle(".")) or
|
||||
not any(is_unpack("iterable", x) for x in args)):
|
||||
if sroot in _bad_roots:
|
||||
raise HyTypeError(
|
||||
expr,
|
||||
@ -1571,19 +1573,19 @@ class HyASTCompiler(object):
|
||||
# pattern-match the arguments.
|
||||
build_method, pattern = _special_form_compilers[sroot]
|
||||
try:
|
||||
parse_tree = pattern.parse(expr[1:])
|
||||
parse_tree = pattern.parse(args)
|
||||
except NoParseError as e:
|
||||
raise HyTypeError(
|
||||
expr[min(e.state.pos + 1, len(expr) - 1)],
|
||||
"parse error for special form '{}': {}".format(
|
||||
expr[0],
|
||||
root,
|
||||
e.msg.replace("<EOF>", "end of form")))
|
||||
return Result() + build_method(
|
||||
self, expr, unmangle(sroot), *parse_tree)
|
||||
|
||||
if root.startswith("."):
|
||||
# (.split "test test") -> "test test".split()
|
||||
# (.a.b.c x) -> (.c (. x a b)) -> x.a.b.c()
|
||||
# (.a.b.c x v1 v2) -> (.c (. x a b) v1 v2) -> x.a.b.c(v1, v2)
|
||||
|
||||
# Get the method name (the last named attribute
|
||||
# in the chain of attributes)
|
||||
@ -1592,25 +1594,22 @@ class HyASTCompiler(object):
|
||||
|
||||
# Get the object we're calling the method on
|
||||
# (extracted with the attribute access DSL)
|
||||
i = 1
|
||||
if len(expr) != 2:
|
||||
# If the expression has only one object,
|
||||
# always use that as the callee.
|
||||
# Otherwise, hunt for the first thing that
|
||||
# isn't a keyword argument or its value.
|
||||
while i < len(expr):
|
||||
if isinstance(expr[i], HyKeyword):
|
||||
# Skip the keyword argument and its value.
|
||||
i += 1
|
||||
else:
|
||||
# Use expr[i].
|
||||
break
|
||||
i += 1
|
||||
else:
|
||||
raise HyTypeError(expr,
|
||||
"attribute access requires object")
|
||||
# Skip past keywords and their arguments.
|
||||
try:
|
||||
kws, obj, rest = (
|
||||
many(KEYWORD + FORM | unpack("mapping")) +
|
||||
FORM +
|
||||
many(FORM)).parse(args)
|
||||
except NoParseError:
|
||||
raise HyTypeError(
|
||||
expr, "attribute access requires object")
|
||||
# Reconstruct `args` to exclude `obj`.
|
||||
args = [x for p in kws for x in p] + list(rest)
|
||||
if is_unpack("iterable", obj):
|
||||
raise HyTypeError(
|
||||
obj, "can't call a method on an unpacking form")
|
||||
func = self.compile(HyExpression(
|
||||
[HySymbol(".").replace(root), expr.pop(i)] +
|
||||
[HySymbol(".").replace(root), obj] +
|
||||
attrs))
|
||||
|
||||
# And get the method
|
||||
@ -1627,7 +1626,7 @@ class HyASTCompiler(object):
|
||||
with_kwargs = root not in (
|
||||
"type", "HyKeyword", "keyword", "name", "keyword?", "identity")
|
||||
args, ret, keywords, oldpy_star, oldpy_kw = self._compile_collect(
|
||||
expr[1:], with_kwargs, oldpy_unpack=True)
|
||||
args, with_kwargs, oldpy_unpack=True)
|
||||
|
||||
return func + ret + asty.Call(
|
||||
expr, func=func.expr, args=args, keywords=keywords,
|
||||
|
@ -15,6 +15,7 @@ from math import isinf
|
||||
|
||||
FORM = some(lambda _: True)
|
||||
SYM = some(lambda x: isinstance(x, HySymbol))
|
||||
KEYWORD = some(lambda x: isinstance(x, HyKeyword))
|
||||
STR = some(lambda x: isinstance(x, HyString))
|
||||
|
||||
def sym(wanted):
|
||||
@ -57,6 +58,14 @@ def notpexpr(*disallowed_heads):
|
||||
isinstance(x[0], HySymbol) and
|
||||
x[0] in disallowed_heads))
|
||||
|
||||
def unpack(kind):
|
||||
"Parse an unpacking form, returning it unchanged."
|
||||
return some(lambda x:
|
||||
isinstance(x, HyExpression)
|
||||
and len(x) > 0
|
||||
and isinstance(x[0], HySymbol)
|
||||
and x[0] == "unpack-" + kind)
|
||||
|
||||
def times(lo, hi, parser):
|
||||
"""Parse `parser` several times (`lo` to `hi`) in a row. `hi` can be
|
||||
float('inf'). The result is a list no matter the number of instances."""
|
||||
|
@ -72,6 +72,17 @@ def test_empty_expr():
|
||||
can_compile("(print '())")
|
||||
|
||||
|
||||
def test_dot_unpacking():
|
||||
|
||||
can_compile("(.meth obj #* args az)")
|
||||
cant_compile("(.meth #* args az)")
|
||||
cant_compile("(. foo #* bar baz)")
|
||||
|
||||
can_compile("(.meth obj #** args az)")
|
||||
can_compile("(.meth #** args obj)")
|
||||
cant_compile("(. foo #** bar baz)")
|
||||
|
||||
|
||||
def test_ast_bad_if():
|
||||
"Make sure AST can't compile invalid if*"
|
||||
cant_compile("(if*)")
|
||||
|
Loading…
Reference in New Issue
Block a user