From 059cd6a3ea86beb2afa95a414f541781df16acaf Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 8 Aug 2023 12:18:40 +0300 Subject: [PATCH 01/13] gh-107704: Argument Clinic: add support for deprecating keyword use of parameters It is now possible to deprecate passing keyword arguments for keyword-or-positional parameters with Argument Clinic, using the new '/ [from X.Y]' syntax. (To be read as "positional-only from Python version X.Y") --- Lib/test/test_clinic.py | 315 ++++- ...-08-15-19-50-49.gh-issue-107704.Uu84vd.rst | 4 + Modules/_sqlite/clinic/connection.c.h | 42 +- Modules/_testclinic.c | 185 +++ Modules/clinic/_testclinic_depr_star.c.h | 1199 +++++++++++++---- Tools/clinic/clinic.py | 327 +++-- 6 files changed, 1672 insertions(+), 400 deletions(-) create mode 100644 Misc/NEWS.d/next/Tools-Demos/2023-08-15-19-50-49.gh-issue-107704.Uu84vd.rst diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 896730571d8152..375a10e404b107 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -1627,7 +1627,7 @@ def test_depr_star_invalid_format_1(self): Docstring. """ err = ( - "Function 'foo.bar': expected format '* [from major.minor]' " + "Function 'foo.bar': expected format '[from major.minor]' " "where 'major' and 'minor' are integers; got '3'" ) self.expect_failure(block, err, lineno=3) @@ -1641,7 +1641,7 @@ def test_depr_star_invalid_format_2(self): Docstring. """ err = ( - "Function 'foo.bar': expected format '* [from major.minor]' " + "Function 'foo.bar': expected format '[from major.minor]' " "where 'major' and 'minor' are integers; got 'a.b'" ) self.expect_failure(block, err, lineno=3) @@ -1655,7 +1655,7 @@ def test_depr_star_invalid_format_3(self): Docstring. """ err = ( - "Function 'foo.bar': expected format '* [from major.minor]' " + "Function 'foo.bar': expected format '[from major.minor]' " "where 'major' and 'minor' are integers; got '1.2.3'" ) self.expect_failure(block, err, lineno=3) @@ -1674,6 +1674,22 @@ def test_parameters_required_after_depr_star(self): ) self.expect_failure(block, err, lineno=4) + def test_parameters_required_after_depr_star2(self): + block = """ + module foo + foo.bar + a: int + * [from 3.14] + * + b: int + Docstring. + """ + err = ( + "Function 'foo.bar' specifies '* [from ...]' without " + "any parameters afterwards" + ) + self.expect_failure(block, err, lineno=4) + def test_depr_star_must_come_before_star(self): block = """ module foo @@ -1697,7 +1713,21 @@ def test_depr_star_duplicate(self): c: int Docstring. """ - err = "Function 'foo.bar' uses '[from ...]' more than once" + err = "Function 'foo.bar' uses '* [from ...]' more than once." + self.expect_failure(block, err, lineno=5) + + def test_depr_slash_duplicate(self): + block = """ + module foo + foo.bar + a: int + / [from 3.14] + b: int + / [from 3.14] + c: int + Docstring. + """ + err = "Function 'bar' uses '/ [from ...]' more than once." self.expect_failure(block, err, lineno=5) def test_single_slash(self): @@ -1713,6 +1743,48 @@ def test_single_slash(self): ) self.expect_failure(block, err) + def test_parameters_required_before_depr_slash(self): + block = """ + module foo + foo.bar + / [from 3.14] + Docstring. + """ + err = ( + "Function 'bar' specifies '/ [from ...]' without " + "any parameters beforehead." + ) + self.expect_failure(block, err, lineno=2) + + def test_parameters_required_before_depr_slash2(self): + block = """ + module foo + foo.bar + / + / [from 3.14] + Docstring. + """ + err = ( + "Function 'bar' has an unsupported group configuration. " + "(Unexpected state 0.d)" + ) + self.expect_failure(block, err, lineno=2) + + def test_parameters_required_before_depr_slash3(self): + block = """ + module foo + foo.bar + a: int + / + / [from 3.14] + Docstring. + """ + err = ( + "Function 'bar' specifies '/ [from ...]' without " + "any parameters beforehead." + ) + self.expect_failure(block, err, lineno=4) + def test_double_slash(self): block = """ module foo @@ -1741,6 +1813,67 @@ def test_mix_star_and_slash(self): ) self.expect_failure(block, err) + def test_depr_star_must_come_after_slash(self): + block = """ + module foo + foo.bar + a: int + * [from 3.14] + / + b: int + Docstring. + """ + err = ( + "Function 'bar' mixes keyword-only and positional-only parameters, " + "which is unsupported." + ) + self.expect_failure(block, err, lineno=4) + + def test_depr_star_must_come_after_depr_slash(self): + block = """ + module foo + foo.bar + a: int + * [from 3.14] + / [from 3.14] + b: int + Docstring. + """ + err = ( + "Function 'bar' mixes keyword-only and positional-only parameters, " + "which is unsupported." + ) + self.expect_failure(block, err, lineno=4) + + def test_star_must_come_after_depr_slash(self): + block = """ + module foo + foo.bar + a: int + * + / [from 3.14] + b: int + Docstring. + """ + err = ( + "Function 'bar' mixes keyword-only and positional-only parameters, " + "which is unsupported." + ) + self.expect_failure(block, err, lineno=4) + + def test_depr_slash_must_come_after_slash(self): + block = """ + module foo + foo.bar + a: int + / [from 3.14] + / + b: int + Docstring. + """ + err = "Function 'foo.bar': '/ [from ...]' must come after '/'" + self.expect_failure(block, err, lineno=4) + def test_parameters_not_permitted_after_slash_for_now(self): block = """ module foo @@ -2537,11 +2670,33 @@ 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, **kwds): + def check_depr_star(self, pnames, fn, *args, name=None, **kwds): + if name is None: + name = fn.__qualname__ + if isinstance(fn, type): + name = f'{fn.__module__}.{name}' regex = ( fr"Passing( more than)?( [0-9]+)? positional argument(s)? to " - fr"{fn.__name__}\(\) is deprecated. Parameter(s)? {pnames} will " - fr"become( a)? keyword-only parameter(s)? in Python 3\.14" + 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) + + def check_depr_kwd(self, pnames, fn, *args, name=None, **kwds): + if name is None: + name = fn.__qualname__ + if isinstance(fn, type): + name = f'{fn.__module__}.{name}' + pl = 's' if ' ' in pnames else '' + regex = ( + fr"Passing keyword argument{pl} {pnames} to " + fr"{re.escape(name)}\(\) is deprecated. Corresponding parameter{pl} " + 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 @@ -3015,46 +3170,40 @@ def test_cloned_func_with_converter_exception_message(self): self.assertEqual(func(), name) def test_depr_star_new(self): - regex = re.escape( - "Passing positional arguments to _testclinic.DeprStarNew() is " - "deprecated. Parameter 'a' will become a keyword-only parameter " - "in Python 3.14." - ) - with self.assertWarnsRegex(DeprecationWarning, regex) as cm: - ac_tester.DeprStarNew(None) - self.assertEqual(cm.filename, __file__) + cls = ac_tester.DeprStarNew + cls(a=None) + self.check_depr_star("'a'", cls, None) + self.assertRaises(TypeError, cls) def test_depr_star_new_cloned(self): - regex = re.escape( - "Passing positional arguments to _testclinic.DeprStarNew.cloned() " - "is deprecated. Parameter 'a' will become a keyword-only parameter " - "in Python 3.14." - ) - obj = ac_tester.DeprStarNew(a=None) - with self.assertWarnsRegex(DeprecationWarning, regex) as cm: - obj.cloned(None) - self.assertEqual(cm.filename, __file__) + fn = ac_tester.DeprStarNew(a=None).cloned + fn(a=None) + self.check_depr_star("'a'", fn, None, name='_testclinic.DeprStarNew.cloned') + self.assertRaises(TypeError, fn) def test_depr_star_init(self): - regex = re.escape( - "Passing positional arguments to _testclinic.DeprStarInit() is " - "deprecated. Parameter 'a' will become a keyword-only parameter " - "in Python 3.14." - ) - with self.assertWarnsRegex(DeprecationWarning, regex) as cm: - ac_tester.DeprStarInit(None) - self.assertEqual(cm.filename, __file__) + cls = ac_tester.DeprStarInit + cls(a=None) + self.check_depr_star("'a'", cls, None) + self.assertRaises(TypeError, cls) def test_depr_star_init_cloned(self): - regex = re.escape( - "Passing positional arguments to _testclinic.DeprStarInit.cloned() " - "is deprecated. Parameter 'a' will become a keyword-only parameter " - "in Python 3.14." - ) - obj = ac_tester.DeprStarInit(a=None) - with self.assertWarnsRegex(DeprecationWarning, regex) as cm: - obj.cloned(None) - self.assertEqual(cm.filename, __file__) + fn = ac_tester.DeprStarInit(a=None).cloned + fn(a=None) + self.check_depr_star("'a'", fn, None, name='_testclinic.DeprStarInit.cloned') + self.assertRaises(TypeError, fn) + + def test_depr_kwd_new(self): + cls = ac_tester.DeprKwdNew + cls(None) + self.check_depr_kwd("'a'", cls, a=None) + self.assertRaises(TypeError, cls) + + def test_depr_kwd_init(self): + cls = ac_tester.DeprKwdInit + cls(None) + self.check_depr_kwd("'a'", cls, a=None) + self.assertRaises(TypeError, cls) def test_depr_star_pos0_len1(self): fn = ac_tester.depr_star_pos0_len1 @@ -3125,6 +3274,90 @@ def test_depr_star_pos2_len2_with_kwd(self): check("a", "b", "c", d=0, e=0) check("a", "b", "c", "d", e=0) + def test_depr_kwd_required_1(self): + fn = ac_tester.depr_kwd_required_1 + fn("a", "b") + self.assertRaises(TypeError, fn, "a") + self.assertRaises(TypeError, fn, "a", "b", "c") + check = partial(self.check_depr_kwd, "'b'", fn) + check("a", b="b") + self.assertRaises(TypeError, fn, a="a", b="b") + + def test_depr_kwd_required_2(self): + fn = ac_tester.depr_kwd_required_2 + fn("a", "b", "c") + self.assertRaises(TypeError, fn, "a", "b") + self.assertRaises(TypeError, fn, "a", "b", "c", "d") + check = partial(self.check_depr_kwd, "'b' and 'c'", fn) + check("a", "b", c="c") + check("a", b="b", c="c") + self.assertRaises(TypeError, fn, a="a", b="b", c="c") + + def test_depr_kwd_optional_1(self): + fn = ac_tester.depr_kwd_optional_1 + fn("a") + fn("a", "b") + self.assertRaises(TypeError, fn) + self.assertRaises(TypeError, fn, "a", "b", "c") + check = partial(self.check_depr_kwd, "'b'", fn) + check("a", b="b") + self.assertRaises(TypeError, fn, a="a", b="b") + + def test_depr_kwd_optional_2(self): + fn = ac_tester.depr_kwd_optional_2 + fn("a") + fn("a", "b") + fn("a", "b", "c") + self.assertRaises(TypeError, fn) + self.assertRaises(TypeError, fn, "a", "b", "c", "d") + check = partial(self.check_depr_kwd, "'b' and 'c'", fn) + check("a", b="b") + check("a", c="c") + check("a", b="b", c="c") + check("a", c="c", b="b") + check("a", "b", c="c") + self.assertRaises(TypeError, fn, a="a", b="b", c="c") + + def test_depr_kwd_optional_3(self): + fn = ac_tester.depr_kwd_optional_3 + fn() + fn("a") + fn("a", "b") + fn("a", "b", "c") + self.assertRaises(TypeError, fn, "a", "b", "c", "d") + check = partial(self.check_depr_kwd, "'a', 'b' and 'c'", fn) + check("a", "b", c="c") + check("a", b="b") + check(a="a") + + def test_depr_kwd_required_optional(self): + fn = ac_tester.depr_kwd_required_optional + fn("a", "b") + fn("a", "b", "c") + self.assertRaises(TypeError, fn) + self.assertRaises(TypeError, fn, "a") + self.assertRaises(TypeError, fn, "a", "b", "c", "d") + check = partial(self.check_depr_kwd, "'b' and 'c'", fn) + check("a", b="b") + check("a", b="b", c="c") + check("a", c="c", b="b") + check("a", "b", c="c") + self.assertRaises(TypeError, fn, "a", c="c") + self.assertRaises(TypeError, fn, a="a", b="b", c="c") + + def test_depr_kwd_noinline(self): + fn = ac_tester.depr_kwd_noinline + fn("a", "b") + fn("a", "b", "c") + self.assertRaises(TypeError, fn, "a") + check = partial(self.check_depr_kwd, "'b' and 'c'", fn) + check("a", b="b") + check("a", b="b", c="c") + check("a", c="c", b="b") + check("a", "b", c="c") + self.assertRaises(TypeError, fn, "a", c="c") + self.assertRaises(TypeError, fn, a="a", b="b", c="c") + class PermutationTests(unittest.TestCase): """Test permutation support functions.""" diff --git a/Misc/NEWS.d/next/Tools-Demos/2023-08-15-19-50-49.gh-issue-107704.Uu84vd.rst b/Misc/NEWS.d/next/Tools-Demos/2023-08-15-19-50-49.gh-issue-107704.Uu84vd.rst new file mode 100644 index 00000000000000..ffdcfa6a429e64 --- /dev/null +++ b/Misc/NEWS.d/next/Tools-Demos/2023-08-15-19-50-49.gh-issue-107704.Uu84vd.rst @@ -0,0 +1,4 @@ +It is now possible to deprecate passing keyword arguments for +keyword-or-positional parameters with Argument Clinic, using the new ``/ +[from X.Y]`` syntax. (To be read as *"positional-only from Python version +X.Y"*.) See :ref:`clinic-howto-deprecate-keyword` for more information. diff --git a/Modules/_sqlite/clinic/connection.c.h b/Modules/_sqlite/clinic/connection.c.h index af98d61ea7cccc..b744ededd03d72 100644 --- a/Modules/_sqlite/clinic/connection.c.h +++ b/Modules/_sqlite/clinic/connection.c.h @@ -16,6 +16,22 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database, int cache_size, int uri, enum autocommit_mode autocommit); +// Emit compiler warnings when we get to Python 3.15. +#if PY_VERSION_HEX >= 0x030f00C0 +# error \ + "In connection.c, update the clinic input of " \ + "'_sqlite3.Connection.__init__'." +#elif PY_VERSION_HEX >= 0x030f00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In connection.c, update the clinic input of " \ + "'_sqlite3.Connection.__init__'.") +# else +# warning \ + "In connection.c, update the clinic input of " \ + "'_sqlite3.Connection.__init__'." +# endif +#endif static int pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs) { @@ -59,28 +75,6 @@ pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs) int uri = 0; enum autocommit_mode autocommit = LEGACY_TRANSACTION_CONTROL; - // Emit compiler warnings when we get to Python 3.15. - #if PY_VERSION_HEX >= 0x030f00C0 - # error \ - "In connection.c, update parameter(s) 'timeout', 'detect_types', " \ - "'isolation_level', 'check_same_thread', 'factory', " \ - "'cached_statements' and 'uri' in the clinic input of " \ - "'_sqlite3.Connection.__init__' to be keyword-only." - #elif PY_VERSION_HEX >= 0x030f00A0 - # ifdef _MSC_VER - # pragma message ( \ - "In connection.c, update parameter(s) 'timeout', 'detect_types', " \ - "'isolation_level', 'check_same_thread', 'factory', " \ - "'cached_statements' and 'uri' in the clinic input of " \ - "'_sqlite3.Connection.__init__' to be keyword-only.") - # else - # warning \ - "In connection.c, update parameter(s) 'timeout', 'detect_types', " \ - "'isolation_level', 'check_same_thread', 'factory', " \ - "'cached_statements' and 'uri' in the clinic input of " \ - "'_sqlite3.Connection.__init__' to be keyword-only." - # endif - #endif if (nargs > 1 && nargs <= 8) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing more than 1 positional argument to _sqlite3.Connection()" @@ -89,7 +83,7 @@ pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs) "'cached_statements' and 'uri' will become keyword-only " "parameters in Python 3.15.", 1)) { - goto exit; + goto exit; } } fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 1, 8, 0, argsbuf); @@ -1692,4 +1686,4 @@ getconfig(pysqlite_Connection *self, PyObject *arg) #ifndef DESERIALIZE_METHODDEF #define DESERIALIZE_METHODDEF #endif /* !defined(DESERIALIZE_METHODDEF) */ -/*[clinic end generated code: output=5a05e5294ad9d2ce input=a9049054013a1b77]*/ +/*[clinic end generated code: output=08b3a3d33755994a input=a9049054013a1b77]*/ diff --git a/Modules/_testclinic.c b/Modules/_testclinic.c index c33536234af0bc..426bbec2a63d50 100644 --- a/Modules/_testclinic.c +++ b/Modules/_testclinic.c @@ -1298,6 +1298,56 @@ static PyTypeObject DeprStarInit = { }; +/*[clinic input] +class _testclinic.DeprKwdNew "PyObject *" "PyObject" +@classmethod +_testclinic.DeprKwdNew.__new__ as depr_kwd_new + a: object + / [from 3.14] +The deprecation message should use the class name instead of __new__. +[clinic start generated code]*/ + +static PyObject * +depr_kwd_new_impl(PyTypeObject *type, PyObject *a) +/*[clinic end generated code: output=618d07afc5616149 input=831cfc26b33703a6]*/ +{ + return type->tp_alloc(type, 0); +} + +static PyTypeObject DeprKwdNew = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "_testclinic.DeprKwdNew", + .tp_basicsize = sizeof(PyObject), + .tp_new = depr_kwd_new, + .tp_flags = Py_TPFLAGS_DEFAULT, +}; + + +/*[clinic input] +class _testclinic.DeprKwdInit "PyObject *" "PyObject" +_testclinic.DeprKwdInit.__init__ as depr_kwd_init + a: object + / [from 3.14] +The deprecation message should use the class name instead of __init__. +[clinic start generated code]*/ + +static int +depr_kwd_init_impl(PyObject *self, PyObject *a) +/*[clinic end generated code: output=6e02eb724a85d840 input=2e85cfe2845593f6]*/ +{ + return 0; +} + +static PyTypeObject DeprKwdInit = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "_testclinic.DeprKwdInit", + .tp_basicsize = sizeof(PyObject), + .tp_new = PyType_GenericNew, + .tp_init = depr_kwd_init, + .tp_flags = Py_TPFLAGS_DEFAULT, +}; + + /*[clinic input] depr_star_pos0_len1 * [from 3.14] @@ -1450,6 +1500,128 @@ depr_star_pos2_len2_with_kwd_impl(PyObject *module, PyObject *a, PyObject *b, } +/*[clinic input] +depr_kwd_required_1 + a: object + / + b: object + / [from 3.14] +[clinic start generated code]*/ + +static PyObject * +depr_kwd_required_1_impl(PyObject *module, PyObject *a, PyObject *b) +/*[clinic end generated code: output=1d8ab19ea78418af input=53f2c398b828462d]*/ +{ + Py_RETURN_NONE; +} + + +/*[clinic input] +depr_kwd_required_2 + a: object + / + b: object + c: object + / [from 3.14] +[clinic start generated code]*/ + +static PyObject * +depr_kwd_required_2_impl(PyObject *module, PyObject *a, PyObject *b, + PyObject *c) +/*[clinic end generated code: output=44a89cb82509ddde input=a2b0ef37de8a01a7]*/ +{ + Py_RETURN_NONE; +} + + +/*[clinic input] +depr_kwd_optional_1 + a: object + / + b: object = None + / [from 3.14] +[clinic start generated code]*/ + +static PyObject * +depr_kwd_optional_1_impl(PyObject *module, PyObject *a, PyObject *b) +/*[clinic end generated code: output=a8a3d67efcc7b058 input=e416981eb78c3053]*/ +{ + Py_RETURN_NONE; +} + + +/*[clinic input] +depr_kwd_optional_2 + a: object + / + b: object = None + c: object = None + / [from 3.14] +[clinic start generated code]*/ + +static PyObject * +depr_kwd_optional_2_impl(PyObject *module, PyObject *a, PyObject *b, + PyObject *c) +/*[clinic end generated code: output=aa2d967f26fdb9f6 input=cae3afb783bfc855]*/ +{ + Py_RETURN_NONE; +} + + +/*[clinic input] +depr_kwd_optional_3 + a: object = None + b: object = None + c: object = None + / [from 3.14] +[clinic start generated code]*/ + +static PyObject * +depr_kwd_optional_3_impl(PyObject *module, PyObject *a, PyObject *b, + PyObject *c) +/*[clinic end generated code: output=a26025bf6118fd07 input=c9183b2f9ccaf992]*/ +{ + Py_RETURN_NONE; +} + + +/*[clinic input] +depr_kwd_required_optional + a: object + / + b: object + c: object = None + / [from 3.14] +[clinic start generated code]*/ + +static PyObject * +depr_kwd_required_optional_impl(PyObject *module, PyObject *a, PyObject *b, + PyObject *c) +/*[clinic end generated code: output=e53a8b7a250d8ffc input=23237a046f8388f5]*/ +{ + Py_RETURN_NONE; +} + + +/*[clinic input] +depr_kwd_noinline + a: object + / + b: object + c: object = None + / [from 3.14] + # Force to use _PyArg_ParseStackAndKeywords. + d: str(accept={str, robuffer}, zeroes=True) = '' +[clinic start generated code]*/ + +static PyObject * +depr_kwd_noinline_impl(PyObject *module, PyObject *a, PyObject *b, + PyObject *c, const char *d, Py_ssize_t d_length) +/*[clinic end generated code: output=f59da8113f2bad7c input=1d6db65bebb069d7]*/ +{ + Py_RETURN_NONE; +} + // Reset PY_VERSION_HEX #undef PY_VERSION_HEX #define PY_VERSION_HEX _SAVED_PY_VERSION @@ -1526,6 +1698,13 @@ static PyMethodDef tester_methods[] = { DEPR_STAR_POS2_LEN1_METHODDEF DEPR_STAR_POS2_LEN2_METHODDEF DEPR_STAR_POS2_LEN2_WITH_KWD_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 {NULL, NULL} }; @@ -1549,6 +1728,12 @@ PyInit__testclinic(void) if (PyModule_AddType(m, &DeprStarInit) < 0) { goto error; } + if (PyModule_AddType(m, &DeprKwdNew) < 0) { + goto error; + } + if (PyModule_AddType(m, &DeprKwdInit) < 0) { + goto error; + } return m; error: diff --git a/Modules/clinic/_testclinic_depr_star.c.h b/Modules/clinic/_testclinic_depr_star.c.h index 1aa42dd4059777..0017975c84f144 100644 --- a/Modules/clinic/_testclinic_depr_star.c.h +++ b/Modules/clinic/_testclinic_depr_star.c.h @@ -22,6 +22,22 @@ PyDoc_STRVAR(depr_star_new__doc__, static PyObject * depr_star_new_impl(PyTypeObject *type, PyObject *a); +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of " \ + "'_testclinic.DeprStarNew.__new__'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of " \ + "'_testclinic.DeprStarNew.__new__'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of " \ + "'_testclinic.DeprStarNew.__new__'." +# endif +#endif static PyObject * depr_star_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) { @@ -56,29 +72,13 @@ depr_star_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) Py_ssize_t nargs = PyTuple_GET_SIZE(args); PyObject *a; - // Emit compiler warnings when we get to Python 3.14. - #if PY_VERSION_HEX >= 0x030e00C0 - # error \ - "In _testclinic.c, update parameter(s) 'a' in the clinic input of" \ - " '_testclinic.DeprStarNew.__new__' to be keyword-only." - #elif PY_VERSION_HEX >= 0x030e00A0 - # ifdef _MSC_VER - # pragma message ( \ - "In _testclinic.c, update parameter(s) 'a' in the clinic input of" \ - " '_testclinic.DeprStarNew.__new__' to be keyword-only.") - # else - # warning \ - "In _testclinic.c, update parameter(s) 'a' in the clinic input of" \ - " '_testclinic.DeprStarNew.__new__' to be keyword-only." - # endif - #endif if (nargs == 1) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing positional arguments to _testclinic.DeprStarNew() is " "deprecated. Parameter 'a' will become a keyword-only parameter " "in Python 3.14.", 1)) { - goto exit; + goto exit; } } fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 1, 1, 0, argsbuf); @@ -107,6 +107,22 @@ PyDoc_STRVAR(depr_star_new_clone__doc__, static PyObject * depr_star_new_clone_impl(PyObject *type, PyObject *a); +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of " \ + "'_testclinic.DeprStarNew.cloned'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of " \ + "'_testclinic.DeprStarNew.cloned'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of " \ + "'_testclinic.DeprStarNew.cloned'." +# endif +#endif static PyObject * depr_star_new_clone(PyObject *type, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -139,29 +155,13 @@ depr_star_new_clone(PyObject *type, PyObject *const *args, Py_ssize_t nargs, PyO PyObject *argsbuf[1]; PyObject *a; - // Emit compiler warnings when we get to Python 3.14. - #if PY_VERSION_HEX >= 0x030e00C0 - # error \ - "In _testclinic.c, update parameter(s) 'a' in the clinic input of" \ - " '_testclinic.DeprStarNew.cloned' to be keyword-only." - #elif PY_VERSION_HEX >= 0x030e00A0 - # ifdef _MSC_VER - # pragma message ( \ - "In _testclinic.c, update parameter(s) 'a' in the clinic input of" \ - " '_testclinic.DeprStarNew.cloned' to be keyword-only.") - # else - # warning \ - "In _testclinic.c, update parameter(s) 'a' in the clinic input of" \ - " '_testclinic.DeprStarNew.cloned' to be keyword-only." - # endif - #endif if (nargs == 1) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing positional arguments to _testclinic.DeprStarNew.cloned()" " is deprecated. Parameter 'a' will become a keyword-only " "parameter in Python 3.14.", 1)) { - goto exit; + goto exit; } } args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); @@ -189,6 +189,22 @@ PyDoc_STRVAR(depr_star_init__doc__, static int depr_star_init_impl(PyObject *self, PyObject *a); +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of " \ + "'_testclinic.DeprStarInit.__init__'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of " \ + "'_testclinic.DeprStarInit.__init__'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of " \ + "'_testclinic.DeprStarInit.__init__'." +# endif +#endif static int depr_star_init(PyObject *self, PyObject *args, PyObject *kwargs) { @@ -223,29 +239,13 @@ depr_star_init(PyObject *self, PyObject *args, PyObject *kwargs) Py_ssize_t nargs = PyTuple_GET_SIZE(args); PyObject *a; - // Emit compiler warnings when we get to Python 3.14. - #if PY_VERSION_HEX >= 0x030e00C0 - # error \ - "In _testclinic.c, update parameter(s) 'a' in the clinic input of" \ - " '_testclinic.DeprStarInit.__init__' to be keyword-only." - #elif PY_VERSION_HEX >= 0x030e00A0 - # ifdef _MSC_VER - # pragma message ( \ - "In _testclinic.c, update parameter(s) 'a' in the clinic input of" \ - " '_testclinic.DeprStarInit.__init__' to be keyword-only.") - # else - # warning \ - "In _testclinic.c, update parameter(s) 'a' in the clinic input of" \ - " '_testclinic.DeprStarInit.__init__' to be keyword-only." - # endif - #endif if (nargs == 1) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing positional arguments to _testclinic.DeprStarInit() is " "deprecated. Parameter 'a' will become a keyword-only parameter " "in Python 3.14.", 1)) { - goto exit; + goto exit; } } fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 1, 1, 0, argsbuf); @@ -274,6 +274,22 @@ PyDoc_STRVAR(depr_star_init_clone__doc__, static PyObject * depr_star_init_clone_impl(PyObject *self, PyObject *a); +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of " \ + "'_testclinic.DeprStarInit.cloned'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of " \ + "'_testclinic.DeprStarInit.cloned'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of " \ + "'_testclinic.DeprStarInit.cloned'." +# endif +#endif static PyObject * depr_star_init_clone(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -306,29 +322,13 @@ depr_star_init_clone(PyObject *self, PyObject *const *args, Py_ssize_t nargs, Py PyObject *argsbuf[1]; PyObject *a; - // Emit compiler warnings when we get to Python 3.14. - #if PY_VERSION_HEX >= 0x030e00C0 - # error \ - "In _testclinic.c, update parameter(s) 'a' in the clinic input of" \ - " '_testclinic.DeprStarInit.cloned' to be keyword-only." - #elif PY_VERSION_HEX >= 0x030e00A0 - # ifdef _MSC_VER - # pragma message ( \ - "In _testclinic.c, update parameter(s) 'a' in the clinic input of" \ - " '_testclinic.DeprStarInit.cloned' to be keyword-only.") - # else - # warning \ - "In _testclinic.c, update parameter(s) 'a' in the clinic input of" \ - " '_testclinic.DeprStarInit.cloned' to be keyword-only." - # endif - #endif if (nargs == 1) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing positional arguments to " "_testclinic.DeprStarInit.cloned() is deprecated. Parameter 'a' " "will become a keyword-only parameter in Python 3.14.", 1)) { - goto exit; + goto exit; } } args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); @@ -342,6 +342,164 @@ depr_star_init_clone(PyObject *self, PyObject *const *args, Py_ssize_t nargs, Py return return_value; } +PyDoc_STRVAR(depr_kwd_new__doc__, +"DeprKwdNew(a)\n" +"--\n" +"\n" +"The deprecation message should use the class name instead of __new__."); + +static PyObject * +depr_kwd_new_impl(PyTypeObject *type, PyObject *a); + +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of " \ + "'_testclinic.DeprKwdNew.__new__'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of " \ + "'_testclinic.DeprKwdNew.__new__'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of " \ + "'_testclinic.DeprKwdNew.__new__'." +# endif +#endif +static PyObject * +depr_kwd_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(a), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"a", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "DeprKwdNew", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject * const *fastargs; + Py_ssize_t nargs = PyTuple_GET_SIZE(args); + PyObject *a; + + fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 1, 1, 0, argsbuf); + if (!fastargs) { + goto exit; + } + if (nargs < 1) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Passing keyword argument 'a' to _testclinic.DeprKwdNew() is " + "deprecated. Corresponding parameter will become positional-only " + "in Python 3.14.", 1)) + { + goto exit; + } + } + a = fastargs[0]; + return_value = depr_kwd_new_impl(type, a); + +exit: + return return_value; +} + +PyDoc_STRVAR(depr_kwd_init__doc__, +"DeprKwdInit(a)\n" +"--\n" +"\n" +"The deprecation message should use the class name instead of __init__."); + +static int +depr_kwd_init_impl(PyObject *self, PyObject *a); + +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of " \ + "'_testclinic.DeprKwdInit.__init__'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of " \ + "'_testclinic.DeprKwdInit.__init__'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of " \ + "'_testclinic.DeprKwdInit.__init__'." +# endif +#endif +static int +depr_kwd_init(PyObject *self, PyObject *args, PyObject *kwargs) +{ + int return_value = -1; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(a), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"a", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "DeprKwdInit", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject * const *fastargs; + Py_ssize_t nargs = PyTuple_GET_SIZE(args); + PyObject *a; + + fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 1, 1, 0, argsbuf); + if (!fastargs) { + goto exit; + } + if (nargs < 1) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Passing keyword argument 'a' to _testclinic.DeprKwdInit() is " + "deprecated. Corresponding parameter will become positional-only " + "in Python 3.14.", 1)) + { + goto exit; + } + } + a = fastargs[0]; + return_value = depr_kwd_init_impl(self, a); + +exit: + return return_value; +} + PyDoc_STRVAR(depr_star_pos0_len1__doc__, "depr_star_pos0_len1($module, /, a)\n" "--\n" @@ -357,6 +515,19 @@ PyDoc_STRVAR(depr_star_pos0_len1__doc__, static PyObject * depr_star_pos0_len1_impl(PyObject *module, PyObject *a); +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of 'depr_star_pos0_len1'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of 'depr_star_pos0_len1'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of 'depr_star_pos0_len1'." +# endif +#endif static PyObject * depr_star_pos0_len1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -389,29 +560,13 @@ depr_star_pos0_len1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P PyObject *argsbuf[1]; PyObject *a; - // Emit compiler warnings when we get to Python 3.14. - #if PY_VERSION_HEX >= 0x030e00C0 - # error \ - "In _testclinic.c, update parameter(s) 'a' in the clinic input of" \ - " 'depr_star_pos0_len1' to be keyword-only." - #elif PY_VERSION_HEX >= 0x030e00A0 - # ifdef _MSC_VER - # pragma message ( \ - "In _testclinic.c, update parameter(s) 'a' in the clinic input of" \ - " 'depr_star_pos0_len1' to be keyword-only.") - # else - # warning \ - "In _testclinic.c, update parameter(s) 'a' in the clinic input of" \ - " 'depr_star_pos0_len1' to be keyword-only." - # endif - #endif if (nargs == 1) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing positional arguments to depr_star_pos0_len1() is " "deprecated. Parameter 'a' will become a keyword-only parameter " "in Python 3.14.", 1)) { - goto exit; + goto exit; } } args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); @@ -440,6 +595,19 @@ PyDoc_STRVAR(depr_star_pos0_len2__doc__, static PyObject * depr_star_pos0_len2_impl(PyObject *module, PyObject *a, PyObject *b); +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of 'depr_star_pos0_len2'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of 'depr_star_pos0_len2'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of 'depr_star_pos0_len2'." +# endif +#endif static PyObject * depr_star_pos0_len2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -473,29 +641,13 @@ depr_star_pos0_len2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P PyObject *a; PyObject *b; - // Emit compiler warnings when we get to Python 3.14. - #if PY_VERSION_HEX >= 0x030e00C0 - # error \ - "In _testclinic.c, update parameter(s) 'a' and 'b' in the clinic " \ - "input of 'depr_star_pos0_len2' to be keyword-only." - #elif PY_VERSION_HEX >= 0x030e00A0 - # ifdef _MSC_VER - # pragma message ( \ - "In _testclinic.c, update parameter(s) 'a' and 'b' in the clinic " \ - "input of 'depr_star_pos0_len2' to be keyword-only.") - # else - # warning \ - "In _testclinic.c, update parameter(s) 'a' and 'b' in the clinic " \ - "input of 'depr_star_pos0_len2' to be keyword-only." - # endif - #endif if (nargs > 0 && nargs <= 2) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing positional arguments to depr_star_pos0_len2() is " "deprecated. Parameters 'a' and 'b' will become keyword-only " "parameters in Python 3.14.", 1)) { - goto exit; + goto exit; } } args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 2, 0, argsbuf); @@ -526,6 +678,22 @@ static PyObject * depr_star_pos0_len3_with_kwd_impl(PyObject *module, PyObject *a, PyObject *b, PyObject *c, PyObject *d); +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of " \ + "'depr_star_pos0_len3_with_kwd'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of " \ + "'depr_star_pos0_len3_with_kwd'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of " \ + "'depr_star_pos0_len3_with_kwd'." +# endif +#endif static PyObject * depr_star_pos0_len3_with_kwd(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -561,32 +729,13 @@ depr_star_pos0_len3_with_kwd(PyObject *module, PyObject *const *args, Py_ssize_t PyObject *c; PyObject *d; - // Emit compiler warnings when we get to Python 3.14. - #if PY_VERSION_HEX >= 0x030e00C0 - # error \ - "In _testclinic.c, update parameter(s) 'a', 'b' and 'c' in the " \ - "clinic input of 'depr_star_pos0_len3_with_kwd' to be " \ - "keyword-only." - #elif PY_VERSION_HEX >= 0x030e00A0 - # ifdef _MSC_VER - # pragma message ( \ - "In _testclinic.c, update parameter(s) 'a', 'b' and 'c' in the " \ - "clinic input of 'depr_star_pos0_len3_with_kwd' to be " \ - "keyword-only.") - # else - # warning \ - "In _testclinic.c, update parameter(s) 'a', 'b' and 'c' in the " \ - "clinic input of 'depr_star_pos0_len3_with_kwd' to be " \ - "keyword-only." - # endif - #endif if (nargs > 0 && nargs <= 3) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing positional arguments to depr_star_pos0_len3_with_kwd() " "is deprecated. Parameters 'a', 'b' and 'c' will become " "keyword-only parameters in Python 3.14.", 1)) { - goto exit; + goto exit; } } args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 3, 3, 1, argsbuf); @@ -618,6 +767,19 @@ PyDoc_STRVAR(depr_star_pos1_len1_opt__doc__, static PyObject * depr_star_pos1_len1_opt_impl(PyObject *module, PyObject *a, PyObject *b); +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of 'depr_star_pos1_len1_opt'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of 'depr_star_pos1_len1_opt'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of 'depr_star_pos1_len1_opt'." +# endif +#endif static PyObject * depr_star_pos1_len1_opt(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -652,29 +814,13 @@ depr_star_pos1_len1_opt(PyObject *module, PyObject *const *args, Py_ssize_t narg PyObject *a; PyObject *b = Py_None; - // Emit compiler warnings when we get to Python 3.14. - #if PY_VERSION_HEX >= 0x030e00C0 - # error \ - "In _testclinic.c, update parameter(s) 'b' in the clinic input of" \ - " 'depr_star_pos1_len1_opt' to be keyword-only." - #elif PY_VERSION_HEX >= 0x030e00A0 - # ifdef _MSC_VER - # pragma message ( \ - "In _testclinic.c, update parameter(s) 'b' in the clinic input of" \ - " 'depr_star_pos1_len1_opt' to be keyword-only.") - # else - # warning \ - "In _testclinic.c, update parameter(s) 'b' in the clinic input of" \ - " 'depr_star_pos1_len1_opt' to be keyword-only." - # endif - #endif if (nargs == 2) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing 2 positional arguments to depr_star_pos1_len1_opt() is " "deprecated. Parameter 'b' will become a keyword-only parameter " "in Python 3.14.", 1)) { - goto exit; + goto exit; } } args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 2, 0, argsbuf); @@ -708,6 +854,19 @@ PyDoc_STRVAR(depr_star_pos1_len1__doc__, static PyObject * depr_star_pos1_len1_impl(PyObject *module, PyObject *a, PyObject *b); +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of 'depr_star_pos1_len1'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of 'depr_star_pos1_len1'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of 'depr_star_pos1_len1'." +# endif +#endif static PyObject * depr_star_pos1_len1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -741,29 +900,13 @@ depr_star_pos1_len1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P PyObject *a; PyObject *b; - // Emit compiler warnings when we get to Python 3.14. - #if PY_VERSION_HEX >= 0x030e00C0 - # error \ - "In _testclinic.c, update parameter(s) 'b' in the clinic input of" \ - " 'depr_star_pos1_len1' to be keyword-only." - #elif PY_VERSION_HEX >= 0x030e00A0 - # ifdef _MSC_VER - # pragma message ( \ - "In _testclinic.c, update parameter(s) 'b' in the clinic input of" \ - " 'depr_star_pos1_len1' to be keyword-only.") - # else - # warning \ - "In _testclinic.c, update parameter(s) 'b' in the clinic input of" \ - " 'depr_star_pos1_len1' to be keyword-only." - # endif - #endif if (nargs == 2) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing 2 positional arguments to depr_star_pos1_len1() is " "deprecated. Parameter 'b' will become a keyword-only parameter " "in Python 3.14.", 1)) { - goto exit; + goto exit; } } args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 2, 0, argsbuf); @@ -794,6 +937,22 @@ static PyObject * depr_star_pos1_len2_with_kwd_impl(PyObject *module, PyObject *a, PyObject *b, PyObject *c, PyObject *d); +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of " \ + "'depr_star_pos1_len2_with_kwd'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of " \ + "'depr_star_pos1_len2_with_kwd'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of " \ + "'depr_star_pos1_len2_with_kwd'." +# endif +#endif static PyObject * depr_star_pos1_len2_with_kwd(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -829,29 +988,13 @@ depr_star_pos1_len2_with_kwd(PyObject *module, PyObject *const *args, Py_ssize_t PyObject *c; PyObject *d; - // Emit compiler warnings when we get to Python 3.14. - #if PY_VERSION_HEX >= 0x030e00C0 - # error \ - "In _testclinic.c, update parameter(s) 'b' and 'c' in the clinic " \ - "input of 'depr_star_pos1_len2_with_kwd' to be keyword-only." - #elif PY_VERSION_HEX >= 0x030e00A0 - # ifdef _MSC_VER - # pragma message ( \ - "In _testclinic.c, update parameter(s) 'b' and 'c' in the clinic " \ - "input of 'depr_star_pos1_len2_with_kwd' to be keyword-only.") - # else - # warning \ - "In _testclinic.c, update parameter(s) 'b' and 'c' in the clinic " \ - "input of 'depr_star_pos1_len2_with_kwd' to be keyword-only." - # endif - #endif if (nargs > 1 && nargs <= 3) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing more than 1 positional argument to " "depr_star_pos1_len2_with_kwd() is deprecated. Parameters 'b' and" " 'c' will become keyword-only parameters in Python 3.14.", 1)) { - goto exit; + goto exit; } } args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 3, 3, 1, argsbuf); @@ -884,6 +1027,19 @@ static PyObject * depr_star_pos2_len1_impl(PyObject *module, PyObject *a, PyObject *b, PyObject *c); +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of 'depr_star_pos2_len1'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of 'depr_star_pos2_len1'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of 'depr_star_pos2_len1'." +# endif +#endif static PyObject * depr_star_pos2_len1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -918,29 +1074,13 @@ depr_star_pos2_len1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P PyObject *b; PyObject *c; - // Emit compiler warnings when we get to Python 3.14. - #if PY_VERSION_HEX >= 0x030e00C0 - # error \ - "In _testclinic.c, update parameter(s) 'c' in the clinic input of" \ - " 'depr_star_pos2_len1' to be keyword-only." - #elif PY_VERSION_HEX >= 0x030e00A0 - # ifdef _MSC_VER - # pragma message ( \ - "In _testclinic.c, update parameter(s) 'c' in the clinic input of" \ - " 'depr_star_pos2_len1' to be keyword-only.") - # else - # warning \ - "In _testclinic.c, update parameter(s) 'c' in the clinic input of" \ - " 'depr_star_pos2_len1' to be keyword-only." - # endif - #endif if (nargs == 3) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing 3 positional arguments to depr_star_pos2_len1() is " "deprecated. Parameter 'c' will become a keyword-only parameter " "in Python 3.14.", 1)) { - goto exit; + goto exit; } } args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 3, 3, 0, argsbuf); @@ -972,6 +1112,19 @@ static PyObject * depr_star_pos2_len2_impl(PyObject *module, PyObject *a, PyObject *b, PyObject *c, PyObject *d); +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of 'depr_star_pos2_len2'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of 'depr_star_pos2_len2'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of 'depr_star_pos2_len2'." +# endif +#endif static PyObject * depr_star_pos2_len2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -1007,29 +1160,13 @@ depr_star_pos2_len2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P PyObject *c; PyObject *d; - // Emit compiler warnings when we get to Python 3.14. - #if PY_VERSION_HEX >= 0x030e00C0 - # error \ - "In _testclinic.c, update parameter(s) 'c' and 'd' in the clinic " \ - "input of 'depr_star_pos2_len2' to be keyword-only." - #elif PY_VERSION_HEX >= 0x030e00A0 - # ifdef _MSC_VER - # pragma message ( \ - "In _testclinic.c, update parameter(s) 'c' and 'd' in the clinic " \ - "input of 'depr_star_pos2_len2' to be keyword-only.") - # else - # warning \ - "In _testclinic.c, update parameter(s) 'c' and 'd' in the clinic " \ - "input of 'depr_star_pos2_len2' to be keyword-only." - # endif - #endif if (nargs > 2 && nargs <= 4) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing more than 2 positional arguments to " "depr_star_pos2_len2() is deprecated. Parameters 'c' and 'd' will" " become keyword-only parameters in Python 3.14.", 1)) { - goto exit; + goto exit; } } args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 4, 4, 0, argsbuf); @@ -1062,6 +1199,22 @@ static PyObject * depr_star_pos2_len2_with_kwd_impl(PyObject *module, PyObject *a, PyObject *b, PyObject *c, PyObject *d, PyObject *e); +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of " \ + "'depr_star_pos2_len2_with_kwd'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of " \ + "'depr_star_pos2_len2_with_kwd'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of " \ + "'depr_star_pos2_len2_with_kwd'." +# endif +#endif static PyObject * depr_star_pos2_len2_with_kwd(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -1098,29 +1251,13 @@ depr_star_pos2_len2_with_kwd(PyObject *module, PyObject *const *args, Py_ssize_t PyObject *d; PyObject *e; - // Emit compiler warnings when we get to Python 3.14. - #if PY_VERSION_HEX >= 0x030e00C0 - # error \ - "In _testclinic.c, update parameter(s) 'c' and 'd' in the clinic " \ - "input of 'depr_star_pos2_len2_with_kwd' to be keyword-only." - #elif PY_VERSION_HEX >= 0x030e00A0 - # ifdef _MSC_VER - # pragma message ( \ - "In _testclinic.c, update parameter(s) 'c' and 'd' in the clinic " \ - "input of 'depr_star_pos2_len2_with_kwd' to be keyword-only.") - # else - # warning \ - "In _testclinic.c, update parameter(s) 'c' and 'd' in the clinic " \ - "input of 'depr_star_pos2_len2_with_kwd' to be keyword-only." - # endif - #endif if (nargs > 2 && nargs <= 4) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing more than 2 positional arguments to " "depr_star_pos2_len2_with_kwd() is deprecated. Parameters 'c' and" " 'd' will become keyword-only parameters in Python 3.14.", 1)) { - goto exit; + goto exit; } } args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 4, 4, 1, argsbuf); @@ -1137,4 +1274,616 @@ depr_star_pos2_len2_with_kwd(PyObject *module, PyObject *const *args, Py_ssize_t exit: return return_value; } -/*[clinic end generated code: output=7a16fee4d6742d54 input=a9049054013a1b77]*/ + +PyDoc_STRVAR(depr_kwd_required_1__doc__, +"depr_kwd_required_1($module, a, /, b)\n" +"--\n" +"\n"); + +#define DEPR_KWD_REQUIRED_1_METHODDEF \ + {"depr_kwd_required_1", _PyCFunction_CAST(depr_kwd_required_1), METH_FASTCALL|METH_KEYWORDS, depr_kwd_required_1__doc__}, + +static PyObject * +depr_kwd_required_1_impl(PyObject *module, PyObject *a, PyObject *b); + +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of 'depr_kwd_required_1'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of 'depr_kwd_required_1'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of 'depr_kwd_required_1'." +# endif +#endif +static PyObject * +depr_kwd_required_1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(b), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"", "b", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "depr_kwd_required_1", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; + PyObject *a; + PyObject *b; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 2, 0, argsbuf); + if (!args) { + goto exit; + } + if (nargs < 2) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Passing keyword argument 'b' to depr_kwd_required_1() is " + "deprecated. Corresponding parameter will become positional-only " + "in Python 3.14.", 1)) + { + goto exit; + } + } + a = args[0]; + b = args[1]; + return_value = depr_kwd_required_1_impl(module, a, b); + +exit: + return return_value; +} + +PyDoc_STRVAR(depr_kwd_required_2__doc__, +"depr_kwd_required_2($module, a, /, b, c)\n" +"--\n" +"\n"); + +#define DEPR_KWD_REQUIRED_2_METHODDEF \ + {"depr_kwd_required_2", _PyCFunction_CAST(depr_kwd_required_2), METH_FASTCALL|METH_KEYWORDS, depr_kwd_required_2__doc__}, + +static PyObject * +depr_kwd_required_2_impl(PyObject *module, PyObject *a, PyObject *b, + PyObject *c); + +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of 'depr_kwd_required_2'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of 'depr_kwd_required_2'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of 'depr_kwd_required_2'." +# endif +#endif +static PyObject * +depr_kwd_required_2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 2 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(b), &_Py_ID(c), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"", "b", "c", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "depr_kwd_required_2", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[3]; + PyObject *a; + PyObject *b; + PyObject *c; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 3, 3, 0, argsbuf); + if (!args) { + goto exit; + } + if (nargs < 3) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Passing keyword arguments 'b' and 'c' to depr_kwd_required_2() " + "is deprecated. Corresponding parameters will become " + "positional-only in Python 3.14.", 1)) + { + goto exit; + } + } + a = args[0]; + b = args[1]; + c = args[2]; + return_value = depr_kwd_required_2_impl(module, a, b, c); + +exit: + return return_value; +} + +PyDoc_STRVAR(depr_kwd_optional_1__doc__, +"depr_kwd_optional_1($module, a, /, b=None)\n" +"--\n" +"\n"); + +#define DEPR_KWD_OPTIONAL_1_METHODDEF \ + {"depr_kwd_optional_1", _PyCFunction_CAST(depr_kwd_optional_1), METH_FASTCALL|METH_KEYWORDS, depr_kwd_optional_1__doc__}, + +static PyObject * +depr_kwd_optional_1_impl(PyObject *module, PyObject *a, PyObject *b); + +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of 'depr_kwd_optional_1'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of 'depr_kwd_optional_1'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of 'depr_kwd_optional_1'." +# endif +#endif +static PyObject * +depr_kwd_optional_1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(b), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"", "b", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "depr_kwd_optional_1", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; + PyObject *a; + PyObject *b = Py_None; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 2, 0, argsbuf); + if (!args) { + goto exit; + } + if (nargs < 2 && kwnames && PySequence_Contains(kwnames, &_Py_ID(b))) { + if (PyErr_Occurred()) { // PySequence_Contains() above can fail + goto exit; + } + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Passing keyword argument 'b' to depr_kwd_optional_1() is " + "deprecated. Corresponding parameter will become positional-only " + "in Python 3.14.", 1)) + { + goto exit; + } + } + a = args[0]; + if (!noptargs) { + goto skip_optional_pos; + } + b = args[1]; +skip_optional_pos: + return_value = depr_kwd_optional_1_impl(module, a, b); + +exit: + return return_value; +} + +PyDoc_STRVAR(depr_kwd_optional_2__doc__, +"depr_kwd_optional_2($module, a, /, b=None, c=None)\n" +"--\n" +"\n"); + +#define DEPR_KWD_OPTIONAL_2_METHODDEF \ + {"depr_kwd_optional_2", _PyCFunction_CAST(depr_kwd_optional_2), METH_FASTCALL|METH_KEYWORDS, depr_kwd_optional_2__doc__}, + +static PyObject * +depr_kwd_optional_2_impl(PyObject *module, PyObject *a, PyObject *b, + PyObject *c); + +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of 'depr_kwd_optional_2'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of 'depr_kwd_optional_2'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of 'depr_kwd_optional_2'." +# endif +#endif +static PyObject * +depr_kwd_optional_2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 2 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(b), &_Py_ID(c), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"", "b", "c", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "depr_kwd_optional_2", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[3]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; + PyObject *a; + PyObject *b = Py_None; + PyObject *c = Py_None; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 3, 0, argsbuf); + if (!args) { + goto exit; + } + if ((nargs < 2 && kwnames && PySequence_Contains(kwnames, &_Py_ID(b))) || (nargs < 3 && kwnames && PySequence_Contains(kwnames, &_Py_ID(c)))) { + if (PyErr_Occurred()) { // PySequence_Contains() above can fail + goto exit; + } + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Passing keyword arguments 'b' and 'c' to depr_kwd_optional_2() " + "is deprecated. Corresponding parameters will become " + "positional-only in Python 3.14.", 1)) + { + goto exit; + } + } + a = args[0]; + if (!noptargs) { + goto skip_optional_pos; + } + if (args[1]) { + b = args[1]; + if (!--noptargs) { + goto skip_optional_pos; + } + } + c = args[2]; +skip_optional_pos: + return_value = depr_kwd_optional_2_impl(module, a, b, c); + +exit: + return return_value; +} + +PyDoc_STRVAR(depr_kwd_optional_3__doc__, +"depr_kwd_optional_3($module, /, a=None, b=None, c=None)\n" +"--\n" +"\n"); + +#define DEPR_KWD_OPTIONAL_3_METHODDEF \ + {"depr_kwd_optional_3", _PyCFunction_CAST(depr_kwd_optional_3), METH_FASTCALL|METH_KEYWORDS, depr_kwd_optional_3__doc__}, + +static PyObject * +depr_kwd_optional_3_impl(PyObject *module, PyObject *a, PyObject *b, + PyObject *c); + +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of 'depr_kwd_optional_3'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of 'depr_kwd_optional_3'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of 'depr_kwd_optional_3'." +# endif +#endif +static PyObject * +depr_kwd_optional_3(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 3 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(a), &_Py_ID(b), &_Py_ID(c), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"a", "b", "c", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "depr_kwd_optional_3", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[3]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0; + PyObject *a = Py_None; + PyObject *b = Py_None; + PyObject *c = Py_None; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 3, 0, argsbuf); + if (!args) { + goto exit; + } + if ((nargs < 1 && kwnames && PySequence_Contains(kwnames, &_Py_ID(a))) || (nargs < 2 && kwnames && PySequence_Contains(kwnames, &_Py_ID(b))) || (nargs < 3 && kwnames && PySequence_Contains(kwnames, &_Py_ID(c)))) { + if (PyErr_Occurred()) { // PySequence_Contains() above can fail + goto exit; + } + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Passing keyword arguments 'a', 'b' and 'c' to " + "depr_kwd_optional_3() is deprecated. Corresponding parameters " + "will become positional-only in Python 3.14.", 1)) + { + goto exit; + } + } + if (!noptargs) { + goto skip_optional_pos; + } + if (args[0]) { + a = args[0]; + if (!--noptargs) { + goto skip_optional_pos; + } + } + if (args[1]) { + b = args[1]; + if (!--noptargs) { + goto skip_optional_pos; + } + } + c = args[2]; +skip_optional_pos: + return_value = depr_kwd_optional_3_impl(module, a, b, c); + +exit: + return return_value; +} + +PyDoc_STRVAR(depr_kwd_required_optional__doc__, +"depr_kwd_required_optional($module, a, /, b, c=None)\n" +"--\n" +"\n"); + +#define DEPR_KWD_REQUIRED_OPTIONAL_METHODDEF \ + {"depr_kwd_required_optional", _PyCFunction_CAST(depr_kwd_required_optional), METH_FASTCALL|METH_KEYWORDS, depr_kwd_required_optional__doc__}, + +static PyObject * +depr_kwd_required_optional_impl(PyObject *module, PyObject *a, PyObject *b, + PyObject *c); + +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of " \ + "'depr_kwd_required_optional'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of " \ + "'depr_kwd_required_optional'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of " \ + "'depr_kwd_required_optional'." +# endif +#endif +static PyObject * +depr_kwd_required_optional(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 2 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(b), &_Py_ID(c), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"", "b", "c", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "depr_kwd_required_optional", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[3]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 2; + PyObject *a; + PyObject *b; + PyObject *c = Py_None; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 3, 0, argsbuf); + if (!args) { + goto exit; + } + if ((nargs < 2) || (nargs < 3 && kwnames && PySequence_Contains(kwnames, &_Py_ID(c)))) { + if (PyErr_Occurred()) { // PySequence_Contains() above can fail + goto exit; + } + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Passing keyword arguments 'b' and 'c' to " + "depr_kwd_required_optional() is deprecated. Corresponding " + "parameters will become positional-only in Python 3.14.", 1)) + { + goto exit; + } + } + a = args[0]; + b = args[1]; + if (!noptargs) { + goto skip_optional_pos; + } + c = args[2]; +skip_optional_pos: + return_value = depr_kwd_required_optional_impl(module, a, b, c); + +exit: + return return_value; +} + +PyDoc_STRVAR(depr_kwd_noinline__doc__, +"depr_kwd_noinline($module, a, /, b, c=None, d=\'\')\n" +"--\n" +"\n"); + +#define DEPR_KWD_NOINLINE_METHODDEF \ + {"depr_kwd_noinline", _PyCFunction_CAST(depr_kwd_noinline), METH_FASTCALL|METH_KEYWORDS, depr_kwd_noinline__doc__}, + +static PyObject * +depr_kwd_noinline_impl(PyObject *module, PyObject *a, PyObject *b, + PyObject *c, const char *d, Py_ssize_t d_length); + +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of 'depr_kwd_noinline'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of 'depr_kwd_noinline'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of 'depr_kwd_noinline'." +# endif +#endif +static PyObject * +depr_kwd_noinline(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 3 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(b), &_Py_ID(c), &_Py_ID(d), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"", "b", "c", "d", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .format = "OO|Os#:depr_kwd_noinline", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *a; + PyObject *b; + PyObject *c = Py_None; + const char *d = ""; + Py_ssize_t d_length; + + if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, + &a, &b, &c, &d, &d_length)) { + goto exit; + } + if ((nargs < 2) || (nargs < 3 && kwnames && PySequence_Contains(kwnames, &_Py_ID(c)))) { + if (PyErr_Occurred()) { // PySequence_Contains() above can fail + goto exit; + } + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Passing keyword arguments 'b' and 'c' to depr_kwd_noinline() is " + "deprecated. Corresponding parameters will become positional-only" + " in Python 3.14.", 1)) + { + goto exit; + } + } + return_value = depr_kwd_noinline_impl(module, a, b, c, d, d_length); + +exit: + return return_value; +} +/*[clinic end generated code: output=b68c191569031144 input=a9049054013a1b77]*/ diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 6ff2622d33b381..4de45e761a149a 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -849,25 +849,27 @@ class CLanguage(Language): #define {methoddef_name} #endif /* !defined({methoddef_name}) */ """) - DEPRECATED_POSITIONAL_PROTOTYPE: Final[str] = r""" + COMPILER_DEPRECATION_WARNING_PROTOTYPE: Final[str] = r""" // Emit compiler warnings when we get to Python {major}.{minor}. #if PY_VERSION_HEX >= 0x{major:02x}{minor:02x}00C0 # error \ - {cpp_message} + {message} #elif PY_VERSION_HEX >= 0x{major:02x}{minor:02x}00A0 # ifdef _MSC_VER # pragma message ( \ - {cpp_message}) + {message}) # else # warning \ - {cpp_message} + {message} # endif #endif - if ({condition}) {{{{ + """ + DEPRECATION_WARNING_PROTOTYPE: Final[str] = r""" + if ({condition}) {{{{{errcheck} if (PyErr_WarnEx(PyExc_DeprecationWarning, - {depr_message}, 1)) + {message}, 1)) {{{{ - goto exit; + goto exit; }}}} }}}} """ @@ -893,6 +895,34 @@ def render( function = o return self.render_function(clinic, function) + def compiler_deprecated_warning( + self, + func: Function, + parameters: list[Parameter], + ) -> str | None: + minversion: VersionTuple | None = None + for p in parameters: + for version in p.deprecated_positional, p.deprecated_keyword: + if version and (not minversion or minversion > version): + minversion = version + if not minversion: + return None + + # Format the preprocessor warning and error messages. + assert isinstance(self.cpp.filename, str) + source = os.path.basename(self.cpp.filename) + message = ( + f"In {source}, update the clinic input of {func.full_name!r}." + ) + code = self.COMPILER_DEPRECATION_WARNING_PROTOTYPE.format( + major=minversion[0], + minor=minversion[1], + message=wrapped_c_string_literal(message, suffix=" \\", + width=72, + subsequent_indent=16), + ) + return normalize_snippet(code) + def deprecate_positional_use( self, func: Function, @@ -910,54 +940,98 @@ def deprecate_positional_use( assert first_param.deprecated_positional == last_param.deprecated_positional thenceforth = first_param.deprecated_positional assert thenceforth is not None - - # Format the preprocessor warning and error messages. - assert isinstance(self.cpp.filename, str) - source = os.path.basename(self.cpp.filename) major, minor = thenceforth - cpp_message = ( - f"In {source}, update parameter(s) {pstr} in the clinic " - f"input of {func.full_name!r} to be keyword-only." - ) # Format the deprecation message. - if first_pos == 0: - preamble = "Passing positional arguments to " + amount = "" + what = "arguments" if len(params) == 1: condition = f"nargs == {first_pos+1}" if first_pos: - preamble = f"Passing {first_pos+1} positional arguments to " - depr_message = preamble + ( - f"{func.fulldisplayname}() is deprecated. Parameter {pstr} will " - f"become a keyword-only parameter in Python {major}.{minor}." - ) + amount = f"{first_pos+1} " + message = f"Parameter {pstr} will become a keyword-only parameter" else: condition = f"nargs > {first_pos} && nargs <= {last_pos+1}" if first_pos: - preamble = ( - f"Passing more than {first_pos} positional " - f"argument{'s' if first_pos != 1 else ''} to " - ) - depr_message = preamble + ( - f"{func.fulldisplayname}() is deprecated. Parameters {pstr} will " - f"become keyword-only parameters in Python {major}.{minor}." - ) - + amount = f"more than {first_pos} " + if first_pos == 1: + what = "argument" + message = f"Parameters {pstr} will become keyword-only parameters" + message = ( + f"Passing {amount}positional {what} to {func.fulldisplayname}() is deprecated. " + f"{message} in Python {major}.{minor}." + ) # Append deprecation warning to docstring. - lines = textwrap.wrap(f"Note: {depr_message}") - docstring = "\n".join(lines) + docstring = textwrap.fill(f"Note: {message}") func.docstring += f"\n\n{docstring}\n" + # Format and return the code block. + code = self.DEPRECATION_WARNING_PROTOTYPE.format( + condition=condition, + errcheck="", + message=wrapped_c_string_literal(message, width=64, + subsequent_indent=20), + ) + return normalize_snippet(code, indent=4) + + def deprecate_keyword_use( + self, + func: Function, + params: dict[int, Parameter], + ) -> str: + assert len(params) > 0 + names = [repr(p.name) for p in params.values()] + first_pos, first_param = next(iter(params.items())) + last_pos, last_param = next(reversed(params.items())) + + # Pretty-print list of names. + pstr = pprint_words(names) + # For now, assume there's only one deprecation level. + assert first_param.deprecated_keyword == last_param.deprecated_keyword + thenceforth = first_param.deprecated_keyword + assert thenceforth is not None + major, minor = thenceforth + + # Format the deprecation message. + containscheck = "" + conditions = [] + for i, p in params.items(): + if p.is_optional(): + #conditions.append(f"nargs < {i+1} && {argname_fmt % i}") + if func.kind.new_or_init: + conditions.append(f"nargs < {i+1} && kwargs && PyDict_Contains(kwargs, &_Py_ID({p.name}))") + containscheck = "PyDict_Contains" + else: + conditions.append(f"nargs < {i+1} && kwnames && PySequence_Contains(kwnames, &_Py_ID({p.name}))") + containscheck = "PySequence_Contains" + else: + conditions = [f"nargs < {i+1}"] + condition = ") || (".join(conditions) + if len(conditions) > 1: + condition = f"({condition})" + if len(params) == 1: + what1 = "argument" + what2 = "parameter" + else: + what1 = "arguments" + what2 = "parameters" + message = ( + f"Passing keyword {what1} {pstr} to {func.fulldisplayname}() is deprecated. " + f"Corresponding {what2} will become positional-only in Python {major}.{minor}." + ) + if containscheck: + errcheck = f""" + if (PyErr_Occurred()) {{{{ // {containscheck}() above can fail + goto exit; + }}}}""" + else: + errcheck = "" # Format and return the code block. - code = self.DEPRECATED_POSITIONAL_PROTOTYPE.format( + code = self.DEPRECATION_WARNING_PROTOTYPE.format( condition=condition, - major=major, - minor=minor, - cpp_message=wrapped_c_string_literal(cpp_message, suffix=" \\", - width=64, - subsequent_indent=16), - depr_message=wrapped_c_string_literal(depr_message, width=64, - subsequent_indent=20), + errcheck=errcheck, + message=wrapped_c_string_literal(message, width=64, + subsequent_indent=20), ) return normalize_snippet(code, indent=4) @@ -1258,6 +1332,18 @@ def parser_body( parser_definition = parser_body(parser_prototype, *parser_code) else: + deprecated_positionals: dict[int, Parameter] = {} + deprecated_keywords: dict[int, Parameter] = {} + for i, p in enumerate(parameters): + if p.deprecated_positional: + deprecated_positionals[i] = p + if p.deprecated_keyword: + deprecated_keywords[i] = p + deprecated: list[str] = [] + if deprecated_keywords: + code = self.deprecate_keyword_use(f, deprecated_keywords) + deprecated.append(code) + has_optional_kw = (max(pos_only, min_pos) + min_kw_only < len(converters) - int(vararg != NO_VARARG)) if vararg == NO_VARARG: args_declaration = "_PyArg_UnpackKeywords", "%s, %s, %s" % ( @@ -1310,7 +1396,8 @@ def parser_body( flags = 'METH_METHOD|' + flags parser_prototype = self.PARSER_PROTOTYPE_DEF_CLASS - deprecated_positionals: dict[int, Parameter] = {} + parser_code.extend(deprecated) + add_label: str | None = None for i, p in enumerate(parameters): if isinstance(p.converter, defining_class_converter): @@ -1325,8 +1412,6 @@ def parser_body( parser_code.append("%s:" % add_label) add_label = None if not p.is_optional(): - if p.deprecated_positional: - deprecated_positionals[i] = p parser_code.append(normalize_snippet(parsearg, indent=4)) elif i < pos_only: add_label = 'skip_optional_posonly' @@ -1356,8 +1441,6 @@ def parser_body( goto %s; }} """ % add_label, indent=4)) - if p.deprecated_positional: - deprecated_positionals[i] = p if i + 1 == len(parameters): parser_code.append(normalize_snippet(parsearg, indent=4)) else: @@ -1373,12 +1456,6 @@ def parser_body( }} """ % add_label, indent=4)) - if deprecated_positionals: - code = self.deprecate_positional_use(f, deprecated_positionals) - assert parser_code is not None - # Insert the deprecation code before parameter parsing. - parser_code.insert(0, code) - if parser_code is not None: if add_label: parser_code.append("%s:" % add_label) @@ -1398,6 +1475,13 @@ def parser_body( goto exit; }} """, indent=4)] + parser_code.extend(deprecated) + + if deprecated_positionals: + code = self.deprecate_positional_use(f, deprecated_positionals) + # Insert the deprecation code before parameter parsing. + parser_code.insert(0, code) + parser_definition = parser_body(parser_prototype, *parser_code, declarations=declarations) @@ -1478,6 +1562,10 @@ def parser_body( parser_definition = parser_definition.replace("{return_value_declaration}", return_value_declaration) + compiler_warning = self.compiler_deprecated_warning(f, parameters) + if compiler_warning: + parser_definition = compiler_warning + "\n" + parser_definition + d = { "docstring_prototype" : docstring_prototype, "docstring_definition" : docstring_definition, @@ -2751,6 +2839,7 @@ class Parameter: group: int = 0 # (`None` signifies that there is no deprecation) deprecated_positional: VersionTuple | None = None + deprecated_keyword: VersionTuple | None = None right_bracket_count: int = dc.field(init=False, default=0) def __repr__(self) -> str: @@ -4583,6 +4672,7 @@ class DSLParser: keyword_only: bool positional_only: bool deprecated_positional: VersionTuple | None + deprecated_keyword: VersionTuple | None group: int parameter_state: ParamState indent: IndentStack @@ -4590,11 +4680,7 @@ class DSLParser: coexist: bool parameter_continuation: str preserve_output: bool - star_from_version_re = create_regex( - before="* [from ", - after="]", - word=False, - ) + from_version_re = re.compile(r'([*/]) +\[from +(.+)\]') def __init__(self, clinic: Clinic) -> None: self.clinic = clinic @@ -4619,6 +4705,7 @@ def reset(self) -> None: self.keyword_only = False self.positional_only = False self.deprecated_positional = None + self.deprecated_keyword = None self.group = 0 self.parameter_state: ParamState = ParamState.START self.indent = IndentStack() @@ -5094,21 +5181,22 @@ def state_parameter(self, line: str) -> None: return line = line.lstrip() - match = self.star_from_version_re.match(line) + version: VersionTuple | None = None + match = self.from_version_re.fullmatch(line) if match: - self.parse_deprecated_positional(match.group(1)) - return + line = match[1] + version = self.parse_version(match[2]) func = self.function match line: case '*': - self.parse_star(func) + self.parse_star(func, version) case '[': self.parse_opening_square_bracket(func) case ']': self.parse_closing_square_bracket(func) case '/': - self.parse_slash(func) + self.parse_slash(func, version) case param: self.parse_parameter(param) @@ -5409,29 +5497,32 @@ def parse_converter( "Annotations must be either a name, a function call, or a string." ) - def parse_deprecated_positional(self, thenceforth: str) -> None: + def parse_version(self, thenceforth: str) -> VersionTuple: assert isinstance(self.function, Function) fname = self.function.full_name - if self.keyword_only: - fail(f"Function {fname!r}: '* [from ...]' must come before '*'") - if self.deprecated_positional: - fail(f"Function {fname!r} uses '[from ...]' more than once.") try: major, minor = thenceforth.split(".") - self.deprecated_positional = int(major), int(minor) + return int(major), int(minor) except ValueError: fail( - f"Function {fname!r}: expected format '* [from major.minor]' " + f"Function {fname!r}: expected format '[from major.minor]' " f"where 'major' and 'minor' are integers; got {thenceforth!r}" ) - def parse_star(self, function: Function) -> None: + def parse_star(self, function: Function, version: VersionTuple | None) -> None: """Parse keyword-only parameter marker '*'.""" - if self.keyword_only: - fail(f"Function {function.name!r} uses '*' more than once.") - self.deprecated_positional = None - self.keyword_only = True + if version is None: + if self.keyword_only: + fail(f"Function {function.name!r} uses '*' more than once.") + self.check_remaining_star() + self.keyword_only = True + else: + if self.keyword_only: + fail(f"Function {function.full_name!r}: '* [from ...]' must come before '*'") + if self.deprecated_positional: + fail(f"Function {function.full_name!r} uses '* [from ...]' more than once.") + self.deprecated_positional = version def parse_opening_square_bracket(self, function: Function) -> None: """Parse opening parameter group symbol '['.""" @@ -5465,11 +5556,30 @@ def parse_closing_square_bracket(self, function: Function) -> None: f"has an unsupported group configuration. " f"(Unexpected state {st}.c)") - def parse_slash(self, function: Function) -> None: + def parse_slash(self, function: Function, version: VersionTuple | None) -> None: """Parse positional-only parameter marker '/'.""" - if self.positional_only: - fail(f"Function {function.name!r} uses '/' more than once.") + if version is None: + if self.deprecated_keyword: + fail(f"Function {function.full_name!r}: '/ [from ...]' must come after '/'") + if self.positional_only: + fail(f"Function {function.name!r} uses '/' more than once.") + else: + if self.deprecated_keyword: + fail(f"Function {function.name!r} uses '/ [from ...]' more than once.") + self.positional_only = True + self.deprecated_keyword = version + if self.keyword_only or self.deprecated_positional: + fail(f"Function {function.name!r} mixes keyword-only and " + f"positional-only parameters, which is unsupported.") + if version is not None: + found = False + for p in reversed(function.parameters.values()): + found = p.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD + break + if not found: + fail(f"Function {function.name!r} specifies '/ [from ...]' " + f"without any parameters beforehead.") # REQUIRED and OPTIONAL are allowed here, that allows positional-only # without option groups to work (and have default values!) allowed = { @@ -5481,19 +5591,13 @@ def parse_slash(self, function: Function) -> None: if (self.parameter_state not in allowed) or self.group: fail(f"Function {function.name!r} has an unsupported group configuration. " f"(Unexpected state {self.parameter_state}.d)") - if self.keyword_only: - fail(f"Function {function.name!r} mixes keyword-only and " - "positional-only parameters, which is unsupported.") # fixup preceding parameters for p in function.parameters.values(): - if p.is_vararg(): - continue - if (p.kind is not inspect.Parameter.POSITIONAL_OR_KEYWORD and - not isinstance(p.converter, self_converter) - ): - fail(f"Function {function.name!r} mixes keyword-only and " - "positional-only parameters, which is unsupported.") - p.kind = inspect.Parameter.POSITIONAL_ONLY + if p.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD: + if version is None: + p.kind = inspect.Parameter.POSITIONAL_ONLY + else: + p.deprecated_keyword = version def state_parameter_docstring_start(self, line: str) -> None: assert self.indent.margin is not None, "self.margin.infer() has not yet been called to set the margin" @@ -5778,6 +5882,30 @@ def format_docstring(self) -> str: signature=signature, parameters=parameters).rstrip() + def check_remaining_star(self, lineno: int | None = None) -> None: + assert isinstance(self.function, Function) + + if self.keyword_only: + symbol = '*' + elif self.deprecated_positional: + symbol = '* [from ...]' + else: + return + + no_param_after_symbol = True + for p in reversed(self.function.parameters.values()): + if self.keyword_only: + if p.kind == inspect.Parameter.KEYWORD_ONLY: + return + elif self.deprecated_positional: + if p.deprecated_positional == self.deprecated_positional: + return + break + + fname = self.function.full_name + fail(f"Function {fname!r} specifies {symbol!r} " + "without any parameters afterwards.", line_number=lineno) + def do_post_block_processing_cleanup(self, lineno: int) -> None: """ Called when processing the block is done. @@ -5785,28 +5913,7 @@ def do_post_block_processing_cleanup(self, lineno: int) -> None: if not self.function: return - def check_remaining( - symbol: str, - condition: Callable[[Parameter], bool] - ) -> None: - assert isinstance(self.function, Function) - - if values := self.function.parameters.values(): - last_param = next(reversed(values)) - no_param_after_symbol = condition(last_param) - else: - no_param_after_symbol = True - if no_param_after_symbol: - fname = self.function.full_name - fail(f"Function {fname!r} specifies {symbol!r} " - "without any parameters afterwards.", line_number=lineno) - - if self.keyword_only: - check_remaining("*", lambda p: p.kind != inspect.Parameter.KEYWORD_ONLY) - - if self.deprecated_positional: - check_remaining("* [from ...]", lambda p: not p.deprecated_positional) - + self.check_remaining_star(lineno) self.function.docstring = self.format_docstring() From b4b9f3ca60fd798761c7d2c48810aca51d890780 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 15 Aug 2023 21:22:55 +0300 Subject: [PATCH 02/13] Silence check-c-globals.py. --- Tools/c-analyzer/cpython/globals-to-fix.tsv | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv b/Tools/c-analyzer/cpython/globals-to-fix.tsv index 28bd2a4430d14e..9cf6cc64c160ee 100644 --- a/Tools/c-analyzer/cpython/globals-to-fix.tsv +++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv @@ -324,6 +324,8 @@ Modules/_testcapi/vectorcall.c - MethodDescriptorNopGet_Type - Modules/_testcapi/vectorcall.c - MethodDescriptor2_Type - Modules/_testclinic.c - DeprStarInit - Modules/_testclinic.c - DeprStarNew - +Modules/_testclinic.c - DeprKwdInit - +Modules/_testclinic.c - DeprKwdNew - ################################## From d5e00645bccff04e7e00774eaf12c78e78aaf8e6 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 15 Aug 2023 22:04:19 +0300 Subject: [PATCH 03/13] Update the documentation. --- Doc/howto/clinic.rst | 65 ++++++++++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 21 deletions(-) diff --git a/Doc/howto/clinic.rst b/Doc/howto/clinic.rst index 463a938fafa8dc..59bab318532a58 100644 --- a/Doc/howto/clinic.rst +++ b/Doc/howto/clinic.rst @@ -1941,54 +1941,72 @@ The generated docstring ends up looking like this: .. _clinic-howto-deprecate-positional: +.. _clinic-howto-deprecate-keyword: -How to deprecate passing parameters positionally ------------------------------------------------- +How to deprecate passing parameters positionally or by keyword +-------------------------------------------------------------- Argument Clinic provides syntax that makes it possible to generate code that -deprecates passing :term:`arguments ` positionally. +deprecates passing :term:`arguments ` for positional-or-keyword +:term:`parameters ` positionally or by keyword. For example, say we've got a module-level function :py:func:`!foo.myfunc` -that has three :term:`parameters `: -positional-or-keyword parameters *a* and *b*, and a keyword-only parameter *c*:: +that has five parameters: a positional-only parameter *a*, three +positional-or-keyword parameters *b*, *c* and *d*, and a keyword-only +parameter *e*:: /*[clinic input] module foo myfunc a: int + / b: int - * c: int + d: int + * + e: int [clinic start generated output]*/ -We now want to make the *b* parameter keyword-only; +We now want to make the *b* parameter positional-only and the *d* parameter +keyword-only; however, we'll have to wait two releases before making this change, as mandated by Python's backwards-compatibility policy (see :pep:`387`). For this example, imagine we're in the development phase for Python 3.12: that means we'll be allowed to introduce deprecation warnings in Python 3.12 -whenever the *b* parameter is passed positionally, -and we'll be allowed to make it keyword-only in Python 3.14 at the earliest. +whenever an argument for the *b* parameter is passed by keyword or an argument +for the *d* parameter is passed positionally, and we'll be allowed to make +them positional-only and keyword-only correspondingly in Python 3.14 at +the earliest. We can use Argument Clinic to emit the desired deprecation warnings -using the ``* [from ...]`` syntax, -by adding the line ``* [from 3.14]`` right above the *b* parameter:: +using the ``[from ...]`` syntax, by adding the line ``/ [from 3.14]`` right +below the *b* parameter and adding the line ``/ [from 3.14]`` right above +the *d* parameter:: /*[clinic input] module foo myfunc a: int - * [from 3.14] + / b: int - * + / [from 3.14] c: int + * [from 3.14] + d: int + * + e: int [clinic start generated output]*/ Next, regenerate Argument Clinic code (``make clinic``), and add unit tests for the new behaviour. The generated code will now emit a :exc:`DeprecationWarning` -when an :term:`argument` for the :term:`parameter` *b* is passed positionally. +when an :term:`argument` for the :term:`parameter` *d* is passed positionally +(e.g ``myfunc(1, 2, 3, 4, e=5)``) or an argument for the parameter *b* is +passed by keyword (e.g ``myfunc(1, b=2, c=3, d=4, e=5)``). +Although ``myfunc(1, 2, 3, d=4, e=5)`` and ``myfunc(1, 2, c=3, d=4, e=5)`` +will pass without warning. C preprocessor directives are also generated for emitting -compiler warnings if the ``* [from ...]`` line has not been removed +compiler warnings if the ``[from ...]`` lines have not been removed from the Argument Clinic input when the deprecation period is over, which means when the alpha phase of the specified Python version kicks in. @@ -2001,21 +2019,26 @@ Luckily for us, compiler warnings are now generated: .. code-block:: none In file included from Modules/foomodule.c:139: - Modules/clinic/foomodule.c.h:139:8: warning: In 'foomodule.c', update parameter(s) 'a' and 'b' in the clinic input of 'mymod.myfunc' to be keyword-only. [-W#warnings] - # warning "In 'foomodule.c', update parameter(s) 'a' and 'b' in the clinic input of 'mymod.myfunc' to be keyword-only. [-W#warnings]" + Modules/clinic/foomodule.c.h:139:8: warning: In 'foomodule.c', update the clinic input of 'mymod.myfunc'. [-W#warnings] + # warning "In 'foomodule.c', update the clinic input of 'mymod.myfunc'. [-W#warnings]" ^ -We now close the deprecation phase by making *b* keyword-only; -replace the ``* [from ...]`` line above *b* -with the ``*`` from the line above *c*:: +We now close the deprecation phase by making *a* positional-only and *c* +keyword-only; +replace the ``/ [from ...]`` line below *b* with the ``/`` from the line +below *a* and the ``* [from ...]`` line above *d* with the ``*`` from +the line above *e*:: /*[clinic input] module foo myfunc a: int - * b: int + / c: int + * + d: int + e: int [clinic start generated output]*/ Finally, run ``make clinic`` to regenerate the Argument Clinic code, From b1300c73e79ee5e676d5f91d6639a19eb44f063a Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 15 Aug 2023 22:33:16 +0300 Subject: [PATCH 04/13] Make constructor's arguments optional, it produces more interesting output. --- Lib/test/test_clinic.py | 16 ++--- Modules/_testclinic.c | 16 ++--- Modules/clinic/_testclinic_depr_star.c.h | 78 +++++++++++++++++------- 3 files changed, 73 insertions(+), 37 deletions(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 375a10e404b107..a84a7bdf513eb5 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -3171,39 +3171,39 @@ def test_cloned_func_with_converter_exception_message(self): def test_depr_star_new(self): cls = ac_tester.DeprStarNew + cls() cls(a=None) self.check_depr_star("'a'", cls, None) - self.assertRaises(TypeError, cls) def test_depr_star_new_cloned(self): - fn = ac_tester.DeprStarNew(a=None).cloned + fn = ac_tester.DeprStarNew().cloned + fn() fn(a=None) self.check_depr_star("'a'", fn, None, name='_testclinic.DeprStarNew.cloned') - self.assertRaises(TypeError, fn) def test_depr_star_init(self): cls = ac_tester.DeprStarInit + cls() cls(a=None) self.check_depr_star("'a'", cls, None) - self.assertRaises(TypeError, cls) def test_depr_star_init_cloned(self): - fn = ac_tester.DeprStarInit(a=None).cloned + fn = ac_tester.DeprStarInit().cloned + fn() fn(a=None) self.check_depr_star("'a'", fn, None, name='_testclinic.DeprStarInit.cloned') - self.assertRaises(TypeError, fn) def test_depr_kwd_new(self): cls = ac_tester.DeprKwdNew + cls() cls(None) self.check_depr_kwd("'a'", cls, a=None) - self.assertRaises(TypeError, cls) def test_depr_kwd_init(self): cls = ac_tester.DeprKwdInit + cls() cls(None) self.check_depr_kwd("'a'", cls, a=None) - self.assertRaises(TypeError, cls) def test_depr_star_pos0_len1(self): fn = ac_tester.depr_star_pos0_len1 diff --git a/Modules/_testclinic.c b/Modules/_testclinic.c index 426bbec2a63d50..b35d840653ecb5 100644 --- a/Modules/_testclinic.c +++ b/Modules/_testclinic.c @@ -1219,13 +1219,13 @@ class _testclinic.DeprStarNew "PyObject *" "PyObject" @classmethod _testclinic.DeprStarNew.__new__ as depr_star_new * [from 3.14] - a: object + a: object = None The deprecation message should use the class name instead of __new__. [clinic start generated code]*/ static PyObject * depr_star_new_impl(PyTypeObject *type, PyObject *a) -/*[clinic end generated code: output=bdbb36244f90cf46 input=f4ae7dafbc23c378]*/ +/*[clinic end generated code: output=bdbb36244f90cf46 input=fdd640db964b4dc1]*/ { return type->tp_alloc(type, 0); } @@ -1260,13 +1260,13 @@ static PyTypeObject DeprStarNew = { class _testclinic.DeprStarInit "PyObject *" "PyObject" _testclinic.DeprStarInit.__init__ as depr_star_init * [from 3.14] - a: object + a: object = None The deprecation message should use the class name instead of __init__. [clinic start generated code]*/ static int depr_star_init_impl(PyObject *self, PyObject *a) -/*[clinic end generated code: output=8d27b43c286d3ecc input=659ebc748d87fa86]*/ +/*[clinic end generated code: output=8d27b43c286d3ecc input=5575b77229d5e2be]*/ { return 0; } @@ -1302,14 +1302,14 @@ static PyTypeObject DeprStarInit = { class _testclinic.DeprKwdNew "PyObject *" "PyObject" @classmethod _testclinic.DeprKwdNew.__new__ as depr_kwd_new - a: object + a: object = None / [from 3.14] The deprecation message should use the class name instead of __new__. [clinic start generated code]*/ static PyObject * depr_kwd_new_impl(PyTypeObject *type, PyObject *a) -/*[clinic end generated code: output=618d07afc5616149 input=831cfc26b33703a6]*/ +/*[clinic end generated code: output=618d07afc5616149 input=6c7d13c471013c10]*/ { return type->tp_alloc(type, 0); } @@ -1326,14 +1326,14 @@ static PyTypeObject DeprKwdNew = { /*[clinic input] class _testclinic.DeprKwdInit "PyObject *" "PyObject" _testclinic.DeprKwdInit.__init__ as depr_kwd_init - a: object + a: object = None / [from 3.14] The deprecation message should use the class name instead of __init__. [clinic start generated code]*/ static int depr_kwd_init_impl(PyObject *self, PyObject *a) -/*[clinic end generated code: output=6e02eb724a85d840 input=2e85cfe2845593f6]*/ +/*[clinic end generated code: output=6e02eb724a85d840 input=b9bf3c20f012d539]*/ { return 0; } diff --git a/Modules/clinic/_testclinic_depr_star.c.h b/Modules/clinic/_testclinic_depr_star.c.h index 0017975c84f144..e96aa8cbaf09c1 100644 --- a/Modules/clinic/_testclinic_depr_star.c.h +++ b/Modules/clinic/_testclinic_depr_star.c.h @@ -9,7 +9,7 @@ preserve PyDoc_STRVAR(depr_star_new__doc__, -"DeprStarNew(a)\n" +"DeprStarNew(a=None)\n" "--\n" "\n" "The deprecation message should use the class name instead of __new__.\n" @@ -70,7 +70,8 @@ depr_star_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) PyObject *argsbuf[1]; PyObject * const *fastargs; Py_ssize_t nargs = PyTuple_GET_SIZE(args); - PyObject *a; + Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 0; + PyObject *a = Py_None; if (nargs == 1) { if (PyErr_WarnEx(PyExc_DeprecationWarning, @@ -81,11 +82,15 @@ depr_star_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) goto exit; } } - fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 1, 1, 0, argsbuf); + fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 0, 1, 0, argsbuf); if (!fastargs) { goto exit; } + if (!noptargs) { + goto skip_optional_pos; + } a = fastargs[0]; +skip_optional_pos: return_value = depr_star_new_impl(type, a); exit: @@ -93,7 +98,7 @@ depr_star_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) } PyDoc_STRVAR(depr_star_new_clone__doc__, -"cloned($self, /, a)\n" +"cloned($self, /, a=None)\n" "--\n" "\n" "Note: Passing positional arguments to _testclinic.DeprStarNew.cloned()\n" @@ -153,7 +158,8 @@ depr_star_new_clone(PyObject *type, PyObject *const *args, Py_ssize_t nargs, PyO }; #undef KWTUPLE PyObject *argsbuf[1]; - PyObject *a; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0; + PyObject *a = Py_None; if (nargs == 1) { if (PyErr_WarnEx(PyExc_DeprecationWarning, @@ -164,11 +170,15 @@ depr_star_new_clone(PyObject *type, PyObject *const *args, Py_ssize_t nargs, PyO goto exit; } } - args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 1, 0, argsbuf); if (!args) { goto exit; } + if (!noptargs) { + goto skip_optional_pos; + } a = args[0]; +skip_optional_pos: return_value = depr_star_new_clone_impl(type, a); exit: @@ -176,7 +186,7 @@ depr_star_new_clone(PyObject *type, PyObject *const *args, Py_ssize_t nargs, PyO } PyDoc_STRVAR(depr_star_init__doc__, -"DeprStarInit(a)\n" +"DeprStarInit(a=None)\n" "--\n" "\n" "The deprecation message should use the class name instead of __init__.\n" @@ -237,7 +247,8 @@ depr_star_init(PyObject *self, PyObject *args, PyObject *kwargs) PyObject *argsbuf[1]; PyObject * const *fastargs; Py_ssize_t nargs = PyTuple_GET_SIZE(args); - PyObject *a; + Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 0; + PyObject *a = Py_None; if (nargs == 1) { if (PyErr_WarnEx(PyExc_DeprecationWarning, @@ -248,11 +259,15 @@ depr_star_init(PyObject *self, PyObject *args, PyObject *kwargs) goto exit; } } - fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 1, 1, 0, argsbuf); + fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 0, 1, 0, argsbuf); if (!fastargs) { goto exit; } + if (!noptargs) { + goto skip_optional_pos; + } a = fastargs[0]; +skip_optional_pos: return_value = depr_star_init_impl(self, a); exit: @@ -260,7 +275,7 @@ depr_star_init(PyObject *self, PyObject *args, PyObject *kwargs) } PyDoc_STRVAR(depr_star_init_clone__doc__, -"cloned($self, /, a)\n" +"cloned($self, /, a=None)\n" "--\n" "\n" "Note: Passing positional arguments to\n" @@ -320,7 +335,8 @@ depr_star_init_clone(PyObject *self, PyObject *const *args, Py_ssize_t nargs, Py }; #undef KWTUPLE PyObject *argsbuf[1]; - PyObject *a; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0; + PyObject *a = Py_None; if (nargs == 1) { if (PyErr_WarnEx(PyExc_DeprecationWarning, @@ -331,11 +347,15 @@ depr_star_init_clone(PyObject *self, PyObject *const *args, Py_ssize_t nargs, Py goto exit; } } - args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 1, 0, argsbuf); if (!args) { goto exit; } + if (!noptargs) { + goto skip_optional_pos; + } a = args[0]; +skip_optional_pos: return_value = depr_star_init_clone_impl(self, a); exit: @@ -343,7 +363,7 @@ depr_star_init_clone(PyObject *self, PyObject *const *args, Py_ssize_t nargs, Py } PyDoc_STRVAR(depr_kwd_new__doc__, -"DeprKwdNew(a)\n" +"DeprKwdNew(a=None)\n" "--\n" "\n" "The deprecation message should use the class name instead of __new__."); @@ -399,13 +419,17 @@ depr_kwd_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) PyObject *argsbuf[1]; PyObject * const *fastargs; Py_ssize_t nargs = PyTuple_GET_SIZE(args); - PyObject *a; + Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 0; + PyObject *a = Py_None; - fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 1, 1, 0, argsbuf); + fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 0, 1, 0, argsbuf); if (!fastargs) { goto exit; } - if (nargs < 1) { + if (nargs < 1 && kwargs && PyDict_Contains(kwargs, &_Py_ID(a))) { + if (PyErr_Occurred()) { // PyDict_Contains() above can fail + goto exit; + } if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing keyword argument 'a' to _testclinic.DeprKwdNew() is " "deprecated. Corresponding parameter will become positional-only " @@ -414,7 +438,11 @@ depr_kwd_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) goto exit; } } + if (!noptargs) { + goto skip_optional_pos; + } a = fastargs[0]; +skip_optional_pos: return_value = depr_kwd_new_impl(type, a); exit: @@ -422,7 +450,7 @@ depr_kwd_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) } PyDoc_STRVAR(depr_kwd_init__doc__, -"DeprKwdInit(a)\n" +"DeprKwdInit(a=None)\n" "--\n" "\n" "The deprecation message should use the class name instead of __init__."); @@ -478,13 +506,17 @@ depr_kwd_init(PyObject *self, PyObject *args, PyObject *kwargs) PyObject *argsbuf[1]; PyObject * const *fastargs; Py_ssize_t nargs = PyTuple_GET_SIZE(args); - PyObject *a; + Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 0; + PyObject *a = Py_None; - fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 1, 1, 0, argsbuf); + fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 0, 1, 0, argsbuf); if (!fastargs) { goto exit; } - if (nargs < 1) { + if (nargs < 1 && kwargs && PyDict_Contains(kwargs, &_Py_ID(a))) { + if (PyErr_Occurred()) { // PyDict_Contains() above can fail + goto exit; + } if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing keyword argument 'a' to _testclinic.DeprKwdInit() is " "deprecated. Corresponding parameter will become positional-only " @@ -493,7 +525,11 @@ depr_kwd_init(PyObject *self, PyObject *args, PyObject *kwargs) goto exit; } } + if (!noptargs) { + goto skip_optional_pos; + } a = fastargs[0]; +skip_optional_pos: return_value = depr_kwd_init_impl(self, a); exit: @@ -1886,4 +1922,4 @@ depr_kwd_noinline(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyO exit: return return_value; } -/*[clinic end generated code: output=b68c191569031144 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=7df75e794cccde22 input=a9049054013a1b77]*/ From ad36c157bbdd8c530db7be850564668d6f960a46 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 16 Aug 2023 14:52:50 +0300 Subject: [PATCH 05/13] Address review comments. --- Doc/howto/clinic.rst | 2 - Lib/test/test_clinic.py | 74 +++--- Modules/_sqlite/clinic/connection.c.h | 15 +- Modules/clinic/_testclinic_depr_star.c.h | 308 ++++++++++------------- Tools/clinic/clinic.py | 73 +++--- 5 files changed, 217 insertions(+), 255 deletions(-) diff --git a/Doc/howto/clinic.rst b/Doc/howto/clinic.rst index 59bab318532a58..7d5b84a5882ea7 100644 --- a/Doc/howto/clinic.rst +++ b/Doc/howto/clinic.rst @@ -2003,8 +2003,6 @@ The generated code will now emit a :exc:`DeprecationWarning` when an :term:`argument` for the :term:`parameter` *d* is passed positionally (e.g ``myfunc(1, 2, 3, 4, e=5)``) or an argument for the parameter *b* is passed by keyword (e.g ``myfunc(1, b=2, c=3, d=4, e=5)``). -Although ``myfunc(1, 2, 3, d=4, e=5)`` and ``myfunc(1, 2, c=3, d=4, e=5)`` -will pass without warning. C preprocessor directives are also generated for emitting compiler warnings if the ``[from ...]`` lines have not been removed from the Argument Clinic input when the deprecation period is over, diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index a84a7bdf513eb5..c47089369bb26c 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -1559,7 +1559,7 @@ def test_parameters_required_after_star(self): "module foo\nfoo.bar\n this: int\n *", "module foo\nfoo.bar\n this: int\n *\nDocstring.", ) - err = "Function 'foo.bar' specifies '*' without any parameters afterwards." + err = "Function 'foo.bar' specifies '*' without following parameters." for block in dataset: with self.subTest(block=block): self.expect_failure(block, err) @@ -1670,7 +1670,7 @@ def test_parameters_required_after_depr_star(self): """ err = ( "Function 'foo.bar' specifies '* [from ...]' without " - "any parameters afterwards" + "following parameters." ) self.expect_failure(block, err, lineno=4) @@ -1686,7 +1686,7 @@ def test_parameters_required_after_depr_star2(self): """ err = ( "Function 'foo.bar' specifies '* [from ...]' without " - "any parameters afterwards" + "following parameters." ) self.expect_failure(block, err, lineno=4) @@ -1716,6 +1716,20 @@ def test_depr_star_duplicate(self): err = "Function 'foo.bar' uses '* [from ...]' more than once." self.expect_failure(block, err, lineno=5) + def test_depr_star_duplicate2(self): + block = """ + module foo + foo.bar + a: int + * [from 3.14] + b: int + * [from 3.15] + c: int + Docstring. + """ + err = "Function 'foo.bar' uses '* [from ...]' more than once." + self.expect_failure(block, err, lineno=5) + def test_depr_slash_duplicate(self): block = """ module foo @@ -1730,6 +1744,20 @@ def test_depr_slash_duplicate(self): err = "Function 'bar' uses '/ [from ...]' 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] + c: int + Docstring. + """ + err = "Function 'bar' uses '/ [from ...]' more than once." + self.expect_failure(block, err, lineno=5) + def test_single_slash(self): block = """ module foo @@ -1752,25 +1780,11 @@ def test_parameters_required_before_depr_slash(self): """ err = ( "Function 'bar' specifies '/ [from ...]' without " - "any parameters beforehead." + "preceding parameters." ) self.expect_failure(block, err, lineno=2) def test_parameters_required_before_depr_slash2(self): - block = """ - module foo - foo.bar - / - / [from 3.14] - Docstring. - """ - err = ( - "Function 'bar' has an unsupported group configuration. " - "(Unexpected state 0.d)" - ) - self.expect_failure(block, err, lineno=2) - - def test_parameters_required_before_depr_slash3(self): block = """ module foo foo.bar @@ -1781,7 +1795,7 @@ def test_parameters_required_before_depr_slash3(self): """ err = ( "Function 'bar' specifies '/ [from ...]' without " - "any parameters beforehead." + "preceding parameters." ) self.expect_failure(block, err, lineno=4) @@ -1807,10 +1821,7 @@ def test_mix_star_and_slash(self): z: int / """ - err = ( - "Function 'bar' mixes keyword-only and positional-only parameters, " - "which is unsupported." - ) + err = "Function 'bar': '/' must precede '*'" self.expect_failure(block, err) def test_depr_star_must_come_after_slash(self): @@ -1823,10 +1834,7 @@ def test_depr_star_must_come_after_slash(self): b: int Docstring. """ - err = ( - "Function 'bar' mixes keyword-only and positional-only parameters, " - "which is unsupported." - ) + err = "Function 'bar': '/' must precede '* [from ...]'" self.expect_failure(block, err, lineno=4) def test_depr_star_must_come_after_depr_slash(self): @@ -1839,10 +1847,7 @@ def test_depr_star_must_come_after_depr_slash(self): b: int Docstring. """ - err = ( - "Function 'bar' mixes keyword-only and positional-only parameters, " - "which is unsupported." - ) + err = "Function 'bar': '/ [from ...]' must precede '* [from ...]'" self.expect_failure(block, err, lineno=4) def test_star_must_come_after_depr_slash(self): @@ -1855,10 +1860,7 @@ def test_star_must_come_after_depr_slash(self): b: int Docstring. """ - err = ( - "Function 'bar' mixes keyword-only and positional-only parameters, " - "which is unsupported." - ) + err = "Function 'bar': '/ [from ...]' must precede '*'" self.expect_failure(block, err, lineno=4) def test_depr_slash_must_come_after_slash(self): @@ -1871,7 +1873,7 @@ def test_depr_slash_must_come_after_slash(self): b: int Docstring. """ - err = "Function 'foo.bar': '/ [from ...]' must come after '/'" + err = "Function 'bar': '/' must precede '/ [from ...]'" self.expect_failure(block, err, lineno=4) def test_parameters_not_permitted_after_slash_for_now(self): diff --git a/Modules/_sqlite/clinic/connection.c.h b/Modules/_sqlite/clinic/connection.c.h index b744ededd03d72..fe2196d0f7ee54 100644 --- a/Modules/_sqlite/clinic/connection.c.h +++ b/Modules/_sqlite/clinic/connection.c.h @@ -18,20 +18,15 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database, // Emit compiler warnings when we get to Python 3.15. #if PY_VERSION_HEX >= 0x030f00C0 -# error \ - "In connection.c, update the clinic input of " \ - "'_sqlite3.Connection.__init__'." +# error "Update the clinic input of '_sqlite3.Connection.__init__'." #elif PY_VERSION_HEX >= 0x030f00A0 # ifdef _MSC_VER -# pragma message ( \ - "In connection.c, update the clinic input of " \ - "'_sqlite3.Connection.__init__'.") +# pragma message ("Update the clinic input of '_sqlite3.Connection.__init__'.") # else -# warning \ - "In connection.c, update the clinic input of " \ - "'_sqlite3.Connection.__init__'." +# warning "Update the clinic input of '_sqlite3.Connection.__init__'." # endif #endif + static int pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs) { @@ -1686,4 +1681,4 @@ getconfig(pysqlite_Connection *self, PyObject *arg) #ifndef DESERIALIZE_METHODDEF #define DESERIALIZE_METHODDEF #endif /* !defined(DESERIALIZE_METHODDEF) */ -/*[clinic end generated code: output=08b3a3d33755994a input=a9049054013a1b77]*/ +/*[clinic end generated code: output=0ad9d55977a51b8f input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_testclinic_depr_star.c.h b/Modules/clinic/_testclinic_depr_star.c.h index e96aa8cbaf09c1..8954e161266672 100644 --- a/Modules/clinic/_testclinic_depr_star.c.h +++ b/Modules/clinic/_testclinic_depr_star.c.h @@ -24,20 +24,15 @@ depr_star_new_impl(PyTypeObject *type, PyObject *a); // Emit compiler warnings when we get to Python 3.14. #if PY_VERSION_HEX >= 0x030e00C0 -# error \ - "In _testclinic.c, update the clinic input of " \ - "'_testclinic.DeprStarNew.__new__'." +# error "Update the clinic input of '_testclinic.DeprStarNew.__new__'." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER -# pragma message ( \ - "In _testclinic.c, update the clinic input of " \ - "'_testclinic.DeprStarNew.__new__'.") +# pragma message ("Update the clinic input of '_testclinic.DeprStarNew.__new__'.") # else -# warning \ - "In _testclinic.c, update the clinic input of " \ - "'_testclinic.DeprStarNew.__new__'." +# warning "Update the clinic input of '_testclinic.DeprStarNew.__new__'." # endif #endif + static PyObject * depr_star_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) { @@ -114,20 +109,15 @@ depr_star_new_clone_impl(PyObject *type, PyObject *a); // Emit compiler warnings when we get to Python 3.14. #if PY_VERSION_HEX >= 0x030e00C0 -# error \ - "In _testclinic.c, update the clinic input of " \ - "'_testclinic.DeprStarNew.cloned'." +# error "Update the clinic input of '_testclinic.DeprStarNew.cloned'." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER -# pragma message ( \ - "In _testclinic.c, update the clinic input of " \ - "'_testclinic.DeprStarNew.cloned'.") +# pragma message ("Update the clinic input of '_testclinic.DeprStarNew.cloned'.") # else -# warning \ - "In _testclinic.c, update the clinic input of " \ - "'_testclinic.DeprStarNew.cloned'." +# warning "Update the clinic input of '_testclinic.DeprStarNew.cloned'." # endif #endif + static PyObject * depr_star_new_clone(PyObject *type, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -201,20 +191,15 @@ depr_star_init_impl(PyObject *self, PyObject *a); // Emit compiler warnings when we get to Python 3.14. #if PY_VERSION_HEX >= 0x030e00C0 -# error \ - "In _testclinic.c, update the clinic input of " \ - "'_testclinic.DeprStarInit.__init__'." +# error "Update the clinic input of '_testclinic.DeprStarInit.__init__'." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER -# pragma message ( \ - "In _testclinic.c, update the clinic input of " \ - "'_testclinic.DeprStarInit.__init__'.") +# pragma message ("Update the clinic input of '_testclinic.DeprStarInit.__init__'.") # else -# warning \ - "In _testclinic.c, update the clinic input of " \ - "'_testclinic.DeprStarInit.__init__'." +# warning "Update the clinic input of '_testclinic.DeprStarInit.__init__'." # endif #endif + static int depr_star_init(PyObject *self, PyObject *args, PyObject *kwargs) { @@ -291,20 +276,15 @@ depr_star_init_clone_impl(PyObject *self, PyObject *a); // Emit compiler warnings when we get to Python 3.14. #if PY_VERSION_HEX >= 0x030e00C0 -# error \ - "In _testclinic.c, update the clinic input of " \ - "'_testclinic.DeprStarInit.cloned'." +# error "Update the clinic input of '_testclinic.DeprStarInit.cloned'." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER -# pragma message ( \ - "In _testclinic.c, update the clinic input of " \ - "'_testclinic.DeprStarInit.cloned'.") +# pragma message ("Update the clinic input of '_testclinic.DeprStarInit.cloned'.") # else -# warning \ - "In _testclinic.c, update the clinic input of " \ - "'_testclinic.DeprStarInit.cloned'." +# warning "Update the clinic input of '_testclinic.DeprStarInit.cloned'." # endif #endif + static PyObject * depr_star_init_clone(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -366,27 +346,27 @@ PyDoc_STRVAR(depr_kwd_new__doc__, "DeprKwdNew(a=None)\n" "--\n" "\n" -"The deprecation message should use the class name instead of __new__."); +"The deprecation message should use the class name instead of __new__.\n" +"\n" +"Note: Passing keyword argument \'a\' to _testclinic.DeprKwdNew() is\n" +"deprecated. Corresponding parameter will become positional-only in\n" +"Python 3.14.\n" +""); static PyObject * depr_kwd_new_impl(PyTypeObject *type, PyObject *a); // Emit compiler warnings when we get to Python 3.14. #if PY_VERSION_HEX >= 0x030e00C0 -# error \ - "In _testclinic.c, update the clinic input of " \ - "'_testclinic.DeprKwdNew.__new__'." +# error "Update the clinic input of '_testclinic.DeprKwdNew.__new__'." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER -# pragma message ( \ - "In _testclinic.c, update the clinic input of " \ - "'_testclinic.DeprKwdNew.__new__'.") +# pragma message ("Update the clinic input of '_testclinic.DeprKwdNew.__new__'.") # else -# warning \ - "In _testclinic.c, update the clinic input of " \ - "'_testclinic.DeprKwdNew.__new__'." +# warning "Update the clinic input of '_testclinic.DeprKwdNew.__new__'." # endif #endif + static PyObject * depr_kwd_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) { @@ -453,27 +433,27 @@ PyDoc_STRVAR(depr_kwd_init__doc__, "DeprKwdInit(a=None)\n" "--\n" "\n" -"The deprecation message should use the class name instead of __init__."); +"The deprecation message should use the class name instead of __init__.\n" +"\n" +"Note: Passing keyword argument \'a\' to _testclinic.DeprKwdInit() is\n" +"deprecated. Corresponding parameter will become positional-only in\n" +"Python 3.14.\n" +""); static int depr_kwd_init_impl(PyObject *self, PyObject *a); // Emit compiler warnings when we get to Python 3.14. #if PY_VERSION_HEX >= 0x030e00C0 -# error \ - "In _testclinic.c, update the clinic input of " \ - "'_testclinic.DeprKwdInit.__init__'." +# error "Update the clinic input of '_testclinic.DeprKwdInit.__init__'." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER -# pragma message ( \ - "In _testclinic.c, update the clinic input of " \ - "'_testclinic.DeprKwdInit.__init__'.") +# pragma message ("Update the clinic input of '_testclinic.DeprKwdInit.__init__'.") # else -# warning \ - "In _testclinic.c, update the clinic input of " \ - "'_testclinic.DeprKwdInit.__init__'." +# warning "Update the clinic input of '_testclinic.DeprKwdInit.__init__'." # endif #endif + static int depr_kwd_init(PyObject *self, PyObject *args, PyObject *kwargs) { @@ -553,17 +533,15 @@ depr_star_pos0_len1_impl(PyObject *module, PyObject *a); // Emit compiler warnings when we get to Python 3.14. #if PY_VERSION_HEX >= 0x030e00C0 -# error \ - "In _testclinic.c, update the clinic input of 'depr_star_pos0_len1'." +# error "Update the clinic input of 'depr_star_pos0_len1'." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER -# pragma message ( \ - "In _testclinic.c, update the clinic input of 'depr_star_pos0_len1'.") +# pragma message ("Update the clinic input of 'depr_star_pos0_len1'.") # else -# warning \ - "In _testclinic.c, update the clinic input of 'depr_star_pos0_len1'." +# warning "Update the clinic input of 'depr_star_pos0_len1'." # endif #endif + static PyObject * depr_star_pos0_len1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -633,17 +611,15 @@ depr_star_pos0_len2_impl(PyObject *module, PyObject *a, PyObject *b); // Emit compiler warnings when we get to Python 3.14. #if PY_VERSION_HEX >= 0x030e00C0 -# error \ - "In _testclinic.c, update the clinic input of 'depr_star_pos0_len2'." +# error "Update the clinic input of 'depr_star_pos0_len2'." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER -# pragma message ( \ - "In _testclinic.c, update the clinic input of 'depr_star_pos0_len2'.") +# pragma message ("Update the clinic input of 'depr_star_pos0_len2'.") # else -# warning \ - "In _testclinic.c, update the clinic input of 'depr_star_pos0_len2'." +# warning "Update the clinic input of 'depr_star_pos0_len2'." # endif #endif + static PyObject * depr_star_pos0_len2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -716,20 +692,15 @@ depr_star_pos0_len3_with_kwd_impl(PyObject *module, PyObject *a, PyObject *b, // Emit compiler warnings when we get to Python 3.14. #if PY_VERSION_HEX >= 0x030e00C0 -# error \ - "In _testclinic.c, update the clinic input of " \ - "'depr_star_pos0_len3_with_kwd'." +# error "Update the clinic input of 'depr_star_pos0_len3_with_kwd'." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER -# pragma message ( \ - "In _testclinic.c, update the clinic input of " \ - "'depr_star_pos0_len3_with_kwd'.") +# pragma message ("Update the clinic input of 'depr_star_pos0_len3_with_kwd'.") # else -# warning \ - "In _testclinic.c, update the clinic input of " \ - "'depr_star_pos0_len3_with_kwd'." +# warning "Update the clinic input of 'depr_star_pos0_len3_with_kwd'." # endif #endif + static PyObject * depr_star_pos0_len3_with_kwd(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -805,17 +776,15 @@ depr_star_pos1_len1_opt_impl(PyObject *module, PyObject *a, PyObject *b); // Emit compiler warnings when we get to Python 3.14. #if PY_VERSION_HEX >= 0x030e00C0 -# error \ - "In _testclinic.c, update the clinic input of 'depr_star_pos1_len1_opt'." +# error "Update the clinic input of 'depr_star_pos1_len1_opt'." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER -# pragma message ( \ - "In _testclinic.c, update the clinic input of 'depr_star_pos1_len1_opt'.") +# pragma message ("Update the clinic input of 'depr_star_pos1_len1_opt'.") # else -# warning \ - "In _testclinic.c, update the clinic input of 'depr_star_pos1_len1_opt'." +# warning "Update the clinic input of 'depr_star_pos1_len1_opt'." # endif #endif + static PyObject * depr_star_pos1_len1_opt(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -892,17 +861,15 @@ depr_star_pos1_len1_impl(PyObject *module, PyObject *a, PyObject *b); // Emit compiler warnings when we get to Python 3.14. #if PY_VERSION_HEX >= 0x030e00C0 -# error \ - "In _testclinic.c, update the clinic input of 'depr_star_pos1_len1'." +# error "Update the clinic input of 'depr_star_pos1_len1'." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER -# pragma message ( \ - "In _testclinic.c, update the clinic input of 'depr_star_pos1_len1'.") +# pragma message ("Update the clinic input of 'depr_star_pos1_len1'.") # else -# warning \ - "In _testclinic.c, update the clinic input of 'depr_star_pos1_len1'." +# warning "Update the clinic input of 'depr_star_pos1_len1'." # endif #endif + static PyObject * depr_star_pos1_len1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -975,20 +942,15 @@ depr_star_pos1_len2_with_kwd_impl(PyObject *module, PyObject *a, PyObject *b, // Emit compiler warnings when we get to Python 3.14. #if PY_VERSION_HEX >= 0x030e00C0 -# error \ - "In _testclinic.c, update the clinic input of " \ - "'depr_star_pos1_len2_with_kwd'." +# error "Update the clinic input of 'depr_star_pos1_len2_with_kwd'." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER -# pragma message ( \ - "In _testclinic.c, update the clinic input of " \ - "'depr_star_pos1_len2_with_kwd'.") +# pragma message ("Update the clinic input of 'depr_star_pos1_len2_with_kwd'.") # else -# warning \ - "In _testclinic.c, update the clinic input of " \ - "'depr_star_pos1_len2_with_kwd'." +# warning "Update the clinic input of 'depr_star_pos1_len2_with_kwd'." # endif #endif + static PyObject * depr_star_pos1_len2_with_kwd(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -1065,17 +1027,15 @@ depr_star_pos2_len1_impl(PyObject *module, PyObject *a, PyObject *b, // Emit compiler warnings when we get to Python 3.14. #if PY_VERSION_HEX >= 0x030e00C0 -# error \ - "In _testclinic.c, update the clinic input of 'depr_star_pos2_len1'." +# error "Update the clinic input of 'depr_star_pos2_len1'." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER -# pragma message ( \ - "In _testclinic.c, update the clinic input of 'depr_star_pos2_len1'.") +# pragma message ("Update the clinic input of 'depr_star_pos2_len1'.") # else -# warning \ - "In _testclinic.c, update the clinic input of 'depr_star_pos2_len1'." +# warning "Update the clinic input of 'depr_star_pos2_len1'." # endif #endif + static PyObject * depr_star_pos2_len1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -1150,17 +1110,15 @@ depr_star_pos2_len2_impl(PyObject *module, PyObject *a, PyObject *b, // Emit compiler warnings when we get to Python 3.14. #if PY_VERSION_HEX >= 0x030e00C0 -# error \ - "In _testclinic.c, update the clinic input of 'depr_star_pos2_len2'." +# error "Update the clinic input of 'depr_star_pos2_len2'." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER -# pragma message ( \ - "In _testclinic.c, update the clinic input of 'depr_star_pos2_len2'.") +# pragma message ("Update the clinic input of 'depr_star_pos2_len2'.") # else -# warning \ - "In _testclinic.c, update the clinic input of 'depr_star_pos2_len2'." +# warning "Update the clinic input of 'depr_star_pos2_len2'." # endif #endif + static PyObject * depr_star_pos2_len2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -1237,20 +1195,15 @@ depr_star_pos2_len2_with_kwd_impl(PyObject *module, PyObject *a, PyObject *b, // Emit compiler warnings when we get to Python 3.14. #if PY_VERSION_HEX >= 0x030e00C0 -# error \ - "In _testclinic.c, update the clinic input of " \ - "'depr_star_pos2_len2_with_kwd'." +# error "Update the clinic input of 'depr_star_pos2_len2_with_kwd'." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER -# pragma message ( \ - "In _testclinic.c, update the clinic input of " \ - "'depr_star_pos2_len2_with_kwd'.") +# pragma message ("Update the clinic input of 'depr_star_pos2_len2_with_kwd'.") # else -# warning \ - "In _testclinic.c, update the clinic input of " \ - "'depr_star_pos2_len2_with_kwd'." +# warning "Update the clinic input of 'depr_star_pos2_len2_with_kwd'." # endif #endif + static PyObject * depr_star_pos2_len2_with_kwd(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -1314,7 +1267,11 @@ depr_star_pos2_len2_with_kwd(PyObject *module, PyObject *const *args, Py_ssize_t PyDoc_STRVAR(depr_kwd_required_1__doc__, "depr_kwd_required_1($module, a, /, b)\n" "--\n" -"\n"); +"\n" +"Note: Passing keyword argument \'b\' to depr_kwd_required_1() is\n" +"deprecated. Corresponding parameter will become positional-only in\n" +"Python 3.14.\n" +""); #define DEPR_KWD_REQUIRED_1_METHODDEF \ {"depr_kwd_required_1", _PyCFunction_CAST(depr_kwd_required_1), METH_FASTCALL|METH_KEYWORDS, depr_kwd_required_1__doc__}, @@ -1324,17 +1281,15 @@ depr_kwd_required_1_impl(PyObject *module, PyObject *a, PyObject *b); // Emit compiler warnings when we get to Python 3.14. #if PY_VERSION_HEX >= 0x030e00C0 -# error \ - "In _testclinic.c, update the clinic input of 'depr_kwd_required_1'." +# error "Update the clinic input of 'depr_kwd_required_1'." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER -# pragma message ( \ - "In _testclinic.c, update the clinic input of 'depr_kwd_required_1'.") +# pragma message ("Update the clinic input of 'depr_kwd_required_1'.") # else -# warning \ - "In _testclinic.c, update the clinic input of 'depr_kwd_required_1'." +# warning "Update the clinic input of 'depr_kwd_required_1'." # endif #endif + static PyObject * depr_kwd_required_1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -1392,7 +1347,11 @@ depr_kwd_required_1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P PyDoc_STRVAR(depr_kwd_required_2__doc__, "depr_kwd_required_2($module, a, /, b, c)\n" "--\n" -"\n"); +"\n" +"Note: Passing keyword arguments \'b\' and \'c\' to depr_kwd_required_2()\n" +"is deprecated. Corresponding parameters will become positional-only in\n" +"Python 3.14.\n" +""); #define DEPR_KWD_REQUIRED_2_METHODDEF \ {"depr_kwd_required_2", _PyCFunction_CAST(depr_kwd_required_2), METH_FASTCALL|METH_KEYWORDS, depr_kwd_required_2__doc__}, @@ -1403,17 +1362,15 @@ depr_kwd_required_2_impl(PyObject *module, PyObject *a, PyObject *b, // Emit compiler warnings when we get to Python 3.14. #if PY_VERSION_HEX >= 0x030e00C0 -# error \ - "In _testclinic.c, update the clinic input of 'depr_kwd_required_2'." +# error "Update the clinic input of 'depr_kwd_required_2'." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER -# pragma message ( \ - "In _testclinic.c, update the clinic input of 'depr_kwd_required_2'.") +# pragma message ("Update the clinic input of 'depr_kwd_required_2'.") # else -# warning \ - "In _testclinic.c, update the clinic input of 'depr_kwd_required_2'." +# warning "Update the clinic input of 'depr_kwd_required_2'." # endif #endif + static PyObject * depr_kwd_required_2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -1473,7 +1430,11 @@ depr_kwd_required_2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P PyDoc_STRVAR(depr_kwd_optional_1__doc__, "depr_kwd_optional_1($module, a, /, b=None)\n" "--\n" -"\n"); +"\n" +"Note: Passing keyword argument \'b\' to depr_kwd_optional_1() is\n" +"deprecated. Corresponding parameter will become positional-only in\n" +"Python 3.14.\n" +""); #define DEPR_KWD_OPTIONAL_1_METHODDEF \ {"depr_kwd_optional_1", _PyCFunction_CAST(depr_kwd_optional_1), METH_FASTCALL|METH_KEYWORDS, depr_kwd_optional_1__doc__}, @@ -1483,17 +1444,15 @@ depr_kwd_optional_1_impl(PyObject *module, PyObject *a, PyObject *b); // Emit compiler warnings when we get to Python 3.14. #if PY_VERSION_HEX >= 0x030e00C0 -# error \ - "In _testclinic.c, update the clinic input of 'depr_kwd_optional_1'." +# error "Update the clinic input of 'depr_kwd_optional_1'." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER -# pragma message ( \ - "In _testclinic.c, update the clinic input of 'depr_kwd_optional_1'.") +# pragma message ("Update the clinic input of 'depr_kwd_optional_1'.") # else -# warning \ - "In _testclinic.c, update the clinic input of 'depr_kwd_optional_1'." +# warning "Update the clinic input of 'depr_kwd_optional_1'." # endif #endif + static PyObject * depr_kwd_optional_1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -1559,7 +1518,11 @@ depr_kwd_optional_1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P PyDoc_STRVAR(depr_kwd_optional_2__doc__, "depr_kwd_optional_2($module, a, /, b=None, c=None)\n" "--\n" -"\n"); +"\n" +"Note: Passing keyword arguments \'b\' and \'c\' to depr_kwd_optional_2()\n" +"is deprecated. Corresponding parameters will become positional-only in\n" +"Python 3.14.\n" +""); #define DEPR_KWD_OPTIONAL_2_METHODDEF \ {"depr_kwd_optional_2", _PyCFunction_CAST(depr_kwd_optional_2), METH_FASTCALL|METH_KEYWORDS, depr_kwd_optional_2__doc__}, @@ -1570,17 +1533,15 @@ depr_kwd_optional_2_impl(PyObject *module, PyObject *a, PyObject *b, // Emit compiler warnings when we get to Python 3.14. #if PY_VERSION_HEX >= 0x030e00C0 -# error \ - "In _testclinic.c, update the clinic input of 'depr_kwd_optional_2'." +# error "Update the clinic input of 'depr_kwd_optional_2'." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER -# pragma message ( \ - "In _testclinic.c, update the clinic input of 'depr_kwd_optional_2'.") +# pragma message ("Update the clinic input of 'depr_kwd_optional_2'.") # else -# warning \ - "In _testclinic.c, update the clinic input of 'depr_kwd_optional_2'." +# warning "Update the clinic input of 'depr_kwd_optional_2'." # endif #endif + static PyObject * depr_kwd_optional_2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -1653,7 +1614,11 @@ depr_kwd_optional_2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P PyDoc_STRVAR(depr_kwd_optional_3__doc__, "depr_kwd_optional_3($module, /, a=None, b=None, c=None)\n" "--\n" -"\n"); +"\n" +"Note: Passing keyword arguments \'a\', \'b\' and \'c\' to\n" +"depr_kwd_optional_3() is deprecated. Corresponding parameters will\n" +"become positional-only in Python 3.14.\n" +""); #define DEPR_KWD_OPTIONAL_3_METHODDEF \ {"depr_kwd_optional_3", _PyCFunction_CAST(depr_kwd_optional_3), METH_FASTCALL|METH_KEYWORDS, depr_kwd_optional_3__doc__}, @@ -1664,17 +1629,15 @@ depr_kwd_optional_3_impl(PyObject *module, PyObject *a, PyObject *b, // Emit compiler warnings when we get to Python 3.14. #if PY_VERSION_HEX >= 0x030e00C0 -# error \ - "In _testclinic.c, update the clinic input of 'depr_kwd_optional_3'." +# error "Update the clinic input of 'depr_kwd_optional_3'." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER -# pragma message ( \ - "In _testclinic.c, update the clinic input of 'depr_kwd_optional_3'.") +# pragma message ("Update the clinic input of 'depr_kwd_optional_3'.") # else -# warning \ - "In _testclinic.c, update the clinic input of 'depr_kwd_optional_3'." +# warning "Update the clinic input of 'depr_kwd_optional_3'." # endif #endif + static PyObject * depr_kwd_optional_3(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -1752,7 +1715,11 @@ depr_kwd_optional_3(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P PyDoc_STRVAR(depr_kwd_required_optional__doc__, "depr_kwd_required_optional($module, a, /, b, c=None)\n" "--\n" -"\n"); +"\n" +"Note: Passing keyword arguments \'b\' and \'c\' to\n" +"depr_kwd_required_optional() is deprecated. Corresponding parameters\n" +"will become positional-only in Python 3.14.\n" +""); #define DEPR_KWD_REQUIRED_OPTIONAL_METHODDEF \ {"depr_kwd_required_optional", _PyCFunction_CAST(depr_kwd_required_optional), METH_FASTCALL|METH_KEYWORDS, depr_kwd_required_optional__doc__}, @@ -1763,20 +1730,15 @@ depr_kwd_required_optional_impl(PyObject *module, PyObject *a, PyObject *b, // Emit compiler warnings when we get to Python 3.14. #if PY_VERSION_HEX >= 0x030e00C0 -# error \ - "In _testclinic.c, update the clinic input of " \ - "'depr_kwd_required_optional'." +# error "Update the clinic input of 'depr_kwd_required_optional'." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER -# pragma message ( \ - "In _testclinic.c, update the clinic input of " \ - "'depr_kwd_required_optional'.") +# pragma message ("Update the clinic input of 'depr_kwd_required_optional'.") # else -# warning \ - "In _testclinic.c, update the clinic input of " \ - "'depr_kwd_required_optional'." +# warning "Update the clinic input of 'depr_kwd_required_optional'." # endif #endif + static PyObject * depr_kwd_required_optional(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -1844,7 +1806,11 @@ depr_kwd_required_optional(PyObject *module, PyObject *const *args, Py_ssize_t n PyDoc_STRVAR(depr_kwd_noinline__doc__, "depr_kwd_noinline($module, a, /, b, c=None, d=\'\')\n" "--\n" -"\n"); +"\n" +"Note: Passing keyword arguments \'b\' and \'c\' to depr_kwd_noinline() is\n" +"deprecated. Corresponding parameters will become positional-only in\n" +"Python 3.14.\n" +""); #define DEPR_KWD_NOINLINE_METHODDEF \ {"depr_kwd_noinline", _PyCFunction_CAST(depr_kwd_noinline), METH_FASTCALL|METH_KEYWORDS, depr_kwd_noinline__doc__}, @@ -1855,17 +1821,15 @@ depr_kwd_noinline_impl(PyObject *module, PyObject *a, PyObject *b, // Emit compiler warnings when we get to Python 3.14. #if PY_VERSION_HEX >= 0x030e00C0 -# error \ - "In _testclinic.c, update the clinic input of 'depr_kwd_noinline'." +# error "Update the clinic input of 'depr_kwd_noinline'." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER -# pragma message ( \ - "In _testclinic.c, update the clinic input of 'depr_kwd_noinline'.") +# pragma message ("Update the clinic input of 'depr_kwd_noinline'.") # else -# warning \ - "In _testclinic.c, update the clinic input of 'depr_kwd_noinline'." +# warning "Update the clinic input of 'depr_kwd_noinline'." # endif #endif + static PyObject * depr_kwd_noinline(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -1922,4 +1886,4 @@ depr_kwd_noinline(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyO exit: return return_value; } -/*[clinic end generated code: output=7df75e794cccde22 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=e9c6f2420b14f1fa input=a9049054013a1b77]*/ diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 4de45e761a149a..3105390c90aebb 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -852,15 +852,12 @@ class CLanguage(Language): COMPILER_DEPRECATION_WARNING_PROTOTYPE: Final[str] = r""" // Emit compiler warnings when we get to Python {major}.{minor}. #if PY_VERSION_HEX >= 0x{major:02x}{minor:02x}00C0 - # error \ - {message} + # error {message} #elif PY_VERSION_HEX >= 0x{major:02x}{minor:02x}00A0 # ifdef _MSC_VER - # pragma message ( \ - {message}) + # pragma message ({message}) # else - # warning \ - {message} + # warning {message} # endif #endif """ @@ -911,15 +908,11 @@ def compiler_deprecated_warning( # Format the preprocessor warning and error messages. assert isinstance(self.cpp.filename, str) source = os.path.basename(self.cpp.filename) - message = ( - f"In {source}, update the clinic input of {func.full_name!r}." - ) + message = f"Update the clinic input of {func.full_name!r}." code = self.COMPILER_DEPRECATION_WARNING_PROTOTYPE.format( major=minversion[0], minor=minversion[1], - message=wrapped_c_string_literal(message, suffix=" \\", - width=72, - subsequent_indent=16), + message=c_repr(message), ) return normalize_snippet(code) @@ -943,24 +936,28 @@ def deprecate_positional_use( major, minor = thenceforth # Format the deprecation message. - amount = "" - what = "arguments" + if first_pos == 0: + preamble = "Passing positional arguments to " if len(params) == 1: condition = f"nargs == {first_pos+1}" if first_pos: - amount = f"{first_pos+1} " - message = f"Parameter {pstr} will become a keyword-only parameter" + preamble = f"Passing {first_pos+1} positional arguments to " + message = preamble + ( + f"{func.fulldisplayname}() is deprecated. Parameter {pstr} will " + f"become a keyword-only parameter in Python {major}.{minor}." + ) else: condition = f"nargs > {first_pos} && nargs <= {last_pos+1}" if first_pos: - amount = f"more than {first_pos} " - if first_pos == 1: - what = "argument" - message = f"Parameters {pstr} will become keyword-only parameters" - message = ( - f"Passing {amount}positional {what} to {func.fulldisplayname}() is deprecated. " - f"{message} in Python {major}.{minor}." - ) + preamble = ( + f"Passing more than {first_pos} positional " + f"argument{'s' if first_pos != 1 else ''} to " + ) + message = preamble + ( + f"{func.fulldisplayname}() is deprecated. Parameters {pstr} will " + f"become keyword-only parameters in Python {major}.{minor}." + ) + # Append deprecation warning to docstring. docstring = textwrap.fill(f"Note: {message}") func.docstring += f"\n\n{docstring}\n" @@ -980,8 +977,8 @@ def deprecate_keyword_use( ) -> str: assert len(params) > 0 names = [repr(p.name) for p in params.values()] - first_pos, first_param = next(iter(params.items())) - last_pos, last_param = next(reversed(params.items())) + first_param = next(iter(params.values())) + last_param = next(reversed(params.values())) # Pretty-print list of names. pstr = pprint_words(names) @@ -997,7 +994,6 @@ def deprecate_keyword_use( conditions = [] for i, p in params.items(): if p.is_optional(): - #conditions.append(f"nargs < {i+1} && {argname_fmt % i}") if func.kind.new_or_init: conditions.append(f"nargs < {i+1} && kwargs && PyDict_Contains(kwargs, &_Py_ID({p.name}))") containscheck = "PyDict_Contains" @@ -1026,6 +1022,9 @@ def deprecate_keyword_use( }}}}""" else: errcheck = "" + # Append deprecation warning to docstring. + docstring = textwrap.fill(f"Note: {message}") + func.docstring += f"\n\n{docstring}\n" # Format and return the code block. code = self.DEPRECATION_WARNING_PROTOTYPE.format( condition=condition, @@ -1564,7 +1563,7 @@ def parser_body( compiler_warning = self.compiler_deprecated_warning(f, parameters) if compiler_warning: - parser_definition = compiler_warning + "\n" + parser_definition + parser_definition = compiler_warning + "\n\n" + parser_definition d = { "docstring_prototype" : docstring_prototype, @@ -5560,18 +5559,22 @@ def parse_slash(self, function: Function, version: VersionTuple | None) -> None: """Parse positional-only parameter marker '/'.""" if version is None: if self.deprecated_keyword: - fail(f"Function {function.full_name!r}: '/ [from ...]' must come after '/'") + fail(f"Function {function.name!r}: '/' must precede '/ [from ...]'") + if self.deprecated_positional: + fail(f"Function {function.name!r}: '/' must precede '* [from ...]'") + if self.keyword_only: + fail(f"Function {function.name!r}: '/' must precede '*'") if self.positional_only: fail(f"Function {function.name!r} uses '/' more than once.") else: if self.deprecated_keyword: fail(f"Function {function.name!r} uses '/ [from ...]' more than once.") - + if self.deprecated_positional: + fail(f"Function {function.name!r}: '/ [from ...]' must precede '* [from ...]'") + if self.keyword_only: + fail(f"Function {function.name!r}: '/ [from ...]' must precede '*'") self.positional_only = True self.deprecated_keyword = version - if self.keyword_only or self.deprecated_positional: - fail(f"Function {function.name!r} mixes keyword-only and " - f"positional-only parameters, which is unsupported.") if version is not None: found = False for p in reversed(function.parameters.values()): @@ -5579,7 +5582,7 @@ def parse_slash(self, function: Function, version: VersionTuple | None) -> None: break if not found: fail(f"Function {function.name!r} specifies '/ [from ...]' " - f"without any parameters beforehead.") + f"without preceding parameters.") # REQUIRED and OPTIONAL are allowed here, that allows positional-only # without option groups to work (and have default values!) allowed = { @@ -5904,7 +5907,7 @@ def check_remaining_star(self, lineno: int | None = None) -> None: fname = self.function.full_name fail(f"Function {fname!r} specifies {symbol!r} " - "without any parameters afterwards.", line_number=lineno) + f"without following parameters.", line_number=lineno) def do_post_block_processing_cleanup(self, lineno: int) -> None: """ From e271834f983054e9b500ba34a289530671b5a9bc Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 17 Aug 2023 13:31:04 +0300 Subject: [PATCH 06/13] Add a test for `* [from ...]` with not inlined parsing code --- Lib/test/test_clinic.py | 13 ++++ Modules/_testclinic.c | 21 ++++++ Modules/clinic/_testclinic_depr_star.c.h | 83 +++++++++++++++++++++++- 3 files changed, 116 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index c47089369bb26c..544e51a050a183 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -3276,6 +3276,19 @@ def test_depr_star_pos2_len2_with_kwd(self): check("a", "b", "c", d=0, e=0) check("a", "b", "c", "d", e=0) + def test_depr_star_noinline(self): + fn = ac_tester.depr_star_noinline + self.assertRaises(TypeError, fn, "a") + fn(a="a", b="b") + fn(a="a", b="b", c="c") + fn("a", b="b") + fn("a", b="b", c="c") + check = partial(self.check_depr_star, "'b' and 'c'", fn) + check("a", "b") + check("a", "b", "c") + check("a", "b", c="c") + self.assertRaises(TypeError, fn, "a", "b", "c", "d") + def test_depr_kwd_required_1(self): fn = ac_tester.depr_kwd_required_1 fn("a", "b") diff --git a/Modules/_testclinic.c b/Modules/_testclinic.c index b35d840653ecb5..5d9363e7fbfab8 100644 --- a/Modules/_testclinic.c +++ b/Modules/_testclinic.c @@ -1500,6 +1500,26 @@ depr_star_pos2_len2_with_kwd_impl(PyObject *module, PyObject *a, PyObject *b, } +/*[clinic input] +depr_star_noinline + a: object + * [from 3.14] + b: object + c: object = None + * + # Force to use _PyArg_ParseStackAndKeywords. + d: str(accept={str, robuffer}, zeroes=True) = '' +[clinic start generated code]*/ + +static PyObject * +depr_star_noinline_impl(PyObject *module, PyObject *a, PyObject *b, + PyObject *c, const char *d, Py_ssize_t d_length) +/*[clinic end generated code: output=cc27dacf5c2754af input=d36cc862a2daef98]*/ +{ + Py_RETURN_NONE; +} + + /*[clinic input] depr_kwd_required_1 a: object @@ -1698,6 +1718,7 @@ static PyMethodDef tester_methods[] = { DEPR_STAR_POS2_LEN1_METHODDEF DEPR_STAR_POS2_LEN2_METHODDEF DEPR_STAR_POS2_LEN2_WITH_KWD_METHODDEF + DEPR_STAR_NOINLINE_METHODDEF DEPR_KWD_REQUIRED_1_METHODDEF DEPR_KWD_REQUIRED_2_METHODDEF DEPR_KWD_OPTIONAL_1_METHODDEF diff --git a/Modules/clinic/_testclinic_depr_star.c.h b/Modules/clinic/_testclinic_depr_star.c.h index 8954e161266672..a117c0474c4dd8 100644 --- a/Modules/clinic/_testclinic_depr_star.c.h +++ b/Modules/clinic/_testclinic_depr_star.c.h @@ -1264,6 +1264,87 @@ depr_star_pos2_len2_with_kwd(PyObject *module, PyObject *const *args, Py_ssize_t return return_value; } +PyDoc_STRVAR(depr_star_noinline__doc__, +"depr_star_noinline($module, /, a, b, c=None, *, d=\'\')\n" +"--\n" +"\n" +"Note: Passing more than 1 positional argument to depr_star_noinline()\n" +"is deprecated. Parameters \'b\' and \'c\' will become keyword-only\n" +"parameters in Python 3.14.\n" +""); + +#define DEPR_STAR_NOINLINE_METHODDEF \ + {"depr_star_noinline", _PyCFunction_CAST(depr_star_noinline), METH_FASTCALL|METH_KEYWORDS, depr_star_noinline__doc__}, + +static PyObject * +depr_star_noinline_impl(PyObject *module, PyObject *a, PyObject *b, + PyObject *c, const char *d, Py_ssize_t d_length); + +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error "Update the clinic input of 'depr_star_noinline'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ("Update the clinic input of 'depr_star_noinline'.") +# else +# warning "Update the clinic input of 'depr_star_noinline'." +# endif +#endif + +static PyObject * +depr_star_noinline(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 4 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(a), &_Py_ID(b), &_Py_ID(c), &_Py_ID(d), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"a", "b", "c", "d", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .format = "OO|O$s#:depr_star_noinline", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *a; + PyObject *b; + PyObject *c = Py_None; + const char *d = ""; + Py_ssize_t d_length; + + if (nargs > 1 && nargs <= 3) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Passing more than 1 positional argument to depr_star_noinline() " + "is deprecated. Parameters 'b' and 'c' will become keyword-only " + "parameters in Python 3.14.", 1)) + { + goto exit; + } + } + if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, + &a, &b, &c, &d, &d_length)) { + goto exit; + } + return_value = depr_star_noinline_impl(module, a, b, c, d, d_length); + +exit: + return return_value; +} + PyDoc_STRVAR(depr_kwd_required_1__doc__, "depr_kwd_required_1($module, a, /, b)\n" "--\n" @@ -1886,4 +1967,4 @@ depr_kwd_noinline(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyO exit: return return_value; } -/*[clinic end generated code: output=e9c6f2420b14f1fa input=a9049054013a1b77]*/ +/*[clinic end generated code: output=1e8128d3fce5869f input=a9049054013a1b77]*/ From e603de750161fb3fba2d675ff92279ebbc435d3f Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 17 Aug 2023 13:37:51 +0300 Subject: [PATCH 07/13] Consistently use simple function name in error messages. --- Lib/test/test_clinic.py | 18 +++++++++--------- Tools/clinic/clinic.py | 10 ++++------ 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 544e51a050a183..6f9ac508074784 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -1559,7 +1559,7 @@ def test_parameters_required_after_star(self): "module foo\nfoo.bar\n this: int\n *", "module foo\nfoo.bar\n this: int\n *\nDocstring.", ) - err = "Function 'foo.bar' specifies '*' without following parameters." + err = "Function 'bar' specifies '*' without following parameters." for block in dataset: with self.subTest(block=block): self.expect_failure(block, err) @@ -1627,7 +1627,7 @@ def test_depr_star_invalid_format_1(self): Docstring. """ err = ( - "Function 'foo.bar': expected format '[from major.minor]' " + "Function 'bar': expected format '[from major.minor]' " "where 'major' and 'minor' are integers; got '3'" ) self.expect_failure(block, err, lineno=3) @@ -1641,7 +1641,7 @@ def test_depr_star_invalid_format_2(self): Docstring. """ err = ( - "Function 'foo.bar': expected format '[from major.minor]' " + "Function 'bar': expected format '[from major.minor]' " "where 'major' and 'minor' are integers; got 'a.b'" ) self.expect_failure(block, err, lineno=3) @@ -1655,7 +1655,7 @@ def test_depr_star_invalid_format_3(self): Docstring. """ err = ( - "Function 'foo.bar': expected format '[from major.minor]' " + "Function 'bar': expected format '[from major.minor]' " "where 'major' and 'minor' are integers; got '1.2.3'" ) self.expect_failure(block, err, lineno=3) @@ -1669,7 +1669,7 @@ def test_parameters_required_after_depr_star(self): Docstring. """ err = ( - "Function 'foo.bar' specifies '* [from ...]' without " + "Function 'bar' specifies '* [from ...]' without " "following parameters." ) self.expect_failure(block, err, lineno=4) @@ -1685,7 +1685,7 @@ def test_parameters_required_after_depr_star2(self): Docstring. """ err = ( - "Function 'foo.bar' specifies '* [from ...]' without " + "Function 'bar' specifies '* [from ...]' without " "following parameters." ) self.expect_failure(block, err, lineno=4) @@ -1699,7 +1699,7 @@ def test_depr_star_must_come_before_star(self): * [from 3.14] Docstring. """ - err = "Function 'foo.bar': '* [from ...]' must come before '*'" + err = "Function 'bar': '* [from ...]' must come before '*'" self.expect_failure(block, err, lineno=4) def test_depr_star_duplicate(self): @@ -1713,7 +1713,7 @@ def test_depr_star_duplicate(self): c: int Docstring. """ - err = "Function 'foo.bar' uses '* [from ...]' more than once." + err = "Function 'bar' uses '* [from ...]' more than once." self.expect_failure(block, err, lineno=5) def test_depr_star_duplicate2(self): @@ -1727,7 +1727,7 @@ def test_depr_star_duplicate2(self): c: int Docstring. """ - err = "Function 'foo.bar' uses '* [from ...]' more than once." + err = "Function 'bar' uses '* [from ...]' more than once." self.expect_failure(block, err, lineno=5) def test_depr_slash_duplicate(self): diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 3105390c90aebb..1bac69cd1c5d89 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -5498,14 +5498,13 @@ def parse_converter( def parse_version(self, thenceforth: str) -> VersionTuple: assert isinstance(self.function, Function) - fname = self.function.full_name try: major, minor = thenceforth.split(".") return int(major), int(minor) except ValueError: fail( - f"Function {fname!r}: expected format '[from major.minor]' " + f"Function {self.function.name!r}: expected format '[from major.minor]' " f"where 'major' and 'minor' are integers; got {thenceforth!r}" ) @@ -5518,9 +5517,9 @@ def parse_star(self, function: Function, version: VersionTuple | None) -> None: self.keyword_only = True else: if self.keyword_only: - fail(f"Function {function.full_name!r}: '* [from ...]' must come before '*'") + fail(f"Function {function.name!r}: '* [from ...]' must come before '*'") if self.deprecated_positional: - fail(f"Function {function.full_name!r} uses '* [from ...]' more than once.") + fail(f"Function {function.name!r} uses '* [from ...]' more than once.") self.deprecated_positional = version def parse_opening_square_bracket(self, function: Function) -> None: @@ -5905,8 +5904,7 @@ def check_remaining_star(self, lineno: int | None = None) -> None: return break - fname = self.function.full_name - fail(f"Function {fname!r} specifies {symbol!r} " + fail(f"Function {self.function.name!r} specifies {symbol!r} " f"without following parameters.", line_number=lineno) def do_post_block_processing_cleanup(self, lineno: int) -> None: From 3b6f6c4c141b9bdaa76da472f73422fd05dca16c Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 18 Aug 2023 16:05:17 +0300 Subject: [PATCH 08/13] Apply suggestions from code review Co-authored-by: Alex Waygood --- Doc/howto/clinic.rst | 6 +++--- Tools/clinic/clinic.py | 13 +++++++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/Doc/howto/clinic.rst b/Doc/howto/clinic.rst index 7d5b84a5882ea7..667859e20a5d87 100644 --- a/Doc/howto/clinic.rst +++ b/Doc/howto/clinic.rst @@ -1968,18 +1968,18 @@ parameter *e*:: We now want to make the *b* parameter positional-only and the *d* parameter keyword-only; -however, we'll have to wait two releases before making this change, +however, we'll have to wait two releases before making these changes, as mandated by Python's backwards-compatibility policy (see :pep:`387`). For this example, imagine we're in the development phase for Python 3.12: that means we'll be allowed to introduce deprecation warnings in Python 3.12 whenever an argument for the *b* parameter is passed by keyword or an argument for the *d* parameter is passed positionally, and we'll be allowed to make -them positional-only and keyword-only correspondingly in Python 3.14 at +them positional-only and keyword-only respectively in Python 3.14 at the earliest. We can use Argument Clinic to emit the desired deprecation warnings using the ``[from ...]`` syntax, by adding the line ``/ [from 3.14]`` right -below the *b* parameter and adding the line ``/ [from 3.14]`` right above +below the *b* parameter and adding the line ``* [from 3.14]`` right above the *d* parameter:: /*[clinic input] diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 1bac69cd1c5d89..f80094537239ec 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -5497,6 +5497,7 @@ def parse_converter( ) def parse_version(self, thenceforth: str) -> VersionTuple: + """Parse Python version in `[from ...]` marker.""" assert isinstance(self.function, Function) try: @@ -5509,7 +5510,11 @@ def parse_version(self, thenceforth: str) -> VersionTuple: ) def parse_star(self, function: Function, version: VersionTuple | None) -> None: - """Parse keyword-only parameter marker '*'.""" + """Parse keyword-only parameter marker '*'. + + The 'version' parameter signifies the future version from which + the marker will take effect (None means it is already in effect). + """ if version is None: if self.keyword_only: fail(f"Function {function.name!r} uses '*' more than once.") @@ -5555,7 +5560,11 @@ def parse_closing_square_bracket(self, function: Function) -> None: f"(Unexpected state {st}.c)") def parse_slash(self, function: Function, version: VersionTuple | None) -> None: - """Parse positional-only parameter marker '/'.""" + """Parse positional-only parameter marker '/'. + + The 'version' parameter signifies the future version from which + the marker will take effect (None means it is already in effect). + """ if version is None: if self.deprecated_keyword: fail(f"Function {function.name!r}: '/' must precede '/ [from ...]'") From 158c5e3d162c3342178de88ae6f4fae679040305 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 18 Aug 2023 16:11:53 +0300 Subject: [PATCH 09/13] Rename _testclinic_depr_star.c.h -> _testclinic_depr.c.h --- Modules/_testclinic.c | 6 +++--- .../{_testclinic_depr_star.c.h => _testclinic_depr.c.h} | 0 2 files changed, 3 insertions(+), 3 deletions(-) rename Modules/clinic/{_testclinic_depr_star.c.h => _testclinic_depr.c.h} (100%) diff --git a/Modules/_testclinic.c b/Modules/_testclinic.c index 5d9363e7fbfab8..46cc9b5527e9e4 100644 --- a/Modules/_testclinic.c +++ b/Modules/_testclinic.c @@ -1195,14 +1195,14 @@ clone_with_conv_f2_impl(PyObject *module, custom_t path) /*[clinic input] output push -destination deprstar new file '{dirname}/clinic/_testclinic_depr_star.c.h' +destination deprstar new file '{dirname}/clinic/_testclinic_depr.c.h' output everything deprstar #output methoddef_ifndef buffer 1 output docstring_prototype suppress output parser_prototype suppress output impl_definition block [clinic start generated code]*/ -/*[clinic end generated code: output=da39a3ee5e6b4b0d input=f88f37038e00fb0a]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=32116eac48a42d34]*/ // Mock Python version 3.8 @@ -1211,7 +1211,7 @@ output impl_definition block #define PY_VERSION_HEX 0x03080000 -#include "clinic/_testclinic_depr_star.c.h" +#include "clinic/_testclinic_depr.c.h" /*[clinic input] diff --git a/Modules/clinic/_testclinic_depr_star.c.h b/Modules/clinic/_testclinic_depr.c.h similarity index 100% rename from Modules/clinic/_testclinic_depr_star.c.h rename to Modules/clinic/_testclinic_depr.c.h From 5c92d36b85f887fea7ee90e2e1273a5c73d1ee7c Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 18 Aug 2023 17:47:59 +0300 Subject: [PATCH 10/13] More efficient checks in case of inlined parsing code. --- Modules/clinic/_testclinic_depr.c.h | 32 +++++++---------------------- Tools/clinic/clinic.py | 24 +++++++++++++--------- 2 files changed, 21 insertions(+), 35 deletions(-) diff --git a/Modules/clinic/_testclinic_depr.c.h b/Modules/clinic/_testclinic_depr.c.h index a117c0474c4dd8..f4f0cbe067ed26 100644 --- a/Modules/clinic/_testclinic_depr.c.h +++ b/Modules/clinic/_testclinic_depr.c.h @@ -406,10 +406,7 @@ depr_kwd_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) if (!fastargs) { goto exit; } - if (nargs < 1 && kwargs && PyDict_Contains(kwargs, &_Py_ID(a))) { - if (PyErr_Occurred()) { // PyDict_Contains() above can fail - goto exit; - } + if (nargs < 1 && fastargs[0]) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing keyword argument 'a' to _testclinic.DeprKwdNew() is " "deprecated. Corresponding parameter will become positional-only " @@ -493,10 +490,7 @@ depr_kwd_init(PyObject *self, PyObject *args, PyObject *kwargs) if (!fastargs) { goto exit; } - if (nargs < 1 && kwargs && PyDict_Contains(kwargs, &_Py_ID(a))) { - if (PyErr_Occurred()) { // PyDict_Contains() above can fail - goto exit; - } + if (nargs < 1 && fastargs[0]) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing keyword argument 'a' to _testclinic.DeprKwdInit() is " "deprecated. Corresponding parameter will become positional-only " @@ -1572,10 +1566,7 @@ depr_kwd_optional_1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P if (!args) { goto exit; } - if (nargs < 2 && kwnames && PySequence_Contains(kwnames, &_Py_ID(b))) { - if (PyErr_Occurred()) { // PySequence_Contains() above can fail - goto exit; - } + if (nargs < 2 && args[1]) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing keyword argument 'b' to depr_kwd_optional_1() is " "deprecated. Corresponding parameter will become positional-only " @@ -1662,10 +1653,7 @@ depr_kwd_optional_2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P if (!args) { goto exit; } - if ((nargs < 2 && kwnames && PySequence_Contains(kwnames, &_Py_ID(b))) || (nargs < 3 && kwnames && PySequence_Contains(kwnames, &_Py_ID(c)))) { - if (PyErr_Occurred()) { // PySequence_Contains() above can fail - goto exit; - } + if ((nargs < 2 && args[1]) || (nargs < 3 && args[2])) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing keyword arguments 'b' and 'c' to depr_kwd_optional_2() " "is deprecated. Corresponding parameters will become " @@ -1758,10 +1746,7 @@ depr_kwd_optional_3(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P if (!args) { goto exit; } - if ((nargs < 1 && kwnames && PySequence_Contains(kwnames, &_Py_ID(a))) || (nargs < 2 && kwnames && PySequence_Contains(kwnames, &_Py_ID(b))) || (nargs < 3 && kwnames && PySequence_Contains(kwnames, &_Py_ID(c)))) { - if (PyErr_Occurred()) { // PySequence_Contains() above can fail - goto exit; - } + if ((nargs < 1 && args[0]) || (nargs < 2 && args[1]) || (nargs < 3 && args[2])) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing keyword arguments 'a', 'b' and 'c' to " "depr_kwd_optional_3() is deprecated. Corresponding parameters " @@ -1859,10 +1844,7 @@ depr_kwd_required_optional(PyObject *module, PyObject *const *args, Py_ssize_t n if (!args) { goto exit; } - if ((nargs < 2) || (nargs < 3 && kwnames && PySequence_Contains(kwnames, &_Py_ID(c)))) { - if (PyErr_Occurred()) { // PySequence_Contains() above can fail - goto exit; - } + if ((nargs < 2) || (nargs < 3 && args[2])) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing keyword arguments 'b' and 'c' to " "depr_kwd_required_optional() is deprecated. Corresponding " @@ -1967,4 +1949,4 @@ depr_kwd_noinline(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyO exit: return return_value; } -/*[clinic end generated code: output=1e8128d3fce5869f input=a9049054013a1b77]*/ +/*[clinic end generated code: output=b3d2164287f7ab27 input=a9049054013a1b77]*/ diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 955ee7a5716754..ebc82bfc4fc50e 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -974,6 +974,7 @@ def deprecate_keyword_use( self, func: Function, params: dict[int, Parameter], + argname_fmt: str | None, ) -> str: assert len(params) > 0 names = [repr(p.name) for p in params.values()] @@ -994,7 +995,9 @@ def deprecate_keyword_use( conditions = [] for i, p in params.items(): if p.is_optional(): - if func.kind.new_or_init: + if argname_fmt: + conditions.append(f"nargs < {i+1} && {argname_fmt % i}") + elif func.kind.new_or_init: conditions.append(f"nargs < {i+1} && kwargs && PyDict_Contains(kwargs, &_Py_ID({p.name}))") containscheck = "PyDict_Contains" else: @@ -1022,9 +1025,10 @@ def deprecate_keyword_use( }}}}""" else: errcheck = "" - # Append deprecation warning to docstring. - docstring = textwrap.fill(f"Note: {message}") - func.docstring += f"\n\n{docstring}\n" + if argname_fmt: + # Append deprecation warning to docstring. + docstring = textwrap.fill(f"Note: {message}") + func.docstring += f"\n\n{docstring}\n" # Format and return the code block. code = self.DEPRECATION_WARNING_PROTOTYPE.format( condition=condition, @@ -1338,10 +1342,6 @@ def parser_body( deprecated_positionals[i] = p if p.deprecated_keyword: deprecated_keywords[i] = p - deprecated: list[str] = [] - if deprecated_keywords: - code = self.deprecate_keyword_use(f, deprecated_keywords) - deprecated.append(code) has_optional_kw = (max(pos_only, min_pos) + min_kw_only < len(converters) - int(vararg != NO_VARARG)) if vararg == NO_VARARG: @@ -1395,7 +1395,9 @@ def parser_body( flags = 'METH_METHOD|' + flags parser_prototype = self.PARSER_PROTOTYPE_DEF_CLASS - parser_code.extend(deprecated) + if deprecated_keywords: + code = self.deprecate_keyword_use(f, deprecated_keywords, argname_fmt) + parser_code.append(code) add_label: str | None = None for i, p in enumerate(parameters): @@ -1474,7 +1476,9 @@ def parser_body( goto exit; }} """, indent=4)] - parser_code.extend(deprecated) + if deprecated_keywords: + code = self.deprecate_keyword_use(f, deprecated_keywords, None) + parser_code.append(code) if deprecated_positionals: code = self.deprecate_positional_use(f, deprecated_positionals) From 660713746879e71c100ec88acf9ffbe2b8b302b0 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 18 Aug 2023 22:20:20 +0300 Subject: [PATCH 11/13] Fix non-inlined parsing in __init__ and __new__. --- Lib/test/test_clinic.py | 27 ++++++ Modules/_testclinic.c | 66 +++++++++++++ Modules/clinic/_testclinic_depr.c.h | 145 +++++++++++++++++++++++++++- Tools/clinic/clinic.py | 2 + 4 files changed, 239 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 3890ba91bc98a8..934c1e3ffccca8 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -3247,6 +3247,19 @@ def test_depr_star_init_cloned(self): fn(a=None) self.check_depr_star("'a'", fn, None, name='_testclinic.DeprStarInit.cloned') + def test_depr_star_init_noinline(self): + cls = ac_tester.DeprStarInitNoInline + self.assertRaises(TypeError, cls, "a") + cls(a="a", b="b") + cls(a="a", b="b", c="c") + cls("a", b="b") + cls("a", b="b", c="c") + check = partial(self.check_depr_star, "'b' and 'c'", cls) + check("a", "b") + check("a", "b", "c") + check("a", "b", c="c") + self.assertRaises(TypeError, cls, "a", "b", "c", "d") + def test_depr_kwd_new(self): cls = ac_tester.DeprKwdNew cls() @@ -3259,6 +3272,20 @@ def test_depr_kwd_init(self): cls(None) self.check_depr_kwd("'a'", cls, a=None) + def test_depr_kwd_init_noinline(self): + cls = ac_tester.DeprKwdInitNoInline + cls = ac_tester.depr_star_noinline + self.assertRaises(TypeError, cls, "a") + cls(a="a", b="b") + cls(a="a", b="b", c="c") + cls("a", b="b") + cls("a", b="b", c="c") + check = partial(self.check_depr_star, "'b' and 'c'", cls) + check("a", "b") + check("a", "b", "c") + check("a", "b", c="c") + self.assertRaises(TypeError, cls, "a", "b", "c", "d") + def test_depr_star_pos0_len1(self): fn = ac_tester.depr_star_pos0_len1 fn(a=None) diff --git a/Modules/_testclinic.c b/Modules/_testclinic.c index 46cc9b5527e9e4..efec04d99029bb 100644 --- a/Modules/_testclinic.c +++ b/Modules/_testclinic.c @@ -1298,6 +1298,36 @@ static PyTypeObject DeprStarInit = { }; +/*[clinic input] +class _testclinic.DeprStarInitNoInline "PyObject *" "PyObject" +_testclinic.DeprStarInitNoInline.__init__ as depr_star_init_noinline + a: object + * [from 3.14] + b: object + c: object = None + * + # Force to use _PyArg_ParseTupleAndKeywordsFast. + d: str(accept={str, robuffer}, zeroes=True) = '' +[clinic start generated code]*/ + +static int +depr_star_init_noinline_impl(PyObject *self, PyObject *a, PyObject *b, + PyObject *c, const char *d, Py_ssize_t d_length) +/*[clinic end generated code: output=9b31fc167f1bf9f7 input=5a887543122bca48]*/ +{ + return 0; +} + +static PyTypeObject DeprStarInitNoInline = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "_testclinic.DeprStarInitNoInline", + .tp_basicsize = sizeof(PyObject), + .tp_new = PyType_GenericNew, + .tp_init = depr_star_init_noinline, + .tp_flags = Py_TPFLAGS_DEFAULT, +}; + + /*[clinic input] class _testclinic.DeprKwdNew "PyObject *" "PyObject" @classmethod @@ -1348,6 +1378,36 @@ static PyTypeObject DeprKwdInit = { }; +/*[clinic input] +class _testclinic.DeprKwdInitNoInline "PyObject *" "PyObject" +_testclinic.DeprKwdInitNoInline.__init__ as depr_kwd_init_noinline + a: object + / + b: object + c: object = None + / [from 3.14] + # Force to use _PyArg_ParseTupleAndKeywordsFast. + d: str(accept={str, robuffer}, zeroes=True) = '' +[clinic start generated code]*/ + +static int +depr_kwd_init_noinline_impl(PyObject *self, PyObject *a, PyObject *b, + PyObject *c, const char *d, Py_ssize_t d_length) +/*[clinic end generated code: output=27759d70ddd25873 input=c19d982c8c70a930]*/ +{ + return 0; +} + +static PyTypeObject DeprKwdInitNoInline = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "_testclinic.DeprKwdInitNoInline", + .tp_basicsize = sizeof(PyObject), + .tp_new = PyType_GenericNew, + .tp_init = depr_kwd_init_noinline, + .tp_flags = Py_TPFLAGS_DEFAULT, +}; + + /*[clinic input] depr_star_pos0_len1 * [from 3.14] @@ -1749,12 +1809,18 @@ PyInit__testclinic(void) if (PyModule_AddType(m, &DeprStarInit) < 0) { goto error; } + if (PyModule_AddType(m, &DeprStarInitNoInline) < 0) { + goto error; + } if (PyModule_AddType(m, &DeprKwdNew) < 0) { goto error; } if (PyModule_AddType(m, &DeprKwdInit) < 0) { goto error; } + if (PyModule_AddType(m, &DeprKwdInitNoInline) < 0) { + goto error; + } return m; error: diff --git a/Modules/clinic/_testclinic_depr.c.h b/Modules/clinic/_testclinic_depr.c.h index f4f0cbe067ed26..ffee40c86f6482 100644 --- a/Modules/clinic/_testclinic_depr.c.h +++ b/Modules/clinic/_testclinic_depr.c.h @@ -342,6 +342,76 @@ depr_star_init_clone(PyObject *self, PyObject *const *args, Py_ssize_t nargs, Py return return_value; } +static int +depr_star_init_noinline_impl(PyObject *self, PyObject *a, PyObject *b, + PyObject *c, const char *d, Py_ssize_t d_length); + +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error "Update the clinic input of '_testclinic.DeprStarInitNoInline.__init__'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ("Update the clinic input of '_testclinic.DeprStarInitNoInline.__init__'.") +# else +# warning "Update the clinic input of '_testclinic.DeprStarInitNoInline.__init__'." +# endif +#endif + +static int +depr_star_init_noinline(PyObject *self, PyObject *args, PyObject *kwargs) +{ + int return_value = -1; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 4 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(a), &_Py_ID(b), &_Py_ID(c), &_Py_ID(d), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"a", "b", "c", "d", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .format = "OO|O$s#:DeprStarInitNoInline", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + Py_ssize_t nargs = PyTuple_GET_SIZE(args); + PyObject *a; + PyObject *b; + PyObject *c = Py_None; + const char *d = ""; + Py_ssize_t d_length; + + if (nargs > 1 && nargs <= 3) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Passing more than 1 positional argument to " + "_testclinic.DeprStarInitNoInline() is deprecated. Parameters 'b'" + " and 'c' will become keyword-only parameters in Python 3.14.", 1)) + { + goto exit; + } + } + if (!_PyArg_ParseTupleAndKeywordsFast(args, kwargs, &_parser, + &a, &b, &c, &d, &d_length)) { + goto exit; + } + return_value = depr_star_init_noinline_impl(self, a, b, c, d, d_length); + +exit: + return return_value; +} + PyDoc_STRVAR(depr_kwd_new__doc__, "DeprKwdNew(a=None)\n" "--\n" @@ -510,6 +580,79 @@ depr_kwd_init(PyObject *self, PyObject *args, PyObject *kwargs) return return_value; } +static int +depr_kwd_init_noinline_impl(PyObject *self, PyObject *a, PyObject *b, + PyObject *c, const char *d, Py_ssize_t d_length); + +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error "Update the clinic input of '_testclinic.DeprKwdInitNoInline.__init__'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ("Update the clinic input of '_testclinic.DeprKwdInitNoInline.__init__'.") +# else +# warning "Update the clinic input of '_testclinic.DeprKwdInitNoInline.__init__'." +# endif +#endif + +static int +depr_kwd_init_noinline(PyObject *self, PyObject *args, PyObject *kwargs) +{ + int return_value = -1; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 3 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(b), &_Py_ID(c), &_Py_ID(d), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"", "b", "c", "d", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .format = "OO|Os#:DeprKwdInitNoInline", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + Py_ssize_t nargs = PyTuple_GET_SIZE(args); + PyObject *a; + PyObject *b; + PyObject *c = Py_None; + const char *d = ""; + Py_ssize_t d_length; + + if (!_PyArg_ParseTupleAndKeywordsFast(args, kwargs, &_parser, + &a, &b, &c, &d, &d_length)) { + goto exit; + } + if ((nargs < 2) || (nargs < 3 && kwargs && PyDict_Contains(kwargs, &_Py_ID(c)))) { + if (PyErr_Occurred()) { // PyDict_Contains() above can fail + goto exit; + } + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Passing keyword arguments 'b' and 'c' to " + "_testclinic.DeprKwdInitNoInline() is deprecated. Corresponding " + "parameters will become positional-only in Python 3.14.", 1)) + { + goto exit; + } + } + return_value = depr_kwd_init_noinline_impl(self, a, b, c, d, d_length); + +exit: + return return_value; +} + PyDoc_STRVAR(depr_star_pos0_len1__doc__, "depr_star_pos0_len1($module, /, a)\n" "--\n" @@ -1949,4 +2092,4 @@ depr_kwd_noinline(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyO exit: return return_value; } -/*[clinic end generated code: output=b3d2164287f7ab27 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=3a52de3a72a21d91 input=a9049054013a1b77]*/ diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index ebc82bfc4fc50e..d07f5d30784e31 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -1476,6 +1476,8 @@ def parser_body( goto exit; }} """, indent=4)] + if deprecated_positionals or deprecated_keywords: + declarations += "\nPy_ssize_t nargs = PyTuple_GET_SIZE(args);" if deprecated_keywords: code = self.deprecate_keyword_use(f, deprecated_keywords, None) parser_code.append(code) From 9fee851c211adb0a827252e7bed446d1da87f2a7 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 18 Aug 2023 23:02:56 +0300 Subject: [PATCH 12/13] Fix a case of: inlined parsing + no keyword args + omit some some optional args. --- Modules/clinic/_testclinic_depr.c.h | 18 +++++++++--------- Tools/clinic/clinic.py | 11 ++++++++--- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/Modules/clinic/_testclinic_depr.c.h b/Modules/clinic/_testclinic_depr.c.h index ffee40c86f6482..661fdaf2a07181 100644 --- a/Modules/clinic/_testclinic_depr.c.h +++ b/Modules/clinic/_testclinic_depr.c.h @@ -476,7 +476,7 @@ depr_kwd_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) if (!fastargs) { goto exit; } - if (nargs < 1 && fastargs[0]) { + if (kwargs && PyDict_GET_SIZE(kwargs) && nargs < 1 && fastargs[0]) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing keyword argument 'a' to _testclinic.DeprKwdNew() is " "deprecated. Corresponding parameter will become positional-only " @@ -560,7 +560,7 @@ depr_kwd_init(PyObject *self, PyObject *args, PyObject *kwargs) if (!fastargs) { goto exit; } - if (nargs < 1 && fastargs[0]) { + if (kwargs && PyDict_GET_SIZE(kwargs) && nargs < 1 && fastargs[0]) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing keyword argument 'a' to _testclinic.DeprKwdInit() is " "deprecated. Corresponding parameter will become positional-only " @@ -635,7 +635,7 @@ depr_kwd_init_noinline(PyObject *self, PyObject *args, PyObject *kwargs) &a, &b, &c, &d, &d_length)) { goto exit; } - if ((nargs < 2) || (nargs < 3 && kwargs && PyDict_Contains(kwargs, &_Py_ID(c)))) { + if (kwargs && PyDict_GET_SIZE(kwargs) && ((nargs < 2) || (nargs < 3 && PyDict_Contains(kwargs, &_Py_ID(c))))) { if (PyErr_Occurred()) { // PyDict_Contains() above can fail goto exit; } @@ -1709,7 +1709,7 @@ depr_kwd_optional_1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P if (!args) { goto exit; } - if (nargs < 2 && args[1]) { + if (kwnames && PyTuple_GET_SIZE(kwnames) && nargs < 2 && args[1]) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing keyword argument 'b' to depr_kwd_optional_1() is " "deprecated. Corresponding parameter will become positional-only " @@ -1796,7 +1796,7 @@ depr_kwd_optional_2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P if (!args) { goto exit; } - if ((nargs < 2 && args[1]) || (nargs < 3 && args[2])) { + if (kwnames && PyTuple_GET_SIZE(kwnames) && ((nargs < 2 && args[1]) || (nargs < 3 && args[2]))) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing keyword arguments 'b' and 'c' to depr_kwd_optional_2() " "is deprecated. Corresponding parameters will become " @@ -1889,7 +1889,7 @@ depr_kwd_optional_3(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P if (!args) { goto exit; } - if ((nargs < 1 && args[0]) || (nargs < 2 && args[1]) || (nargs < 3 && args[2])) { + if (kwnames && PyTuple_GET_SIZE(kwnames) && ((nargs < 1 && args[0]) || (nargs < 2 && args[1]) || (nargs < 3 && args[2]))) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing keyword arguments 'a', 'b' and 'c' to " "depr_kwd_optional_3() is deprecated. Corresponding parameters " @@ -1987,7 +1987,7 @@ depr_kwd_required_optional(PyObject *module, PyObject *const *args, Py_ssize_t n if (!args) { goto exit; } - if ((nargs < 2) || (nargs < 3 && args[2])) { + if (kwnames && PyTuple_GET_SIZE(kwnames) && ((nargs < 2) || (nargs < 3 && args[2]))) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing keyword arguments 'b' and 'c' to " "depr_kwd_required_optional() is deprecated. Corresponding " @@ -2075,7 +2075,7 @@ depr_kwd_noinline(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyO &a, &b, &c, &d, &d_length)) { goto exit; } - if ((nargs < 2) || (nargs < 3 && kwnames && PySequence_Contains(kwnames, &_Py_ID(c)))) { + if (kwnames && PyTuple_GET_SIZE(kwnames) && ((nargs < 2) || (nargs < 3 && PySequence_Contains(kwnames, &_Py_ID(c))))) { if (PyErr_Occurred()) { // PySequence_Contains() above can fail goto exit; } @@ -2092,4 +2092,4 @@ depr_kwd_noinline(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyO exit: return return_value; } -/*[clinic end generated code: output=3a52de3a72a21d91 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=fc558c1efdcab076 input=a9049054013a1b77]*/ diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index d07f5d30784e31..fe84e810760b40 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -998,16 +998,21 @@ def deprecate_keyword_use( if argname_fmt: conditions.append(f"nargs < {i+1} && {argname_fmt % i}") elif func.kind.new_or_init: - conditions.append(f"nargs < {i+1} && kwargs && PyDict_Contains(kwargs, &_Py_ID({p.name}))") + conditions.append(f"nargs < {i+1} && PyDict_Contains(kwargs, &_Py_ID({p.name}))") containscheck = "PyDict_Contains" else: - conditions.append(f"nargs < {i+1} && kwnames && PySequence_Contains(kwnames, &_Py_ID({p.name}))") + conditions.append(f"nargs < {i+1} && PySequence_Contains(kwnames, &_Py_ID({p.name}))") containscheck = "PySequence_Contains" else: conditions = [f"nargs < {i+1}"] condition = ") || (".join(conditions) if len(conditions) > 1: - condition = f"({condition})" + condition = f"(({condition}))" + if last_param.is_optional(): + if func.kind.new_or_init: + condition = f"kwargs && PyDict_GET_SIZE(kwargs) && {condition}" + else: + condition = f"kwnames && PyTuple_GET_SIZE(kwnames) && {condition}" if len(params) == 1: what1 = "argument" what2 = "parameter" From 38c9560cb07c5a766c330fd91ff96557fca3a538 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 19 Aug 2023 08:42:44 +0300 Subject: [PATCH 13/13] Update Tools/c-analyzer/cpython/globals-to-fix.tsv. --- Tools/c-analyzer/cpython/globals-to-fix.tsv | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv b/Tools/c-analyzer/cpython/globals-to-fix.tsv index 9cf6cc64c160ee..58f9c8e8a3e9a5 100644 --- a/Tools/c-analyzer/cpython/globals-to-fix.tsv +++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv @@ -323,8 +323,10 @@ Modules/_testcapi/vectorcall.c - MethodDescriptorDerived_Type - Modules/_testcapi/vectorcall.c - MethodDescriptorNopGet_Type - Modules/_testcapi/vectorcall.c - MethodDescriptor2_Type - Modules/_testclinic.c - DeprStarInit - +Modules/_testclinic.c - DeprStarInitNoInline - Modules/_testclinic.c - DeprStarNew - Modules/_testclinic.c - DeprKwdInit - +Modules/_testclinic.c - DeprKwdInitNoInline - Modules/_testclinic.c - DeprKwdNew -