Skip to content

gh-95065, gh-107704: Argument Clinic: support multiple '/ [from ...]' and '* [from ...]' markers #108132

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Include/internal/pycore_global_objects_fini_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(exp)
STRUCT_FOR_ID(extend)
STRUCT_FOR_ID(extra_tokens)
STRUCT_FOR_ID(f)
STRUCT_FOR_ID(facility)
STRUCT_FOR_ID(factory)
STRUCT_FOR_ID(false)
Expand Down Expand Up @@ -443,6 +444,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(fset)
STRUCT_FOR_ID(func)
STRUCT_FOR_ID(future)
STRUCT_FOR_ID(g)
STRUCT_FOR_ID(generation)
STRUCT_FOR_ID(genexpr)
STRUCT_FOR_ID(get)
Expand All @@ -456,6 +458,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(globals)
STRUCT_FOR_ID(groupindex)
STRUCT_FOR_ID(groups)
STRUCT_FOR_ID(h)
STRUCT_FOR_ID(handle)
STRUCT_FOR_ID(hash_name)
STRUCT_FOR_ID(header)
Expand Down
3 changes: 3 additions & 0 deletions Include/internal/pycore_runtime_init_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions Include/internal/pycore_unicodeobject_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

96 changes: 75 additions & 21 deletions Lib/test/test_clinic.py
Original file line number Diff line number Diff line change
Expand Up @@ -1751,7 +1751,7 @@ def test_depr_star_must_come_before_star(self):
* [from 3.14]
Docstring.
"""
err = "Function 'bar': '* [from ...]' must come before '*'"
err = "Function 'bar': '* [from ...]' must precede '*'"
self.expect_failure(block, err, lineno=4)

def test_depr_star_duplicate(self):
Expand All @@ -1765,7 +1765,7 @@ def test_depr_star_duplicate(self):
c: int
Docstring.
"""
err = "Function 'bar' uses '* [from ...]' more than once."
err = "Function 'bar' uses '* [from 3.14]' more than once."
self.expect_failure(block, err, lineno=5)

def test_depr_star_duplicate2(self):
Expand All @@ -1779,7 +1779,7 @@ def test_depr_star_duplicate2(self):
c: int
Docstring.
"""
err = "Function 'bar' uses '* [from ...]' more than once."
err = "Function 'bar': '* [from 3.15]' must precede '* [from 3.14]'"
self.expect_failure(block, err, lineno=5)

def test_depr_slash_duplicate(self):
Expand All @@ -1793,21 +1793,21 @@ def test_depr_slash_duplicate(self):
c: int
Docstring.
"""
err = "Function 'bar' uses '/ [from ...]' more than once."
err = "Function 'bar' uses '/ [from 3.14]' more than once."
self.expect_failure(block, err, lineno=5)

def test_depr_slash_duplicate2(self):
block = """
module foo
foo.bar
a: int
/ [from 3.14]
b: int
/ [from 3.15]
b: int
/ [from 3.14]
c: int
Docstring.
"""
err = "Function 'bar' uses '/ [from ...]' more than once."
err = "Function 'bar': '/ [from 3.14]' must precede '/ [from 3.15]'"
self.expect_failure(block, err, lineno=5)

def test_single_slash(self):
Expand Down Expand Up @@ -2724,7 +2724,15 @@ class ClinicFunctionalTest(unittest.TestCase):
locals().update((name, getattr(ac_tester, name))
for name in dir(ac_tester) if name.startswith('test_'))

def check_depr_star(self, pnames, fn, *args, name=None, **kwds):
def check_depr(self, regex, fn, /, *args, **kwds):
with self.assertWarnsRegex(DeprecationWarning, regex) as cm:
# Record the line number, so we're sure we've got the correct stack
# level on the deprecation warning.
_, lineno = fn(*args, **kwds), sys._getframe().f_lineno
self.assertEqual(cm.filename, __file__)
self.assertEqual(cm.lineno, lineno)

def check_depr_star(self, pnames, fn, /, *args, name=None, **kwds):
if name is None:
name = fn.__qualname__
if isinstance(fn, type):
Expand All @@ -2734,12 +2742,7 @@ def check_depr_star(self, pnames, fn, *args, name=None, **kwds):
fr"{re.escape(name)}\(\) is deprecated. Parameters? {pnames} will "
fr"become( a)? keyword-only parameters? in Python 3\.14"
)
with self.assertWarnsRegex(DeprecationWarning, regex) as cm:
# Record the line number, so we're sure we've got the correct stack
# level on the deprecation warning.
_, lineno = fn(*args, **kwds), sys._getframe().f_lineno
self.assertEqual(cm.filename, __file__)
self.assertEqual(cm.lineno, lineno)
self.check_depr(regex, fn, *args, **kwds)

