Skip to content

Commit

Permalink
pythongh-122688: Fix support of var-positional parameter in Argument …
Browse files Browse the repository at this point in the history
…Clinic (pythonGH-122689)

* Parameters after the var-positional parameter are now keyword-only
  instead of positional-or-keyword.
* Correctly calculate min_kw_only.
* Raise errors for invalid combinations of the var-positional parameter
  with "*", "/" and deprecation markers.
  • Loading branch information
serhiy-storchaka authored Aug 9, 2024
1 parent b6e745a commit 8393608
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 96 deletions.
12 changes: 6 additions & 6 deletions Lib/test/clinic.test.c
Original file line number Diff line number Diff line change
Expand Up @@ -4134,8 +4134,8 @@ test_vararg_and_posonly
a: object
*args: object
/
*args: object
[clinic start generated code]*/

Expand Down Expand Up @@ -4177,7 +4177,7 @@ test_vararg_and_posonly(PyObject *module, PyObject *const *args, Py_ssize_t narg

static PyObject *
test_vararg_and_posonly_impl(PyObject *module, PyObject *a, PyObject *args)
/*[clinic end generated code: output=79b75dc07decc8d6 input=08dc2bf7afbf1613]*/
/*[clinic end generated code: output=79b75dc07decc8d6 input=9cfa748bbff09877]*/

/*[clinic input]
test_vararg
Expand Down Expand Up @@ -4920,7 +4920,7 @@ Test_an_metho_arg_named_arg_impl(TestObj *self, int arg)
/*[clinic input]
Test.__init__
*args: object
/
Varargs init method. For example, nargs is translated to PyTuple_GET_SIZE.
[clinic start generated code]*/

Expand Down Expand Up @@ -4958,14 +4958,14 @@ Test___init__(PyObject *self, PyObject *args, PyObject *kwargs)

static int
Test___init___impl(TestObj *self, PyObject *args)
/*[clinic end generated code: output=0ed1009fe0dcf98d input=96c3ddc0cd38fc0c]*/
/*[clinic end generated code: output=0ed1009fe0dcf98d input=2a8bd0033c9ac772]*/


/*[clinic input]
@classmethod
Test.__new__
*args: object
/
Varargs new method. For example, nargs is translated to PyTuple_GET_SIZE.
[clinic start generated code]*/

Expand Down Expand Up @@ -5002,7 +5002,7 @@ Test(PyTypeObject *type, PyObject *args, PyObject *kwargs)

static PyObject *
Test_impl(PyTypeObject *type, PyObject *args)
/*[clinic end generated code: output=8b219f6633e2a2e9 input=26a672e2e9750120]*/
/*[clinic end generated code: output=8b219f6633e2a2e9 input=70ad829df3dd9b84]*/


/*[clinic input]
Expand Down
96 changes: 85 additions & 11 deletions Lib/test/test_clinic.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ def __init__(self):
"""
self.expect_failure(block, err, lineno=8)

def test_multiple_star_in_args(self):
def test_star_after_vararg(self):
err = "'my_test_func' uses '*' more than once."
block = """
/*[clinic input]
Expand All @@ -336,6 +336,20 @@ def test_multiple_star_in_args(self):
"""
self.expect_failure(block, err, lineno=6)

def test_vararg_after_star(self):
err = "'my_test_func' uses '*' more than once."
block = """
/*[clinic input]
my_test_func
pos_arg: object
*
*args: object
kw_arg: object
[clinic start generated code]*/
"""
self.expect_failure(block, err, lineno=6)

def test_module_already_got_one(self):
err = "Already defined module 'm'!"
block = """
Expand Down Expand Up @@ -1787,13 +1801,43 @@ def test_parameters_required_after_depr_star2(self):
)
self.expect_failure(block, err, lineno=4)

def test_parameters_required_after_depr_star3(self):
block = """
module foo
foo.bar
a: int
* [from 3.14]
*args: object
b: int
Docstring.
"""
err = (
"Function 'bar' specifies '* [from ...]' without "
"following parameters."
)
self.expect_failure(block, err, lineno=4)

def test_depr_star_must_come_before_star(self):
block = """
module foo
foo.bar
this: int
a: int
*
* [from 3.14]
b: int
Docstring.
"""
err = "Function 'bar': '* [from ...]' must precede '*'"
self.expect_failure(block, err, lineno=4)

def test_depr_star_must_come_before_vararg(self):
block = """
module foo
foo.bar
a: int
*args: object
* [from 3.14]
b: int
Docstring.
"""
err = "Function 'bar': '* [from ...]' must precede '*'"
Expand Down Expand Up @@ -1908,7 +1952,7 @@ def test_double_slash(self):
err = "Function 'bar' uses '/' more than once."
self.expect_failure(block, err)

def test_mix_star_and_slash(self):
def test_slash_after_star(self):
block = """
module foo
foo.bar
Expand All @@ -1921,6 +1965,19 @@ def test_mix_star_and_slash(self):
err = "Function 'bar': '/' must precede '*'"
self.expect_failure(block, err)

def test_slash_after_vararg(self):
block = """
module foo
foo.bar
x: int
y: int
*args: object
z: int
/
"""
err = "Function 'bar': '/' must precede '*'"
self.expect_failure(block, err)

def test_depr_star_must_come_after_slash(self):
block = """
module foo
Expand Down Expand Up @@ -1960,6 +2017,19 @@ def test_star_must_come_after_depr_slash(self):
err = "Function 'bar': '/ [from ...]' must precede '*'"
self.expect_failure(block, err, lineno=4)

