implement keyword-only arguments
Python 3 supports keyword-only arguments as described in the immortal PEP 3102. This commit implements keyword-only argument support for Hy using a `&kwonly` lambda-list-keyword with semantics analogous how `&optional` arguments are handled: `&kwonly` arguments are either a symbol, in which case the keyword argument so named is mandatory, or a two-element list, the first of which is the symbolic name of the keyword argument and the second of which is its default value if not supplied. If Hy is running under Python 2, attempting to use `&kwonly` args will raise a HyTypeError. This effort is with the aim of resolving #453.
This commit is contained in:
parent
d8abfb3653
commit
ef079d5e08
@ -485,11 +485,13 @@ class HyASTCompiler(object):
|
|||||||
|
|
||||||
def _parse_lambda_list(self, exprs):
|
def _parse_lambda_list(self, exprs):
|
||||||
""" Return FunctionDef parameter values from lambda list."""
|
""" Return FunctionDef parameter values from lambda list."""
|
||||||
ll_keywords = ("&rest", "&optional", "&key", "&kwargs")
|
ll_keywords = ("&rest", "&optional", "&key", "&kwonly", "&kwargs")
|
||||||
ret = Result()
|
ret = Result()
|
||||||
args = []
|
args = []
|
||||||
defaults = []
|
defaults = []
|
||||||
varargs = None
|
varargs = None
|
||||||
|
kwonlyargs = []
|
||||||
|
kwonlydefaults = []
|
||||||
kwargs = None
|
kwargs = None
|
||||||
lambda_keyword = None
|
lambda_keyword = None
|
||||||
|
|
||||||
@ -506,6 +508,8 @@ class HyASTCompiler(object):
|
|||||||
lambda_keyword = expr
|
lambda_keyword = expr
|
||||||
elif expr == "&key":
|
elif expr == "&key":
|
||||||
lambda_keyword = expr
|
lambda_keyword = expr
|
||||||
|
elif expr == "&kwonly":
|
||||||
|
lambda_keyword = expr
|
||||||
elif expr == "&kwargs":
|
elif expr == "&kwargs":
|
||||||
lambda_keyword = expr
|
lambda_keyword = expr
|
||||||
else:
|
else:
|
||||||
@ -554,6 +558,24 @@ class HyASTCompiler(object):
|
|||||||
args.append(k)
|
args.append(k)
|
||||||
ret += self.compile(v)
|
ret += self.compile(v)
|
||||||
defaults.append(ret.force_expr)
|
defaults.append(ret.force_expr)
|
||||||
|
elif lambda_keyword == "&kwonly":
|
||||||
|
if not PY3:
|
||||||
|
raise HyTypeError(expr,
|
||||||
|
"keyword-only arguments are only "
|
||||||
|
"available under Python 3")
|
||||||
|
if isinstance(expr, HyList):
|
||||||
|
if len(expr) != 2:
|
||||||
|
raise HyTypeError(expr,
|
||||||
|
"keyword-only args should be bare "
|
||||||
|
"names or 2-item lists")
|
||||||
|
k, v = expr
|
||||||
|
kwonlyargs.append(k)
|
||||||
|
ret += self.compile(v)
|
||||||
|
kwonlydefaults.append(ret.force_expr)
|
||||||
|
else:
|
||||||
|
k = expr
|
||||||
|
kwonlyargs.append(k)
|
||||||
|
kwonlydefaults.append(None)
|
||||||
elif lambda_keyword == "&kwargs":
|
elif lambda_keyword == "&kwargs":
|
||||||
if kwargs:
|
if kwargs:
|
||||||
raise HyTypeError(expr,
|
raise HyTypeError(expr,
|
||||||
@ -561,7 +583,7 @@ class HyASTCompiler(object):
|
|||||||
"&kwargs argument")
|
"&kwargs argument")
|
||||||
kwargs = str(expr)
|
kwargs = str(expr)
|
||||||
|
|
||||||
return ret, args, defaults, varargs, kwargs
|
return ret, args, defaults, varargs, kwonlyargs, kwonlydefaults, kwargs
|
||||||
|
|
||||||
def _storeize(self, name, func=None):
|
def _storeize(self, name, func=None):
|
||||||
"""Return a new `name` object with an ast.Store() context"""
|
"""Return a new `name` object with an ast.Store() context"""
|
||||||
@ -2026,7 +2048,9 @@ class HyASTCompiler(object):
|
|||||||
if not isinstance(arglist, HyList):
|
if not isinstance(arglist, HyList):
|
||||||
raise HyTypeError(expression,
|
raise HyTypeError(expression,
|
||||||
"First argument to (fn) must be a list")
|
"First argument to (fn) must be a list")
|
||||||
ret, args, defaults, stararg, kwargs = self._parse_lambda_list(arglist)
|
|
||||||
|
(ret, args, defaults, stararg,
|
||||||
|
kwonlyargs, kwonlydefaults, kwargs) = self._parse_lambda_list(arglist)
|
||||||
for i, arg in enumerate(args):
|
for i, arg in enumerate(args):
|
||||||
if isinstance(arg, HyList):
|
if isinstance(arg, HyList):
|
||||||
# Destructuring argument
|
# Destructuring argument
|
||||||
@ -2049,6 +2073,11 @@ class HyASTCompiler(object):
|
|||||||
lineno=x.start_line,
|
lineno=x.start_line,
|
||||||
col_offset=x.start_column) for x in args]
|
col_offset=x.start_column) for x in args]
|
||||||
|
|
||||||
|
kwonlyargs = [ast.arg(arg=ast_str(x), annotation=None,
|
||||||
|
lineno=x.start_line,
|
||||||
|
col_offset=x.start_column)
|
||||||
|
for x in kwonlyargs]
|
||||||
|
|
||||||
# XXX: Beware. Beware. This wasn't put into the parse lambda
|
# XXX: Beware. Beware. This wasn't put into the parse lambda
|
||||||
# list because it's really just an internal parsing thing.
|
# list because it's really just an internal parsing thing.
|
||||||
|
|
||||||
@ -2065,12 +2094,18 @@ class HyASTCompiler(object):
|
|||||||
lineno=x.start_line,
|
lineno=x.start_line,
|
||||||
col_offset=x.start_column) for x in args]
|
col_offset=x.start_column) for x in args]
|
||||||
|
|
||||||
|
if PY3:
|
||||||
|
kwonlyargs = [ast.Name(arg=ast_str(x), id=ast_str(x),
|
||||||
|
ctx=ast.Param(), lineno=x.start_line,
|
||||||
|
col_offset=x.start_column)
|
||||||
|
for x in kwonlyargs]
|
||||||
|
|
||||||
args = ast.arguments(
|
args = ast.arguments(
|
||||||
args=args,
|
args=args,
|
||||||
vararg=stararg,
|
vararg=stararg,
|
||||||
kwarg=kwargs,
|
kwarg=kwargs,
|
||||||
kwonlyargs=[],
|
kwonlyargs=kwonlyargs,
|
||||||
kw_defaults=[],
|
kw_defaults=kwonlydefaults,
|
||||||
defaults=defaults)
|
defaults=defaults)
|
||||||
|
|
||||||
body = self._compile_branch(expression)
|
body = self._compile_branch(expression)
|
||||||
|
@ -10,3 +10,29 @@
|
|||||||
(try (raise ValueError :from NameError)
|
(try (raise ValueError :from NameError)
|
||||||
(except [e [ValueError]]
|
(except [e [ValueError]]
|
||||||
(assert (= (type (. e __cause__)) NameError)))))
|
(assert (= (type (. e __cause__)) NameError)))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn test-kwonly []
|
||||||
|
"NATIVE: test keyword-only arguments"
|
||||||
|
;; keyword-only with default works
|
||||||
|
(let [[kwonly-foo-default-false (fn [&kwonly [foo false]] foo)]]
|
||||||
|
(assert (= (apply kwonly-foo-default-false) false))
|
||||||
|
(assert (= (apply kwonly-foo-default-false [] {"foo" true}) true)))
|
||||||
|
;; keyword-only without default ...
|
||||||
|
(let [[kwonly-foo-no-default (fn [&kwonly foo] foo)]
|
||||||
|
[attempt-to-omit-default (try
|
||||||
|
(kwonly-foo-no-default)
|
||||||
|
(catch [e [Exception]] e))]]
|
||||||
|
;; works
|
||||||
|
(assert (= (apply kwonly-foo-no-default [] {"foo" "quux"}) "quux"))
|
||||||
|
;; raises TypeError with appropriate message if not supplied
|
||||||
|
(assert (isinstance attempt-to-omit-default TypeError))
|
||||||
|
(assert (in "missing 1 required keyword-only argument: 'foo'"
|
||||||
|
(. attempt-to-omit-default args [0]))))
|
||||||
|
;; keyword-only with other arg types works
|
||||||
|
(let [[function-of-various-args
|
||||||
|
(fn [a b &rest args &kwonly foo &kwargs kwargs]
|
||||||
|
(, a b args foo kwargs))]]
|
||||||
|
(assert (= (apply function-of-various-args
|
||||||
|
[1 2 3 4] {"foo" 5 "bar" 6 "quux" 7})
|
||||||
|
(, 1 2 (, 3 4) 5 {"bar" 6 "quux" 7})))))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user