def check_depr_kwd(self, pnames, fn, *args, name=None, **kwds):
if name is None:
Expand All @@ -2749,15 +2752,10 @@ def check_depr_kwd(self, pnames, fn, *args, name=None, **kwds):
pl = 's' if ' ' in pnames else ''
regex = (
fr"Passing keyword argument{pl} {pnames} to "
fr"{re.escape(name)}\(\) is deprecated. Corresponding parameter{pl} "
fr"{re.escape(name)}\(\) is deprecated. Parameter{pl} {pnames} "
fr"will become positional-only in Python 3\.14."
)
with self.assertWarnsRegex(DeprecationWarning, regex) as cm:
# Record the line number, so we're sure we've got the correct stack
# level on the deprecation warning.
_, lineno = fn(*args, **kwds), sys._getframe().f_lineno
self.assertEqual(cm.filename, __file__)
self.assertEqual(cm.lineno, lineno)
self.check_depr(regex, fn, *args, **kwds)

def test_objects_converter(self):
with self.assertRaises(TypeError):
Expand Down Expand Up @@ -3368,6 +3366,24 @@ def test_depr_star_noinline(self):
check("a", "b", c="c")
self.assertRaises(TypeError, fn, "a", "b", "c", "d")

def test_depr_star_multi(self):
fn = ac_tester.depr_star_multi
self.assertRaises(TypeError, fn, "a")
fn("a", b="b", c="c", d="d", e="e", f="f", g="g", h="h")
errmsg = (
"Passing more than 1 positional argument to depr_star_multi() is deprecated. "
"Parameter 'b' will become a keyword-only parameter in Python 3.16. "
"Parameters 'c' and 'd' will become keyword-only parameters in Python 3.15. "
"Parameters 'e', 'f' and 'g' will become keyword-only parameters in Python 3.14.")
check = partial(self.check_depr, re.escape(errmsg), fn)
check("a", "b", c="c", d="d", e="e", f="f", g="g", h="h")
check("a", "b", "c", d="d", e="e", f="f", g="g", h="h")
check("a", "b", "c", "d", e="e", f="f", g="g", h="h")
check("a", "b", "c", "d", "e", f="f", g="g", h="h")
check("a", "b", "c", "d", "e", "f", g="g", h="h")
check("a", "b", "c", "d", "e", "f", "g", h="h")
self.assertRaises(TypeError, fn, "a", "b", "c", "d", "e", "f", "g", "h")

def test_depr_kwd_required_1(self):
fn = ac_tester.depr_kwd_required_1
fn("a", "b")
Expand Down Expand Up @@ -3452,6 +3468,44 @@ def test_depr_kwd_noinline(self):
self.assertRaises(TypeError, fn, "a", c="c")
self.assertRaises(TypeError, fn, a="a", b="b", c="c")

def test_depr_kwd_multi(self):
fn = ac_tester.depr_kwd_multi
fn("a", "b", "c", "d", "e", "f", "g", h="h")
errmsg = (
"Passing keyword arguments 'b', 'c', 'd', 'e', 'f' and 'g' to depr_kwd_multi() is deprecated. "
"Parameter 'b' will become positional-only in Python 3.14. "
"Parameters 'c' and 'd' will become positional-only in Python 3.15. "
"Parameters 'e', 'f' and 'g' will become positional-only in Python 3.16.")
check = partial(self.check_depr, re.escape(errmsg), fn)
check("a", "b", "c", "d", "e", "f", g="g", h="h")
check("a", "b", "c", "d", "e", f="f", g="g", h="h")
check("a", "b", "c", "d", e="e", f="f", g="g", h="h")
check("a", "b", "c", d="d", e="e", f="f", g="g", h="h")
check("a", "b", c="c", d="d", e="e", f="f", g="g", h="h")
check("a", b="b", c="c", d="d", e="e", f="f", g="g", h="h")
self.assertRaises(TypeError, fn, a="a", b="b", c="c", d="d", e="e", f="f", g="g", h="h")

