mirror of
https://github.com/python/cpython.git
synced 2025-11-03 03:22:27 +00:00
gh-107704: Argument Clinic: add support for deprecating keyword use of parameters (GH-107984)
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") Co-authored-by: Erlend E. Aasland <erlend@python.org> Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
parent
eb953d6e44
commit
2f311437cd
9 changed files with 2993 additions and 1345 deletions
|
|
@ -1941,54 +1941,70 @@ The generated docstring ends up looking like this:
|
||||||
|
|
||||||
|
|
||||||
.. _clinic-howto-deprecate-positional:
|
.. _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
|
Argument Clinic provides syntax that makes it possible to generate code that
|
||||||
deprecates passing :term:`arguments <argument>` positionally.
|
deprecates passing :term:`arguments <argument>` for positional-or-keyword
|
||||||
|
:term:`parameters <parameter>` positionally or by keyword.
|
||||||
For example, say we've got a module-level function :py:func:`!foo.myfunc`
|
For example, say we've got a module-level function :py:func:`!foo.myfunc`
|
||||||
that has three :term:`parameters <parameter>`:
|
that has five parameters: a positional-only parameter *a*, three
|
||||||
positional-or-keyword parameters *a* and *b*, and a keyword-only parameter *c*::
|
positional-or-keyword parameters *b*, *c* and *d*, and a keyword-only
|
||||||
|
parameter *e*::
|
||||||
|
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
module foo
|
module foo
|
||||||
myfunc
|
myfunc
|
||||||
a: int
|
a: int
|
||||||
|
/
|
||||||
b: int
|
b: int
|
||||||
*
|
|
||||||
c: int
|
c: int
|
||||||
|
d: int
|
||||||
|
*
|
||||||
|
e: int
|
||||||
[clinic start generated output]*/
|
[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
|
||||||
however, we'll have to wait two releases before making this change,
|
keyword-only;
|
||||||
|
however, we'll have to wait two releases before making these changes,
|
||||||
as mandated by Python's backwards-compatibility policy (see :pep:`387`).
|
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:
|
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
|
that means we'll be allowed to introduce deprecation warnings in Python 3.12
|
||||||
whenever the *b* parameter is passed positionally,
|
whenever an argument for the *b* parameter is passed by keyword or an argument
|
||||||
and we'll be allowed to make it keyword-only in Python 3.14 at the earliest.
|
for the *d* parameter is passed positionally, and we'll be allowed to make
|
||||||
|
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
|
We can use Argument Clinic to emit the desired deprecation warnings
|
||||||
using the ``* [from ...]`` syntax,
|
using the ``[from ...]`` syntax, by adding the line ``/ [from 3.14]`` right
|
||||||
by adding the line ``* [from 3.14]`` right above the *b* parameter::
|
below the *b* parameter and adding the line ``* [from 3.14]`` right above
|
||||||
|
the *d* parameter::
|
||||||
|
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
module foo
|
module foo
|
||||||
myfunc
|
myfunc
|
||||||
a: int
|
a: int
|
||||||
* [from 3.14]
|
/
|
||||||
b: int
|
b: int
|
||||||
*
|
/ [from 3.14]
|
||||||
c: int
|
c: int
|
||||||
|
* [from 3.14]
|
||||||
|
d: int
|
||||||
|
*
|
||||||
|
e: int
|
||||||
[clinic start generated output]*/
|
[clinic start generated output]*/
|
||||||
|
|
||||||
Next, regenerate Argument Clinic code (``make clinic``),
|
Next, regenerate Argument Clinic code (``make clinic``),
|
||||||
and add unit tests for the new behaviour.
|
and add unit tests for the new behaviour.
|
||||||
|
|
||||||
The generated code will now emit a :exc:`DeprecationWarning`
|
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)``).
|
||||||
C preprocessor directives are also generated for emitting
|
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,
|
from the Argument Clinic input when the deprecation period is over,
|
||||||
which means when the alpha phase of the specified Python version kicks in.
|
which means when the alpha phase of the specified Python version kicks in.
|
||||||
|
|
||||||
|
|
@ -2001,21 +2017,26 @@ Luckily for us, compiler warnings are now generated:
|
||||||
.. code-block:: none
|
.. code-block:: none
|
||||||
|
|
||||||
In file included from Modules/foomodule.c:139:
|
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]
|
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 parameter(s) 'a' and 'b' in the clinic input of 'mymod.myfunc' to be keyword-only. [-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;
|
We now close the deprecation phase by making *a* positional-only and *c*
|
||||||
replace the ``* [from ...]`` line above *b*
|
keyword-only;
|
||||||
with the ``*`` from the line above *c*::
|
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]
|
/*[clinic input]
|
||||||
module foo
|
module foo
|
||||||
myfunc
|
myfunc
|
||||||
a: int
|
a: int
|
||||||
*
|
|
||||||
b: int
|
b: int
|
||||||
|
/
|
||||||
c: int
|
c: int
|
||||||
|
*
|
||||||
|
d: int
|
||||||
|
e: int
|
||||||
[clinic start generated output]*/
|
[clinic start generated output]*/
|
||||||
|
|
||||||
Finally, run ``make clinic`` to regenerate the Argument Clinic code,
|
Finally, run ``make clinic`` to regenerate the Argument Clinic code,
|
||||||
|
|
|
||||||
|
|
@ -1611,7 +1611,7 @@ class ClinicParserTest(TestCase):
|
||||||
"module foo\nfoo.bar\n this: int\n *",
|
"module foo\nfoo.bar\n this: int\n *",
|
||||||
"module foo\nfoo.bar\n this: int\n *\nDocstring.",
|
"module foo\nfoo.bar\n this: int\n *\nDocstring.",
|
||||||
)
|
)
|
||||||
err = "Function 'foo.bar' specifies '*' without any parameters afterwards."
|
err = "Function 'bar' specifies '*' without following parameters."
|
||||||
for block in dataset:
|
for block in dataset:
|
||||||
with self.subTest(block=block):
|
with self.subTest(block=block):
|
||||||
self.expect_failure(block, err)
|
self.expect_failure(block, err)
|
||||||
|
|
@ -1679,7 +1679,7 @@ class ClinicParserTest(TestCase):
|
||||||
Docstring.
|
Docstring.
|
||||||
"""
|
"""
|
||||||
err = (
|
err = (
|
||||||
"Function 'foo.bar': expected format '* [from major.minor]' "
|
"Function 'bar': expected format '[from major.minor]' "
|
||||||
"where 'major' and 'minor' are integers; got '3'"
|
"where 'major' and 'minor' are integers; got '3'"
|
||||||
)
|
)
|
||||||
self.expect_failure(block, err, lineno=3)
|
self.expect_failure(block, err, lineno=3)
|
||||||
|
|
@ -1693,7 +1693,7 @@ class ClinicParserTest(TestCase):
|
||||||
Docstring.
|
Docstring.
|
||||||
"""
|
"""
|
||||||
err = (
|
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'"
|
"where 'major' and 'minor' are integers; got 'a.b'"
|
||||||
)
|
)
|
||||||
self.expect_failure(block, err, lineno=3)
|
self.expect_failure(block, err, lineno=3)
|
||||||
|
|
@ -1707,7 +1707,7 @@ class ClinicParserTest(TestCase):
|
||||||
Docstring.
|
Docstring.
|
||||||
"""
|
"""
|
||||||
err = (
|
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'"
|
"where 'major' and 'minor' are integers; got '1.2.3'"
|
||||||
)
|
)
|
||||||
self.expect_failure(block, err, lineno=3)
|
self.expect_failure(block, err, lineno=3)
|
||||||
|
|
@ -1721,8 +1721,24 @@ class ClinicParserTest(TestCase):
|
||||||
Docstring.
|
Docstring.
|
||||||
"""
|
"""
|
||||||
err = (
|
err = (
|
||||||
"Function 'foo.bar' specifies '* [from ...]' without "
|
"Function 'bar' specifies '* [from ...]' without "
|
||||||
"any parameters afterwards"
|
"following parameters."
|
||||||
|
)
|
||||||
|
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 'bar' specifies '* [from ...]' without "
|
||||||
|
"following parameters."
|
||||||
)
|
)
|
||||||
self.expect_failure(block, err, lineno=4)
|
self.expect_failure(block, err, lineno=4)
|
||||||
|
|
||||||
|
|
@ -1735,7 +1751,7 @@ class ClinicParserTest(TestCase):
|
||||||
* [from 3.14]
|
* [from 3.14]
|
||||||
Docstring.
|
Docstring.
|
||||||
"""
|
"""
|
||||||
err = "Function 'foo.bar': '* [from ...]' must come before '*'"
|
err = "Function 'bar': '* [from ...]' must come before '*'"
|
||||||
self.expect_failure(block, err, lineno=4)
|
self.expect_failure(block, err, lineno=4)
|
||||||
|
|
||||||
def test_depr_star_duplicate(self):
|
def test_depr_star_duplicate(self):
|
||||||
|
|
@ -1749,7 +1765,49 @@ class ClinicParserTest(TestCase):
|
||||||
c: int
|
c: int
|
||||||
Docstring.
|
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):
|
||||||
|
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_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_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)
|
self.expect_failure(block, err, lineno=5)
|
||||||
|
|
||||||
def test_single_slash(self):
|
def test_single_slash(self):
|
||||||
|
|
@ -1765,6 +1823,34 @@ class ClinicParserTest(TestCase):
|
||||||
)
|
)
|
||||||
self.expect_failure(block, err)
|
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 "
|
||||||
|
"preceding parameters."
|
||||||
|
)
|
||||||
|
self.expect_failure(block, err, lineno=2)
|
||||||
|
|
||||||
|
def test_parameters_required_before_depr_slash2(self):
|
||||||
|
block = """
|
||||||
|
module foo
|
||||||
|
foo.bar
|
||||||
|
a: int
|
||||||
|
/
|
||||||
|
/ [from 3.14]
|
||||||
|
Docstring.
|
||||||
|
"""
|
||||||
|
err = (
|
||||||
|
"Function 'bar' specifies '/ [from ...]' without "
|
||||||
|
"preceding parameters."
|
||||||
|
)
|
||||||
|
self.expect_failure(block, err, lineno=4)
|
||||||
|
|
||||||
def test_double_slash(self):
|
def test_double_slash(self):
|
||||||
block = """
|
block = """
|
||||||
module foo
|
module foo
|
||||||
|
|
@ -1787,12 +1873,61 @@ class ClinicParserTest(TestCase):
|
||||||
z: int
|
z: int
|
||||||
/
|
/
|
||||||
"""
|
"""
|
||||||
err = (
|
err = "Function 'bar': '/' must precede '*'"
|
||||||
"Function 'bar' mixes keyword-only and positional-only parameters, "
|
|
||||||
"which is unsupported."
|
|
||||||
)
|
|
||||||
self.expect_failure(block, err)
|
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': '/' must precede '* [from ...]'"
|
||||||
|
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': '/ [from ...]' must precede '* [from ...]'"
|
||||||
|
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': '/ [from ...]' must precede '*'"
|
||||||
|
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 'bar': '/' must precede '/ [from ...]'"
|
||||||
|
self.expect_failure(block, err, lineno=4)
|
||||||
|
|
||||||
def test_parameters_not_permitted_after_slash_for_now(self):
|
def test_parameters_not_permitted_after_slash_for_now(self):
|
||||||
block = """
|
block = """
|
||||||
module foo
|
module foo
|
||||||
|
|
@ -2589,11 +2724,33 @@ class ClinicFunctionalTest(unittest.TestCase):
|
||||||
locals().update((name, getattr(ac_tester, name))
|
locals().update((name, getattr(ac_tester, name))
|
||||||
for name in dir(ac_tester) if name.startswith('test_'))
|
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 = (
|
regex = (
|
||||||
fr"Passing( more than)?( [0-9]+)? positional argument(s)? to "
|
fr"Passing( more than)?( [0-9]+)? positional argument(s)? to "
|
||||||
fr"{fn.__name__}\(\) is deprecated. Parameter(s)? {pnames} will "
|
fr"{re.escape(name)}\(\) is deprecated. Parameters? {pnames} will "
|
||||||
fr"become( a)? keyword-only parameter(s)? in Python 3\.14"
|
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:
|
with self.assertWarnsRegex(DeprecationWarning, regex) as cm:
|
||||||
# Record the line number, so we're sure we've got the correct stack
|
# Record the line number, so we're sure we've got the correct stack
|
||||||
|
|
@ -3067,46 +3224,67 @@ class ClinicFunctionalTest(unittest.TestCase):
|
||||||
self.assertEqual(func(), name)
|
self.assertEqual(func(), name)
|
||||||
|
|
||||||
def test_depr_star_new(self):
|
def test_depr_star_new(self):
|
||||||
regex = re.escape(
|
cls = ac_tester.DeprStarNew
|
||||||
"Passing positional arguments to _testclinic.DeprStarNew() is "
|
cls()
|
||||||
"deprecated. Parameter 'a' will become a keyword-only parameter "
|
cls(a=None)
|
||||||
"in Python 3.14."
|
self.check_depr_star("'a'", cls, None)
|
||||||
)
|
|
||||||
with self.assertWarnsRegex(DeprecationWarning, regex) as cm:
|
|
||||||
ac_tester.DeprStarNew(None)
|
|
||||||
self.assertEqual(cm.filename, __file__)
|
|
||||||
|
|
||||||
def test_depr_star_new_cloned(self):
|
def test_depr_star_new_cloned(self):
|
||||||
regex = re.escape(
|
fn = ac_tester.DeprStarNew().cloned
|
||||||
"Passing positional arguments to _testclinic.DeprStarNew.cloned() "
|
fn()
|
||||||
"is deprecated. Parameter 'a' will become a keyword-only parameter "
|
fn(a=None)
|
||||||
"in Python 3.14."
|
self.check_depr_star("'a'", fn, None, name='_testclinic.DeprStarNew.cloned')
|
||||||
)
|
|
||||||
obj = ac_tester.DeprStarNew(a=None)
|
|
||||||
with self.assertWarnsRegex(DeprecationWarning, regex) as cm:
|
|
||||||
obj.cloned(None)
|
|
||||||
self.assertEqual(cm.filename, __file__)
|
|
||||||
|
|
||||||
def test_depr_star_init(self):
|
def test_depr_star_init(self):
|
||||||
regex = re.escape(
|
cls = ac_tester.DeprStarInit
|
||||||
"Passing positional arguments to _testclinic.DeprStarInit() is "
|
cls()
|
||||||
"deprecated. Parameter 'a' will become a keyword-only parameter "
|
cls(a=None)
|
||||||
"in Python 3.14."
|
self.check_depr_star("'a'", cls, None)
|
||||||
)
|
|
||||||
with self.assertWarnsRegex(DeprecationWarning, regex) as cm:
|
|
||||||
ac_tester.DeprStarInit(None)
|
|
||||||
self.assertEqual(cm.filename, __file__)
|
|
||||||
|
|
||||||
def test_depr_star_init_cloned(self):
|
def test_depr_star_init_cloned(self):
|
||||||
regex = re.escape(
|
fn = ac_tester.DeprStarInit().cloned
|
||||||
"Passing positional arguments to _testclinic.DeprStarInit.cloned() "
|
fn()
|
||||||
"is deprecated. Parameter 'a' will become a keyword-only parameter "
|
fn(a=None)
|
||||||
"in Python 3.14."
|
self.check_depr_star("'a'", fn, None, name='_testclinic.DeprStarInit.cloned')
|
||||||
)
|
|
||||||
obj = ac_tester.DeprStarInit(a=None)
|
def test_depr_star_init_noinline(self):
|
||||||
with self.assertWarnsRegex(DeprecationWarning, regex) as cm:
|
cls = ac_tester.DeprStarInitNoInline
|
||||||
obj.cloned(None)
|
self.assertRaises(TypeError, cls, "a")
|
||||||
self.assertEqual(cm.filename, __file__)
|
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()
|
||||||
|
cls(None)
|
||||||
|
self.check_depr_kwd("'a'", cls, a=None)
|
||||||
|
|
||||||
|
def test_depr_kwd_init(self):
|
||||||
|
cls = ac_tester.DeprKwdInit
|
||||||
|
cls()
|
||||||
|
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):
|
def test_depr_star_pos0_len1(self):
|
||||||
fn = ac_tester.depr_star_pos0_len1
|
fn = ac_tester.depr_star_pos0_len1
|
||||||
|
|
@ -3177,6 +3355,103 @@ class ClinicFunctionalTest(unittest.TestCase):
|
||||||
check("a", "b", "c", d=0, e=0)
|
check("a", "b", "c", d=0, e=0)
|
||||||
check("a", "b", "c", "d", 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")
|
||||||
|
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):
|
class PermutationTests(unittest.TestCase):
|
||||||
"""Test permutation support functions."""
|
"""Test permutation support functions."""
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
37
Modules/_sqlite/clinic/connection.c.h
generated
37
Modules/_sqlite/clinic/connection.c.h
generated
|
|
@ -16,6 +16,17 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database,
|
||||||
int cache_size, int uri,
|
int cache_size, int uri,
|
||||||
enum autocommit_mode autocommit);
|
enum autocommit_mode autocommit);
|
||||||
|
|
||||||
|
// Emit compiler warnings when we get to Python 3.15.
|
||||||
|
#if PY_VERSION_HEX >= 0x030f00C0
|
||||||
|
# error "Update the clinic input of '_sqlite3.Connection.__init__'."
|
||||||
|
#elif PY_VERSION_HEX >= 0x030f00A0
|
||||||
|
# ifdef _MSC_VER
|
||||||
|
# pragma message ("Update the clinic input of '_sqlite3.Connection.__init__'.")
|
||||||
|
# else
|
||||||
|
# warning "Update the clinic input of '_sqlite3.Connection.__init__'."
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
static int
|
static int
|
||||||
pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs)
|
pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs)
|
||||||
{
|
{
|
||||||
|
|
@ -59,28 +70,6 @@ pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs)
|
||||||
int uri = 0;
|
int uri = 0;
|
||||||
enum autocommit_mode autocommit = LEGACY_TRANSACTION_CONTROL;
|
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 (nargs > 1 && nargs <= 8) {
|
||||||
if (PyErr_WarnEx(PyExc_DeprecationWarning,
|
if (PyErr_WarnEx(PyExc_DeprecationWarning,
|
||||||
"Passing more than 1 positional argument to _sqlite3.Connection()"
|
"Passing more than 1 positional argument to _sqlite3.Connection()"
|
||||||
|
|
@ -89,7 +78,7 @@ pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs)
|
||||||
"'cached_statements' and 'uri' will become keyword-only "
|
"'cached_statements' and 'uri' will become keyword-only "
|
||||||
"parameters in Python 3.15.", 1))
|
"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);
|
fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 1, 8, 0, argsbuf);
|
||||||
|
|
@ -1692,4 +1681,4 @@ exit:
|
||||||
#ifndef DESERIALIZE_METHODDEF
|
#ifndef DESERIALIZE_METHODDEF
|
||||||
#define DESERIALIZE_METHODDEF
|
#define DESERIALIZE_METHODDEF
|
||||||
#endif /* !defined(DESERIALIZE_METHODDEF) */
|
#endif /* !defined(DESERIALIZE_METHODDEF) */
|
||||||
/*[clinic end generated code: output=5a05e5294ad9d2ce input=a9049054013a1b77]*/
|
/*[clinic end generated code: output=0ad9d55977a51b8f input=a9049054013a1b77]*/
|
||||||
|
|
|
||||||
|
|
@ -1195,14 +1195,14 @@ clone_with_conv_f2_impl(PyObject *module, custom_t path)
|
||||||
|
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
output push
|
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 everything deprstar
|
||||||
#output methoddef_ifndef buffer 1
|
#output methoddef_ifndef buffer 1
|
||||||
output docstring_prototype suppress
|
output docstring_prototype suppress
|
||||||
output parser_prototype suppress
|
output parser_prototype suppress
|
||||||
output impl_definition block
|
output impl_definition block
|
||||||
[clinic start generated code]*/
|
[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
|
// Mock Python version 3.8
|
||||||
|
|
@ -1211,7 +1211,7 @@ output impl_definition block
|
||||||
#define PY_VERSION_HEX 0x03080000
|
#define PY_VERSION_HEX 0x03080000
|
||||||
|
|
||||||
|
|
||||||
#include "clinic/_testclinic_depr_star.c.h"
|
#include "clinic/_testclinic_depr.c.h"
|
||||||
|
|
||||||
|
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
|
|
@ -1219,13 +1219,13 @@ class _testclinic.DeprStarNew "PyObject *" "PyObject"
|
||||||
@classmethod
|
@classmethod
|
||||||
_testclinic.DeprStarNew.__new__ as depr_star_new
|
_testclinic.DeprStarNew.__new__ as depr_star_new
|
||||||
* [from 3.14]
|
* [from 3.14]
|
||||||
a: object
|
a: object = None
|
||||||
The deprecation message should use the class name instead of __new__.
|
The deprecation message should use the class name instead of __new__.
|
||||||
[clinic start generated code]*/
|
[clinic start generated code]*/
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
depr_star_new_impl(PyTypeObject *type, PyObject *a)
|
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);
|
return type->tp_alloc(type, 0);
|
||||||
}
|
}
|
||||||
|
|
@ -1260,13 +1260,13 @@ static PyTypeObject DeprStarNew = {
|
||||||
class _testclinic.DeprStarInit "PyObject *" "PyObject"
|
class _testclinic.DeprStarInit "PyObject *" "PyObject"
|
||||||
_testclinic.DeprStarInit.__init__ as depr_star_init
|
_testclinic.DeprStarInit.__init__ as depr_star_init
|
||||||
* [from 3.14]
|
* [from 3.14]
|
||||||
a: object
|
a: object = None
|
||||||
The deprecation message should use the class name instead of __init__.
|
The deprecation message should use the class name instead of __init__.
|
||||||
[clinic start generated code]*/
|
[clinic start generated code]*/
|
||||||
|
|
||||||
static int
|
static int
|
||||||
depr_star_init_impl(PyObject *self, PyObject *a)
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -1298,6 +1298,116 @@ 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
|
||||||
|
_testclinic.DeprKwdNew.__new__ as depr_kwd_new
|
||||||
|
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=6c7d13c471013c10]*/
|
||||||
|
{
|
||||||
|
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 = 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=b9bf3c20f012d539]*/
|
||||||
|
{
|
||||||
|
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]
|
||||||
|
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]
|
/*[clinic input]
|
||||||
depr_star_pos0_len1
|
depr_star_pos0_len1
|
||||||
* [from 3.14]
|
* [from 3.14]
|
||||||
|
|
@ -1450,6 +1560,148 @@ 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
|
||||||
|
/
|
||||||
|
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
|
// Reset PY_VERSION_HEX
|
||||||
#undef PY_VERSION_HEX
|
#undef PY_VERSION_HEX
|
||||||
#define PY_VERSION_HEX _SAVED_PY_VERSION
|
#define PY_VERSION_HEX _SAVED_PY_VERSION
|
||||||
|
|
@ -1526,6 +1778,14 @@ static PyMethodDef tester_methods[] = {
|
||||||
DEPR_STAR_POS2_LEN1_METHODDEF
|
DEPR_STAR_POS2_LEN1_METHODDEF
|
||||||
DEPR_STAR_POS2_LEN2_METHODDEF
|
DEPR_STAR_POS2_LEN2_METHODDEF
|
||||||
DEPR_STAR_POS2_LEN2_WITH_KWD_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
|
||||||
|
DEPR_KWD_OPTIONAL_2_METHODDEF
|
||||||
|
DEPR_KWD_OPTIONAL_3_METHODDEF
|
||||||
|
DEPR_KWD_REQUIRED_OPTIONAL_METHODDEF
|
||||||
|
DEPR_KWD_NOINLINE_METHODDEF
|
||||||
{NULL, NULL}
|
{NULL, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1549,6 +1809,18 @@ PyInit__testclinic(void)
|
||||||
if (PyModule_AddType(m, &DeprStarInit) < 0) {
|
if (PyModule_AddType(m, &DeprStarInit) < 0) {
|
||||||
goto error;
|
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;
|
return m;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
|
|
|
||||||
2095
Modules/clinic/_testclinic_depr.c.h
generated
Normal file
2095
Modules/clinic/_testclinic_depr.c.h
generated
Normal file
File diff suppressed because it is too large
Load diff
1140
Modules/clinic/_testclinic_depr_star.c.h
generated
1140
Modules/clinic/_testclinic_depr_star.c.h
generated
File diff suppressed because it is too large
Load diff
|
|
@ -323,7 +323,11 @@ Modules/_testcapi/vectorcall.c - MethodDescriptorDerived_Type -
|
||||||
Modules/_testcapi/vectorcall.c - MethodDescriptorNopGet_Type -
|
Modules/_testcapi/vectorcall.c - MethodDescriptorNopGet_Type -
|
||||||
Modules/_testcapi/vectorcall.c - MethodDescriptor2_Type -
|
Modules/_testcapi/vectorcall.c - MethodDescriptor2_Type -
|
||||||
Modules/_testclinic.c - DeprStarInit -
|
Modules/_testclinic.c - DeprStarInit -
|
||||||
|
Modules/_testclinic.c - DeprStarInitNoInline -
|
||||||
Modules/_testclinic.c - DeprStarNew -
|
Modules/_testclinic.c - DeprStarNew -
|
||||||
|
Modules/_testclinic.c - DeprKwdInit -
|
||||||
|
Modules/_testclinic.c - DeprKwdInitNoInline -
|
||||||
|
Modules/_testclinic.c - DeprKwdNew -
|
||||||
|
|
||||||
|
|
||||||
##################################
|
##################################
|
||||||
|
|
|
||||||
|
Can't render this file because it has a wrong number of fields in line 4.
|
|
|
@ -849,25 +849,24 @@ class CLanguage(Language):
|
||||||
#define {methoddef_name}
|
#define {methoddef_name}
|
||||||
#endif /* !defined({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}.
|
// Emit compiler warnings when we get to Python {major}.{minor}.
|
||||||
#if PY_VERSION_HEX >= 0x{major:02x}{minor:02x}00C0
|
#if PY_VERSION_HEX >= 0x{major:02x}{minor:02x}00C0
|
||||||
# error \
|
# error {message}
|
||||||
{cpp_message}
|
|
||||||
#elif PY_VERSION_HEX >= 0x{major:02x}{minor:02x}00A0
|
#elif PY_VERSION_HEX >= 0x{major:02x}{minor:02x}00A0
|
||||||
# ifdef _MSC_VER
|
# ifdef _MSC_VER
|
||||||
# pragma message ( \
|
# pragma message ({message})
|
||||||
{cpp_message})
|
|
||||||
# else
|
# else
|
||||||
# warning \
|
# warning {message}
|
||||||
{cpp_message}
|
|
||||||
# endif
|
# endif
|
||||||
#endif
|
#endif
|
||||||
if ({condition}) {{{{
|
"""
|
||||||
|
DEPRECATION_WARNING_PROTOTYPE: Final[str] = r"""
|
||||||
|
if ({condition}) {{{{{errcheck}
|
||||||
if (PyErr_WarnEx(PyExc_DeprecationWarning,
|
if (PyErr_WarnEx(PyExc_DeprecationWarning,
|
||||||
{depr_message}, 1))
|
{message}, 1))
|
||||||
{{{{
|
{{{{
|
||||||
goto exit;
|
goto exit;
|
||||||
}}}}
|
}}}}
|
||||||
}}}}
|
}}}}
|
||||||
"""
|
"""
|
||||||
|
|
@ -893,6 +892,30 @@ class CLanguage(Language):
|
||||||
function = o
|
function = o
|
||||||
return self.render_function(clinic, function)
|
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"Update the clinic input of {func.full_name!r}."
|
||||||
|
code = self.COMPILER_DEPRECATION_WARNING_PROTOTYPE.format(
|
||||||
|
major=minversion[0],
|
||||||
|
minor=minversion[1],
|
||||||
|
message=c_repr(message),
|
||||||
|
)
|
||||||
|
return normalize_snippet(code)
|
||||||
|
|
||||||
def deprecate_positional_use(
|
def deprecate_positional_use(
|
||||||
self,
|
self,
|
||||||
func: Function,
|
func: Function,
|
||||||
|
|
@ -910,15 +933,7 @@ class CLanguage(Language):
|
||||||
assert first_param.deprecated_positional == last_param.deprecated_positional
|
assert first_param.deprecated_positional == last_param.deprecated_positional
|
||||||
thenceforth = first_param.deprecated_positional
|
thenceforth = first_param.deprecated_positional
|
||||||
assert thenceforth is not None
|
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
|
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.
|
# Format the deprecation message.
|
||||||
if first_pos == 0:
|
if first_pos == 0:
|
||||||
|
|
@ -927,7 +942,7 @@ class CLanguage(Language):
|
||||||
condition = f"nargs == {first_pos+1}"
|
condition = f"nargs == {first_pos+1}"
|
||||||
if first_pos:
|
if first_pos:
|
||||||
preamble = f"Passing {first_pos+1} positional arguments to "
|
preamble = f"Passing {first_pos+1} positional arguments to "
|
||||||
depr_message = preamble + (
|
message = preamble + (
|
||||||
f"{func.fulldisplayname}() is deprecated. Parameter {pstr} will "
|
f"{func.fulldisplayname}() is deprecated. Parameter {pstr} will "
|
||||||
f"become a keyword-only parameter in Python {major}.{minor}."
|
f"become a keyword-only parameter in Python {major}.{minor}."
|
||||||
)
|
)
|
||||||
|
|
@ -938,26 +953,93 @@ class CLanguage(Language):
|
||||||
f"Passing more than {first_pos} positional "
|
f"Passing more than {first_pos} positional "
|
||||||
f"argument{'s' if first_pos != 1 else ''} to "
|
f"argument{'s' if first_pos != 1 else ''} to "
|
||||||
)
|
)
|
||||||
depr_message = preamble + (
|
message = preamble + (
|
||||||
f"{func.fulldisplayname}() is deprecated. Parameters {pstr} will "
|
f"{func.fulldisplayname}() is deprecated. Parameters {pstr} will "
|
||||||
f"become keyword-only parameters in Python {major}.{minor}."
|
f"become keyword-only parameters in Python {major}.{minor}."
|
||||||
)
|
)
|
||||||
|
|
||||||
# Append deprecation warning to docstring.
|
# Append deprecation warning to docstring.
|
||||||
lines = textwrap.wrap(f"Note: {depr_message}")
|
docstring = textwrap.fill(f"Note: {message}")
|
||||||
docstring = "\n".join(lines)
|
|
||||||
func.docstring += f"\n\n{docstring}\n"
|
func.docstring += f"\n\n{docstring}\n"
|
||||||
|
|
||||||
# Format and return the code block.
|
# Format and return the code block.
|
||||||
code = self.DEPRECATED_POSITIONAL_PROTOTYPE.format(
|
code = self.DEPRECATION_WARNING_PROTOTYPE.format(
|
||||||
condition=condition,
|
condition=condition,
|
||||||
major=major,
|
errcheck="",
|
||||||
minor=minor,
|
message=wrapped_c_string_literal(message, width=64,
|
||||||
cpp_message=wrapped_c_string_literal(cpp_message, suffix=" \\",
|
subsequent_indent=20),
|
||||||
width=64,
|
)
|
||||||
subsequent_indent=16),
|
return normalize_snippet(code, indent=4)
|
||||||
depr_message=wrapped_c_string_literal(depr_message, width=64,
|
|
||||||
subsequent_indent=20),
|
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()]
|
||||||
|
first_param = next(iter(params.values()))
|
||||||
|
last_param = next(reversed(params.values()))
|
||||||
|
|
||||||
|
# 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():
|
||||||
|
if argname_fmt:
|
||||||
|
conditions.append(f"nargs < {i+1} && {argname_fmt % i}")
|
||||||
|
elif func.kind.new_or_init:
|
||||||
|
conditions.append(f"nargs < {i+1} && PyDict_Contains(kwargs, &_Py_ID({p.name}))")
|
||||||
|
containscheck = "PyDict_Contains"
|
||||||
|
else:
|
||||||
|
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}))"
|
||||||
|
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"
|
||||||
|
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 = ""
|
||||||
|
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,
|
||||||
|
errcheck=errcheck,
|
||||||
|
message=wrapped_c_string_literal(message, width=64,
|
||||||
|
subsequent_indent=20),
|
||||||
)
|
)
|
||||||
return normalize_snippet(code, indent=4)
|
return normalize_snippet(code, indent=4)
|
||||||
|
|
||||||
|
|
@ -1258,6 +1340,14 @@ class CLanguage(Language):
|
||||||
parser_definition = parser_body(parser_prototype, *parser_code)
|
parser_definition = parser_body(parser_prototype, *parser_code)
|
||||||
|
|
||||||
else:
|
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
|
||||||
|
|
||||||
has_optional_kw = (max(pos_only, min_pos) + min_kw_only < len(converters) - int(vararg != NO_VARARG))
|
has_optional_kw = (max(pos_only, min_pos) + min_kw_only < len(converters) - int(vararg != NO_VARARG))
|
||||||
if vararg == NO_VARARG:
|
if vararg == NO_VARARG:
|
||||||
args_declaration = "_PyArg_UnpackKeywords", "%s, %s, %s" % (
|
args_declaration = "_PyArg_UnpackKeywords", "%s, %s, %s" % (
|
||||||
|
|
@ -1310,7 +1400,10 @@ class CLanguage(Language):
|
||||||
flags = 'METH_METHOD|' + flags
|
flags = 'METH_METHOD|' + flags
|
||||||
parser_prototype = self.PARSER_PROTOTYPE_DEF_CLASS
|
parser_prototype = self.PARSER_PROTOTYPE_DEF_CLASS
|
||||||
|
|
||||||
deprecated_positionals: dict[int, Parameter] = {}
|
if deprecated_keywords:
|
||||||
|
code = self.deprecate_keyword_use(f, deprecated_keywords, argname_fmt)
|
||||||
|
parser_code.append(code)
|
||||||
|
|
||||||
add_label: str | None = None
|
add_label: str | None = None
|
||||||
for i, p in enumerate(parameters):
|
for i, p in enumerate(parameters):
|
||||||
if isinstance(p.converter, defining_class_converter):
|
if isinstance(p.converter, defining_class_converter):
|
||||||
|
|
@ -1325,8 +1418,6 @@ class CLanguage(Language):
|
||||||
parser_code.append("%s:" % add_label)
|
parser_code.append("%s:" % add_label)
|
||||||
add_label = None
|
add_label = None
|
||||||
if not p.is_optional():
|
if not p.is_optional():
|
||||||
if p.deprecated_positional:
|
|
||||||
deprecated_positionals[i] = p
|
|
||||||
parser_code.append(normalize_snippet(parsearg, indent=4))
|
parser_code.append(normalize_snippet(parsearg, indent=4))
|
||||||
elif i < pos_only:
|
elif i < pos_only:
|
||||||
add_label = 'skip_optional_posonly'
|
add_label = 'skip_optional_posonly'
|
||||||
|
|
@ -1356,8 +1447,6 @@ class CLanguage(Language):
|
||||||
goto %s;
|
goto %s;
|
||||||
}}
|
}}
|
||||||
""" % add_label, indent=4))
|
""" % add_label, indent=4))
|
||||||
if p.deprecated_positional:
|
|
||||||
deprecated_positionals[i] = p
|
|
||||||
if i + 1 == len(parameters):
|
if i + 1 == len(parameters):
|
||||||
parser_code.append(normalize_snippet(parsearg, indent=4))
|
parser_code.append(normalize_snippet(parsearg, indent=4))
|
||||||
else:
|
else:
|
||||||
|
|
@ -1373,12 +1462,6 @@ class CLanguage(Language):
|
||||||
}}
|
}}
|
||||||
""" % add_label, indent=4))
|
""" % 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 parser_code is not None:
|
||||||
if add_label:
|
if add_label:
|
||||||
parser_code.append("%s:" % add_label)
|
parser_code.append("%s:" % add_label)
|
||||||
|
|
@ -1398,6 +1481,17 @@ class CLanguage(Language):
|
||||||
goto exit;
|
goto exit;
|
||||||
}}
|
}}
|
||||||
""", indent=4)]
|
""", 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)
|
||||||
|
|
||||||
|
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,
|
parser_definition = parser_body(parser_prototype, *parser_code,
|
||||||
declarations=declarations)
|
declarations=declarations)
|
||||||
|
|
||||||
|
|
@ -1478,6 +1572,10 @@ class CLanguage(Language):
|
||||||
|
|
||||||
parser_definition = parser_definition.replace("{return_value_declaration}", return_value_declaration)
|
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\n" + parser_definition
|
||||||
|
|
||||||
d = {
|
d = {
|
||||||
"docstring_prototype" : docstring_prototype,
|
"docstring_prototype" : docstring_prototype,
|
||||||
"docstring_definition" : docstring_definition,
|
"docstring_definition" : docstring_definition,
|
||||||
|
|
@ -2739,6 +2837,7 @@ class Parameter:
|
||||||
group: int = 0
|
group: int = 0
|
||||||
# (`None` signifies that there is no deprecation)
|
# (`None` signifies that there is no deprecation)
|
||||||
deprecated_positional: VersionTuple | None = None
|
deprecated_positional: VersionTuple | None = None
|
||||||
|
deprecated_keyword: VersionTuple | None = None
|
||||||
right_bracket_count: int = dc.field(init=False, default=0)
|
right_bracket_count: int = dc.field(init=False, default=0)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
|
|
@ -4576,6 +4675,7 @@ class DSLParser:
|
||||||
keyword_only: bool
|
keyword_only: bool
|
||||||
positional_only: bool
|
positional_only: bool
|
||||||
deprecated_positional: VersionTuple | None
|
deprecated_positional: VersionTuple | None
|
||||||
|
deprecated_keyword: VersionTuple | None
|
||||||
group: int
|
group: int
|
||||||
parameter_state: ParamState
|
parameter_state: ParamState
|
||||||
indent: IndentStack
|
indent: IndentStack
|
||||||
|
|
@ -4583,11 +4683,7 @@ class DSLParser:
|
||||||
coexist: bool
|
coexist: bool
|
||||||
parameter_continuation: str
|
parameter_continuation: str
|
||||||
preserve_output: bool
|
preserve_output: bool
|
||||||
star_from_version_re = create_regex(
|
from_version_re = re.compile(r'([*/]) +\[from +(.+)\]')
|
||||||
before="* [from ",
|
|
||||||
after="]",
|
|
||||||
word=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, clinic: Clinic) -> None:
|
def __init__(self, clinic: Clinic) -> None:
|
||||||
self.clinic = clinic
|
self.clinic = clinic
|
||||||
|
|
@ -4612,6 +4708,7 @@ class DSLParser:
|
||||||
self.keyword_only = False
|
self.keyword_only = False
|
||||||
self.positional_only = False
|
self.positional_only = False
|
||||||
self.deprecated_positional = None
|
self.deprecated_positional = None
|
||||||
|
self.deprecated_keyword = None
|
||||||
self.group = 0
|
self.group = 0
|
||||||
self.parameter_state: ParamState = ParamState.START
|
self.parameter_state: ParamState = ParamState.START
|
||||||
self.indent = IndentStack()
|
self.indent = IndentStack()
|
||||||
|
|
@ -5089,21 +5186,22 @@ class DSLParser:
|
||||||
return
|
return
|
||||||
|
|
||||||
line = line.lstrip()
|
line = line.lstrip()
|
||||||
match = self.star_from_version_re.match(line)
|
version: VersionTuple | None = None
|
||||||
|
match = self.from_version_re.fullmatch(line)
|
||||||
if match:
|
if match:
|
||||||
self.parse_deprecated_positional(match.group(1))
|
line = match[1]
|
||||||
return
|
version = self.parse_version(match[2])
|
||||||
|
|
||||||
func = self.function
|
func = self.function
|
||||||
match line:
|
match line:
|
||||||
case '*':
|
case '*':
|
||||||
self.parse_star(func)
|
self.parse_star(func, version)
|
||||||
case '[':
|
case '[':
|
||||||
self.parse_opening_square_bracket(func)
|
self.parse_opening_square_bracket(func)
|
||||||
case ']':
|
case ']':
|
||||||
self.parse_closing_square_bracket(func)
|
self.parse_closing_square_bracket(func)
|
||||||
case '/':
|
case '/':
|
||||||
self.parse_slash(func)
|
self.parse_slash(func, version)
|
||||||
case param:
|
case param:
|
||||||
self.parse_parameter(param)
|
self.parse_parameter(param)
|
||||||
|
|
||||||
|
|
@ -5404,29 +5502,36 @@ class DSLParser:
|
||||||
"Annotations must be either a name, a function call, or a string."
|
"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:
|
||||||
|
"""Parse Python version in `[from ...]` marker."""
|
||||||
assert isinstance(self.function, Function)
|
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:
|
try:
|
||||||
major, minor = thenceforth.split(".")
|
major, minor = thenceforth.split(".")
|
||||||
self.deprecated_positional = int(major), int(minor)
|
return int(major), int(minor)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
fail(
|
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}"
|
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 '*'."""
|
"""Parse keyword-only parameter marker '*'.
|
||||||
if self.keyword_only:
|
|
||||||
fail(f"Function {function.name!r} uses '*' more than once.")
|
The 'version' parameter signifies the future version from which
|
||||||
self.deprecated_positional = None
|
the marker will take effect (None means it is already in effect).
|
||||||
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.name!r}: '* [from ...]' must come before '*'")
|
||||||
|
if self.deprecated_positional:
|
||||||
|
fail(f"Function {function.name!r} uses '* [from ...]' more than once.")
|
||||||
|
self.deprecated_positional = version
|
||||||
|
|
||||||
def parse_opening_square_bracket(self, function: Function) -> None:
|
def parse_opening_square_bracket(self, function: Function) -> None:
|
||||||
"""Parse opening parameter group symbol '['."""
|
"""Parse opening parameter group symbol '['."""
|
||||||
|
|
@ -5460,11 +5565,38 @@ class DSLParser:
|
||||||
f"has an unsupported group configuration. "
|
f"has an unsupported group configuration. "
|
||||||
f"(Unexpected state {st}.c)")
|
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 '/'."""
|
"""Parse positional-only parameter marker '/'.
|
||||||
if self.positional_only:
|
|
||||||
fail(f"Function {function.name!r} uses '/' more than once.")
|
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 ...]'")
|
||||||
|
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.positional_only = True
|
||||||
|
self.deprecated_keyword = version
|
||||||
|
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 preceding parameters.")
|
||||||
# REQUIRED and OPTIONAL are allowed here, that allows positional-only
|
# REQUIRED and OPTIONAL are allowed here, that allows positional-only
|
||||||
# without option groups to work (and have default values!)
|
# without option groups to work (and have default values!)
|
||||||
allowed = {
|
allowed = {
|
||||||
|
|
@ -5476,19 +5608,13 @@ class DSLParser:
|
||||||
if (self.parameter_state not in allowed) or self.group:
|
if (self.parameter_state not in allowed) or self.group:
|
||||||
fail(f"Function {function.name!r} has an unsupported group configuration. "
|
fail(f"Function {function.name!r} has an unsupported group configuration. "
|
||||||
f"(Unexpected state {self.parameter_state}.d)")
|
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
|
# fixup preceding parameters
|
||||||
for p in function.parameters.values():
|
for p in function.parameters.values():
|
||||||
if p.is_vararg():
|
if p.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD:
|
||||||
continue
|
if version is None:
|
||||||
if (p.kind is not inspect.Parameter.POSITIONAL_OR_KEYWORD and
|
p.kind = inspect.Parameter.POSITIONAL_ONLY
|
||||||
not isinstance(p.converter, self_converter)
|
else:
|
||||||
):
|
p.deprecated_keyword = version
|
||||||
fail(f"Function {function.name!r} mixes keyword-only and "
|
|
||||||
"positional-only parameters, which is unsupported.")
|
|
||||||
p.kind = inspect.Parameter.POSITIONAL_ONLY
|
|
||||||
|
|
||||||
def state_parameter_docstring_start(self, line: str) -> None:
|
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"
|
assert self.indent.margin is not None, "self.margin.infer() has not yet been called to set the margin"
|
||||||
|
|
@ -5773,6 +5899,29 @@ class DSLParser:
|
||||||
signature=signature,
|
signature=signature,
|
||||||
parameters=parameters).rstrip()
|
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
|
||||||
|
|
||||||
|
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:
|
def do_post_block_processing_cleanup(self, lineno: int) -> None:
|
||||||
"""
|
"""
|
||||||
Called when processing the block is done.
|
Called when processing the block is done.
|
||||||
|
|
@ -5780,28 +5929,7 @@ class DSLParser:
|
||||||
if not self.function:
|
if not self.function:
|
||||||
return
|
return
|
||||||
|
|
||||||
def check_remaining(
|
self.check_remaining_star(lineno)
|
||||||
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.function.docstring = self.format_docstring()
|
self.function.docstring = self.format_docstring()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue