From ed930edefe862ca9760dcb4a54a84ef34b4f8f8b Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Mon, 2 Jan 2017 23:24:55 -0800 Subject: [PATCH] Allow keyword args in method calls before the obj (#1167) Unlike Python, Hy allows the programmer to intermingle positional and keyword arguments. This change removes an exception to that rule for method calls, in which the method callee always had to be the first thing after the method. Thus, `(.split :sep "o" "foo")` now compiles to `"foo".split(sep="o")` instead of `HyKeyword("sep").split("o", "foo")`. --- hy/compiler.py | 23 ++++++++++++++++++----- tests/native_tests/language.hy | 12 +++++++++--- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/hy/compiler.py b/hy/compiler.py index 34402bc..46a2deb 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -2068,12 +2068,25 @@ class HyASTCompiler(object): # Get the object we're calling the method on # (extracted with the attribute access DSL) - if len(expression) < 2: - raise HyTypeError(expression, - "attribute access requires object") - + i = 1 + if len(expression) != 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(expression): + if isinstance(expression[i], HyKeyword): + # Skip the keyword argument and its value. + i += 1 + else: + # Use expression[i]. + break + i += 1 + else: + raise HyTypeError(expression, + "attribute access requires object") func = self.compile(HyExpression( - [HySymbol(".").replace(fn), expression.pop(1)] + + [HySymbol(".").replace(fn), expression.pop(i)] + attrs)) # And get the method diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 261b49a..6893b4b 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -397,26 +397,32 @@ (defclass X [object] []) (defclass M [object] - [meth (fn [self &rest args] - (.join " " (+ (, "meth") args)))]) + [meth (fn [self &rest args &kwargs kwargs] + (.join " " (+ (, "meth") args + (tuple (map (fn [k] (get kwargs k)) (sorted (.keys kwargs)))))))]) (setv x (X)) (setv m (M)) (assert (= (.meth m) "meth")) (assert (= (.meth m "foo" "bar") "meth foo bar")) + (assert (= (.meth :b "1" :a "2" m "foo" "bar") "meth foo bar 2 1")) (assert (= (apply .meth [m "foo" "bar"]) "meth foo bar")) (setv x.p m) (assert (= (.p.meth x) "meth")) (assert (= (.p.meth x "foo" "bar") "meth foo bar")) + (assert (= (.p.meth :b "1" :a "2" x "foo" "bar") "meth foo bar 2 1")) (assert (= (apply .p.meth [x "foo" "bar"]) "meth foo bar")) (setv x.a (X)) (setv x.a.b m) (assert (= (.a.b.meth x) "meth")) (assert (= (.a.b.meth x "foo" "bar") "meth foo bar")) - (assert (= (apply .a.b.meth [x "foo" "bar"]) "meth foo bar"))) + (assert (= (.a.b.meth :b "1" :a "2" x "foo" "bar") "meth foo bar 2 1")) + (assert (= (apply .a.b.meth [x "foo" "bar"]) "meth foo bar")) + + (assert (is (.isdigit :foo) False))) (defn test-do []