def test_depr_multi(self):
fn = ac_tester.depr_multi
self.assertRaises(TypeError, fn, "a", "b", "c", "d", "e", "f", "g")
errmsg = (
"Passing more than 4 positional arguments to depr_multi() is deprecated. "
"Parameter 'e' will become a keyword-only parameter in Python 3.15. "
"Parameter 'f' will become a keyword-only parameter in Python 3.14.")
check = partial(self.check_depr, re.escape(errmsg), fn)
check("a", "b", "c", "d", "e", "f", g="g")
check("a", "b", "c", "d", "e", f="f", g="g")
fn("a", "b", "c", "d", e="e", f="f", g="g")
fn("a", "b", "c", d="d", e="e", f="f", g="g")
errmsg = (
"Passing keyword arguments 'b' and 'c' to depr_multi() is deprecated. "
"Parameter 'b' will become positional-only in Python 3.14. "
"Parameter 'c' will become positional-only in Python 3.15.")
check = partial(self.check_depr, re.escape(errmsg), fn)
check("a", "b", c="c", d="d", e="e", f="f", g="g")
check("a", b="b", c="c", d="d", e="e", f="f", g="g")
self.assertRaises(TypeError, fn, a="a", b="b", c="c", d="d", e="e", f="f", g="g")


class PermutationTests(unittest.TestCase):
"""Test permutation support functions."""
Expand Down
82 changes: 82 additions & 0 deletions Modules/_testclinic.c
Original file line number Diff line number Diff line change
Expand Up @@ -1580,6 +1580,32 @@ depr_star_noinline_impl(PyObject *module, PyObject *a, PyObject *b,
}


/*[clinic input]
depr_star_multi
a: object
* [from 3.16]
b: object
* [from 3.15]
c: object
d: object
* [from 3.14]
e: object
f: object
g: object
*
h: object
[clinic start generated code]*/

static PyObject *
depr_star_multi_impl(PyObject *module, PyObject *a, PyObject *b, PyObject *c,
PyObject *d, PyObject *e, PyObject *f, PyObject *g,
PyObject *h)
/*[clinic end generated code: output=77681653f4202068 input=3ebd05d888a957ea]*/
{
Py_RETURN_NONE;
}


/*[clinic input]
depr_kwd_required_1
a: object
Expand Down Expand Up @@ -1702,6 +1728,59 @@ depr_kwd_noinline_impl(PyObject *module, PyObject *a, PyObject *b,
Py_RETURN_NONE;
}


/*[clinic input]
depr_kwd_multi
a: object
/
b: object
/ [from 3.14]
c: object
d: object
/ [from 3.15]
e: object
f: object
g: object
/ [from 3.16]
h: object
[clinic start generated code]*/

static PyObject *
depr_kwd_multi_impl(PyObject *module, PyObject *a, PyObject *b, PyObject *c,
PyObject *d, PyObject *e, PyObject *f, PyObject *g,
PyObject *h)
/*[clinic end generated code: output=ddfbde80fe1942e1 input=7a074e621c79efd7]*/
{
Py_RETURN_NONE;
}


/*[clinic input]
depr_multi
a: object
/
b: object
/ [from 3.14]
c: object
/ [from 3.15]
d: object
* [from 3.15]
e: object
* [from 3.14]
f: object
*
g: object
[clinic start generated code]*/

static PyObject *
depr_multi_impl(PyObject *module, PyObject *a, PyObject *b, PyObject *c,
PyObject *d, PyObject *e, PyObject *f, PyObject *g)
/*[clinic end generated code: output=f81c92852ca2d4ee input=5b847c5e44bedd02]*/
{
Py_RETURN_NONE;
}


// Reset PY_VERSION_HEX
#undef PY_VERSION_HEX
#define PY_VERSION_HEX _SAVED_PY_VERSION
Expand Down Expand Up @@ -1779,13 +1858,16 @@ static PyMethodDef tester_methods[] = {
DEPR_STAR_POS2_LEN2_METHODDEF
DEPR_STAR_POS2_LEN2_WITH_KWD_METHODDEF
DEPR_STAR_NOINLINE_METHODDEF
DEPR_STAR_MULTI_METHODDEF
DEPR_KWD_REQUIRED_1_METHODDEF
DEPR_KWD_REQUIRED_2_METHODDEF
DEPR_KWD_OPTIONAL_1_METHODDEF
DEPR_KWD_OPTIONAL_2_METHODDEF
DEPR_KWD_OPTIONAL_3_METHODDEF
DEPR_KWD_REQUIRED_OPTIONAL_METHODDEF
DEPR_KWD_NOINLINE_METHODDEF
DEPR_KWD_MULTI_METHODDEF
DEPR_MULTI_METHODDEF
{NULL, NULL}
};

Expand Down
Loading