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):
|
||||
""" Return FunctionDef parameter values from lambda list."""
|
||||
ll_keywords = ("&rest", "&optional", "&key", "&kwargs")
|
||||
ll_keywords = ("&rest", "&optional", "&key", "&kwonly", "&kwargs")
|
||||
ret = Result()
|
||||
args = []
|
||||
defaults = []
|
||||
varargs = None
|
||||
kwonlyargs = []
|
||||
kwonlydefaults = []
|
||||
kwargs = None
|
||||
lambda_keyword = None
|
||||
|
||||
@ -506,6 +508,8 @@ class HyASTCompiler(object):
|
||||
lambda_keyword = expr
|
||||
elif expr == "&key":
|
||||
lambda_keyword = expr
|
||||
elif expr == "&kwonly":
|
||||
lambda_keyword = expr
|
||||
elif expr == "&kwargs":
|
||||
lambda_keyword = expr
|
||||
else:
|
||||
@ -554,6 +558,24 @@ class HyASTCompiler(object):
|
||||
args.append(k)
|
||||
ret += self.compile(v)
|
||||
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":
|
||||
if kwargs:
|
||||
raise HyTypeError(expr,
|
||||
@ -561,7 +583,7 @@ class HyASTCompiler(object):
|
||||
"&kwargs argument")
|
||||
kwargs = str(expr)
|
||||
|
||||
return ret, args, defaults, varargs, kwargs
|
||||
return ret, args, defaults, varargs, kwonlyargs, kwonlydefaults, kwargs
|
||||
|
||||
def _storeize(self, name, func=None):
|
||||
"""Return a new `name` object with an ast.Store() context"""
|
||||
@ -2026,7 +2048,9 @@ class HyASTCompiler(object):
|
||||
if not isinstance(arglist, HyList):
|
||||
raise HyTypeError(expression,
|
||||
"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):
|
||||
if isinstance(arg, HyList):
|
||||
# Destructuring argument
|
||||
@ -2049,6 +2073,11 @@ class HyASTCompiler(object):
|
||||
lineno=x.start_line,
|
||||
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
|
||||
# list because it's really just an internal parsing thing.
|
||||
|
||||
@ -2065,12 +2094,18 @@ class HyASTCompiler(object):
|
||||
lineno=x.start_line,
|
||||
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=args,
|
||||
vararg=stararg,
|
||||
kwarg=kwargs,
|
||||
kwonlyargs=[],
|
||||
kw_defaults=[],
|
||||
kwonlyargs=kwonlyargs,
|
||||
kw_defaults=kwonlydefaults,
|
||||
defaults=defaults)
|
||||
|
||||
body = self._compile_branch(expression)
|
||||
|
@ -10,3 +10,29 @@
|
||||
(try (raise ValueError :from NameError)
|
||||
(except [e [ValueError]]
|
||||
(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