def test_vararg_must_come_after_depr_slash(self):
block = """
module foo
foo.bar
a: int
*args: object
/ [from 3.14]
b: int
Docstring.
"""
err = "Function 'bar': '/ [from ...]' must precede '*'"
self.expect_failure(block, err, lineno=4)

def test_depr_slash_must_come_after_slash(self):
block = """
module foo
Expand Down Expand Up @@ -1987,7 +2057,7 @@ def test_parameters_not_permitted_after_slash_for_now(self):
self.expect_failure(block, err)

def test_parameters_no_more_than_one_vararg(self):
err = "Too many var args"
err = "Function 'bar' uses '*' more than once."
block = """
module foo
foo.bar
Expand Down Expand Up @@ -3319,13 +3389,6 @@ def test_posonly_vararg(self):
with self.assertRaises(TypeError):
ac_tester.posonly_vararg(1, 2, 3, b=4)

def test_vararg_and_posonly(self):
with self.assertRaises(TypeError):
ac_tester.vararg_and_posonly()
with self.assertRaises(TypeError):
ac_tester.vararg_and_posonly(1, b=2)
self.assertEqual(ac_tester.vararg_and_posonly(1, 2, 3, 4), (1, (2, 3, 4)))

def test_vararg(self):
with self.assertRaises(TypeError):
ac_tester.vararg()
Expand Down Expand Up @@ -3363,6 +3426,17 @@ def test_vararg_with_only_defaults(self):
self.assertEqual(ac_tester.vararg_with_only_defaults(1, 2, 3, 4), ((1, 2, 3, 4), None))
self.assertEqual(ac_tester.vararg_with_only_defaults(1, 2, 3, 4, b=5), ((1, 2, 3, 4), 5))

def test_vararg_kwonly_req_opt(self):
fn = ac_tester.vararg_kwonly_req_opt
self.assertRaises(TypeError, fn)
self.assertEqual(fn(a=1), ((), 1, None, None))
self.assertEqual(fn(a=1, b=2), ((), 1, 2, None))
self.assertEqual(fn(a=1, b=2, c=3), ((), 1, 2, 3))
self.assertRaises(TypeError, fn, 1)
self.assertEqual(fn(1, a=2), ((1,), 2, None, None))
self.assertEqual(fn(1, a=2, b=3), ((1,), 2, 3, None))
self.assertEqual(fn(1, a=2, b=3, c=4), ((1,), 2, 3, 4))

def test_gh_32092_oob(self):
ac_tester.gh_32092_oob(1, 2, 3, 4, kw1=5, kw2=6)

Expand Down
41 changes: 21 additions & 20 deletions Modules/_testclinic.c
Original file line number Diff line number Diff line change
Expand Up @@ -981,23 +981,6 @@ posonly_vararg_impl(PyObject *module, PyObject *a, PyObject *b,
}


/*[clinic input]
vararg_and_posonly
a: object
*args: object
/
[clinic start generated code]*/

static PyObject *
vararg_and_posonly_impl(PyObject *module, PyObject *a, PyObject *args)
/*[clinic end generated code: output=42792f799465a14d input=defe017b19ba52e8]*/
{
return pack_arguments_newref(2, a, args);
}


/*[clinic input]
vararg
Expand Down Expand Up @@ -1068,6 +1051,25 @@ vararg_with_only_defaults_impl(PyObject *module, PyObject *args, PyObject *b)
}


/*[clinic input]
vararg_kwonly_req_opt
*args: object
a: object
b: object = None
c: object = None
[clinic start generated code]*/

static PyObject *
vararg_kwonly_req_opt_impl(PyObject *module, PyObject *args, PyObject *a,
PyObject *b, PyObject *c)
/*[clinic end generated code: output=54694a99c3da370a input=b0d8bf09e540d400]*/
{
return pack_arguments_newref(4, args, a, b, c);
}



/*[clinic input]
gh_32092_oob
Expand Down Expand Up @@ -1115,15 +1117,14 @@ gh_32092_kw_pass_impl(PyObject *module, PyObject *pos, PyObject *args,
gh_99233_refcount
*args: object
/
Proof-of-concept of GH-99233 refcount error bug.
[clinic start generated code]*/

static PyObject *
gh_99233_refcount_impl(PyObject *module, PyObject *args)
/*[clinic end generated code: output=585855abfbca9a7f input=85f5fb47ac91a626]*/
/*[clinic end generated code: output=585855abfbca9a7f input=eecfdc2092d90dc3]*/
{
Py_RETURN_NONE;
}
Expand Down Expand Up @@ -1923,11 +1924,11 @@ static PyMethodDef tester_methods[] = {
POSONLY_OPT_KEYWORDS_OPT_KWONLY_OPT_METHODDEF
KEYWORD_ONLY_PARAMETER_METHODDEF
POSONLY_VARARG_METHODDEF
VARARG_AND_POSONLY_METHODDEF
VARARG_METHODDEF
VARARG_WITH_DEFAULT_METHODDEF
VARARG_WITH_DEFAULT2_METHODDEF
VARARG_WITH_ONLY_DEFAULTS_METHODDEF
VARARG_KWONLY_REQ_OPT_METHODDEF
GH_32092_OOB_METHODDEF
GH_32092_KW_PASS_METHODDEF
GH_99233_REFCOUNT_METHODDEF
Expand Down
Loading

0 comments on commit 8393608

Please sign in to comment.