From 047d18edbd688de8c5cc5d454e166e9cdd621b01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 1 Aug 2024 14:01:32 +0200 Subject: [PATCH 01/13] Fix _PyArg_UnpackKeywordsWithVararg(). This fixes an issue where passing positional arguments as explicit keyword arguments to functions accepting positional and variadic arguments. --- Python/getargs.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Python/getargs.c b/Python/getargs.c index b96ce3a22dae7c..0b3fabcfd065f8 100644 --- a/Python/getargs.c +++ b/Python/getargs.c @@ -2589,7 +2589,13 @@ _PyArg_UnpackKeywordsWithVararg(PyObject *const *args, Py_ssize_t nargs, * Otherwise, we leave a place at `buf[vararg]` for vararg tuple * so the index is `i + 1`. */ if (nargs < vararg && i != vararg) { - buf[i] = current_arg; + if (current_arg != NULL) { + // It might happen that in the previous iteration, + // we did "buf[i + 1] = current_arg" and that in + // this current loop iteration, current_arg == NULL. + // We do not want to put a NULL on a previously valid value. + buf[i] = current_arg; + } } else { buf[i + 1] = current_arg; From 15fc80460c6671d45e795d96a3753b0b685851a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 1 Aug 2024 14:01:34 +0200 Subject: [PATCH 02/13] add test --- Lib/test/test_clinic.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index f3fd610414cd8a..7e202d7ca4a625 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -3339,6 +3339,7 @@ def test_vararg_with_default(self): self.assertEqual(ac_tester.vararg_with_default(1, b=False), (1, (), False)) self.assertEqual(ac_tester.vararg_with_default(1, 2, 3, 4), (1, (2, 3, 4), False)) self.assertEqual(ac_tester.vararg_with_default(1, 2, 3, 4, b=True), (1, (2, 3, 4), True)) + self.assertEqual(ac_tester.vararg_with_default(a=1, b=False), (1, (), False)) def test_vararg_with_only_defaults(self): self.assertEqual(ac_tester.vararg_with_only_defaults(), ((), None)) From 0e601bf9f7fa89427d3f96d85f86faabbf2af2b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 1 Aug 2024 14:14:31 +0200 Subject: [PATCH 03/13] add tests --- Lib/test/test_clinic.py | 11 ++++ Modules/_testclinic.c | 24 +++++++++ Modules/clinic/_testclinic.c.h | 91 +++++++++++++++++++++++++++++++++- 3 files changed, 125 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 7e202d7ca4a625..7d1c1119e6b8ad 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -3340,6 +3340,17 @@ def test_vararg_with_default(self): self.assertEqual(ac_tester.vararg_with_default(1, 2, 3, 4), (1, (2, 3, 4), False)) self.assertEqual(ac_tester.vararg_with_default(1, 2, 3, 4, b=True), (1, (2, 3, 4), True)) self.assertEqual(ac_tester.vararg_with_default(a=1, b=False), (1, (), False)) + self.assertEqual(ac_tester.vararg_with_default(b=False, a=1), (1, (), False)) + + def test_vararg_with_multiple_defaults(self): + func = ac_tester.vararg_with_multiple_defaults + with self.assertRaises(TypeError): + func() + self.assertEqual(func(1, b1=True), (1, (), True, False, False)) + self.assertEqual(func(1, 2, 3, 4), (1, (2, 3, 4), False, False, False)) + self.assertEqual(func(1, 2, 3, 4, b1=True), (1, (2, 3, 4), True, False, False)) + self.assertEqual(func(a=1, b1=True), (1, (), True, False, False)) + self.assertEqual(func(b1=True, a=1), (1, (), True, False, False)) def test_vararg_with_only_defaults(self): self.assertEqual(ac_tester.vararg_with_only_defaults(), ((), None)) diff --git a/Modules/_testclinic.c b/Modules/_testclinic.c index 4187e13231dc69..b799bcc0dd00f2 100644 --- a/Modules/_testclinic.c +++ b/Modules/_testclinic.c @@ -1033,6 +1033,29 @@ vararg_with_default_impl(PyObject *module, PyObject *a, PyObject *args, } +/*[clinic input] +vararg_with_multiple_defaults + + a: object + *args: object + b1: bool = False + b2: bool = False + b3: bool = False + +[clinic start generated code]*/ + +static PyObject * +vararg_with_multiple_defaults_impl(PyObject *module, PyObject *a, + PyObject *args, int b1, int b2, int b3) +/*[clinic end generated code: output=66087a6689e6bee5 input=66e4b2858ad13769]*/ +{ + PyObject *obj_b1 = b1 ? Py_True : Py_False; + PyObject *obj_b2 = b2 ? Py_True : Py_False; + PyObject *obj_b3 = b3 ? Py_True : Py_False; + return pack_arguments_newref(5, a, args, obj_b1, obj_b2, obj_b3); +} + + /*[clinic input] vararg_with_only_defaults @@ -1907,6 +1930,7 @@ static PyMethodDef tester_methods[] = { VARARG_AND_POSONLY_METHODDEF VARARG_METHODDEF VARARG_WITH_DEFAULT_METHODDEF + VARARG_WITH_MULTIPLE_DEFAULTS_METHODDEF VARARG_WITH_ONLY_DEFAULTS_METHODDEF GH_32092_OOB_METHODDEF GH_32092_KW_PASS_METHODDEF diff --git a/Modules/clinic/_testclinic.c.h b/Modules/clinic/_testclinic.c.h index e02f39d15cce0f..457c42e8f3eb7a 100644 --- a/Modules/clinic/_testclinic.c.h +++ b/Modules/clinic/_testclinic.c.h @@ -2742,6 +2742,95 @@ vararg_with_default(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P return return_value; } +PyDoc_STRVAR(vararg_with_multiple_defaults__doc__, +"vararg_with_multiple_defaults($module, /, a, *args, b1=False, b2=False,\n" +" b3=False)\n" +"--\n" +"\n"); + +#define VARARG_WITH_MULTIPLE_DEFAULTS_METHODDEF \ + {"vararg_with_multiple_defaults", _PyCFunction_CAST(vararg_with_multiple_defaults), METH_FASTCALL|METH_KEYWORDS, vararg_with_multiple_defaults__doc__}, + +static PyObject * +vararg_with_multiple_defaults_impl(PyObject *module, PyObject *a, + PyObject *args, int b1, int b2, int b3); + +static PyObject * +vararg_with_multiple_defaults(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_LATIN1_CHR('a'), &_Py_ID(b1), &_Py_ID(b2), &_Py_ID(b3), }, + }; + #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", "b1", "b2", "b3", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "vararg_with_multiple_defaults", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[5]; + Py_ssize_t noptargs = Py_MIN(nargs, 1) + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; + PyObject *a; + PyObject *__clinic_args = NULL; + int b1 = 0; + int b2 = 0; + int b3 = 0; + + args = _PyArg_UnpackKeywordsWithVararg(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, 1, argsbuf); + if (!args) { + goto exit; + } + a = args[0]; + __clinic_args = args[1]; + if (!noptargs) { + goto skip_optional_kwonly; + } + if (args[2]) { + b1 = PyObject_IsTrue(args[2]); + if (b1 < 0) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_kwonly; + } + } + if (args[3]) { + b2 = PyObject_IsTrue(args[3]); + if (b2 < 0) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_kwonly; + } + } + b3 = PyObject_IsTrue(args[4]); + if (b3 < 0) { + goto exit; + } +skip_optional_kwonly: + return_value = vararg_with_multiple_defaults_impl(module, a, __clinic_args, b1, b2, b3); + +exit: + Py_XDECREF(__clinic_args); + return return_value; +} + PyDoc_STRVAR(vararg_with_only_defaults__doc__, "vararg_with_only_defaults($module, /, *args, b=None)\n" "--\n" @@ -3418,4 +3507,4 @@ _testclinic_TestClass_get_defining_class_arg(PyObject *self, PyTypeObject *cls, exit: return return_value; } -/*[clinic end generated code: output=0d0ceed6c46547bb input=a9049054013a1b77]*/ +/*[clinic end generated code: output=051098e3597dc951 input=a9049054013a1b77]*/ From 46e511ed9b5df6ad943b75d3ca37478bcad676ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 1 Aug 2024 14:14:39 +0200 Subject: [PATCH 04/13] regenerate objects --- .../internal/pycore_global_objects_fini_generated.h | 3 +++ Include/internal/pycore_global_strings.h | 3 +++ Include/internal/pycore_runtime_init_generated.h | 3 +++ Include/internal/pycore_unicodeobject_generated.h | 12 ++++++++++++ 4 files changed, 21 insertions(+) diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index d9b46df507dfd7..da3cac4f07d9b1 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -801,6 +801,9 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(attribute)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(authorizer_callback)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(autocommit)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(b1)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(b2)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(b3)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(backtick)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(base)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(before)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 10773d7a6c7e3f..6797a51b56d647 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -290,6 +290,9 @@ struct _Py_global_strings { STRUCT_FOR_ID(attribute) STRUCT_FOR_ID(authorizer_callback) STRUCT_FOR_ID(autocommit) + STRUCT_FOR_ID(b1) + STRUCT_FOR_ID(b2) + STRUCT_FOR_ID(b3) STRUCT_FOR_ID(backtick) STRUCT_FOR_ID(base) STRUCT_FOR_ID(before) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index 618f8d0a36b6c3..17177d147e0320 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -799,6 +799,9 @@ extern "C" { INIT_ID(attribute), \ INIT_ID(authorizer_callback), \ INIT_ID(autocommit), \ + INIT_ID(b1), \ + INIT_ID(b2), \ + INIT_ID(b3), \ INIT_ID(backtick), \ INIT_ID(base), \ INIT_ID(before), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index f848a002c3b5d1..96fca115ef0473 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -960,6 +960,18 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(b1); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(b2); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(b3); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(backtick); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); From 078e9f74232245eacd7e3c530330b84bb50336e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 1 Aug 2024 14:14:43 +0200 Subject: [PATCH 05/13] blurb --- .../2024-08-01-14-04-18.gh-issue-118814.m3pHDY.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-08-01-14-04-18.gh-issue-118814.m3pHDY.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-08-01-14-04-18.gh-issue-118814.m3pHDY.rst b/Misc/NEWS.d/next/Core and Builtins/2024-08-01-14-04-18.gh-issue-118814.m3pHDY.rst new file mode 100644 index 00000000000000..377ee911bb2d3b --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-08-01-14-04-18.gh-issue-118814.m3pHDY.rst @@ -0,0 +1,3 @@ +Fix argument parsing by ``_PyArg_UnpackKeywordsWithVararg`` when passing +positional as keyword arguments before variadic arguments. Patch by Bénédikt +Tran. From f13fa456c08984d6c15aab8b2be37f2bcb8e13af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 1 Aug 2024 17:38:04 +0200 Subject: [PATCH 06/13] use better names --- Lib/test/test_clinic.py | 10 ++++----- Modules/_testclinic.c | 16 ++++++-------- Modules/clinic/_testclinic.c.h | 40 +++++++++++++--------------------- 3 files changed, 27 insertions(+), 39 deletions(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 7d1c1119e6b8ad..c286f140b21aa1 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -3346,11 +3346,11 @@ def test_vararg_with_multiple_defaults(self): func = ac_tester.vararg_with_multiple_defaults with self.assertRaises(TypeError): func() - self.assertEqual(func(1, b1=True), (1, (), True, False, False)) - self.assertEqual(func(1, 2, 3, 4), (1, (2, 3, 4), False, False, False)) - self.assertEqual(func(1, 2, 3, 4, b1=True), (1, (2, 3, 4), True, False, False)) - self.assertEqual(func(a=1, b1=True), (1, (), True, False, False)) - self.assertEqual(func(b1=True, a=1), (1, (), True, False, False)) + self.assertEqual(func(1, kw1=True), (1, (), True, False)) + self.assertEqual(func(1, 2, 3, 4), (1, (2, 3, 4), False, False)) + self.assertEqual(func(1, 2, 3, 4, kw1=True), (1, (2, 3, 4), True, False)) + self.assertEqual(func(a=1, kw1=True), (1, (), True, False)) + self.assertEqual(func(kw1=True, a=1), (1, (), True, False)) def test_vararg_with_only_defaults(self): self.assertEqual(ac_tester.vararg_with_only_defaults(), ((), None)) diff --git a/Modules/_testclinic.c b/Modules/_testclinic.c index b799bcc0dd00f2..a15cf4dfefd041 100644 --- a/Modules/_testclinic.c +++ b/Modules/_testclinic.c @@ -1038,21 +1038,19 @@ vararg_with_multiple_defaults a: object *args: object - b1: bool = False - b2: bool = False - b3: bool = False + kw1: bool = False + kw2: bool = False [clinic start generated code]*/ static PyObject * vararg_with_multiple_defaults_impl(PyObject *module, PyObject *a, - PyObject *args, int b1, int b2, int b3) -/*[clinic end generated code: output=66087a6689e6bee5 input=66e4b2858ad13769]*/ + PyObject *args, int kw1, int kw2) +/*[clinic end generated code: output=ae7ee8d22dfc7fbf input=534d91e23e6d360b]*/ { - PyObject *obj_b1 = b1 ? Py_True : Py_False; - PyObject *obj_b2 = b2 ? Py_True : Py_False; - PyObject *obj_b3 = b3 ? Py_True : Py_False; - return pack_arguments_newref(5, a, args, obj_b1, obj_b2, obj_b3); + PyObject *obj_kw1 = kw1 ? Py_True : Py_False; + PyObject *obj_kw2 = kw2 ? Py_True : Py_False; + return pack_arguments_newref(4, a, args, obj_kw1, obj_kw2); } diff --git a/Modules/clinic/_testclinic.c.h b/Modules/clinic/_testclinic.c.h index 457c42e8f3eb7a..d671081c05d138 100644 --- a/Modules/clinic/_testclinic.c.h +++ b/Modules/clinic/_testclinic.c.h @@ -2743,8 +2743,8 @@ vararg_with_default(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P } PyDoc_STRVAR(vararg_with_multiple_defaults__doc__, -"vararg_with_multiple_defaults($module, /, a, *args, b1=False, b2=False,\n" -" b3=False)\n" +"vararg_with_multiple_defaults($module, /, a, *args, kw1=False,\n" +" kw2=False)\n" "--\n" "\n"); @@ -2753,7 +2753,7 @@ PyDoc_STRVAR(vararg_with_multiple_defaults__doc__, static PyObject * vararg_with_multiple_defaults_impl(PyObject *module, PyObject *a, - PyObject *args, int b1, int b2, int b3); + PyObject *args, int kw1, int kw2); static PyObject * vararg_with_multiple_defaults(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) @@ -2761,14 +2761,14 @@ vararg_with_multiple_defaults(PyObject *module, PyObject *const *args, Py_ssize_ PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) - #define NUM_KEYWORDS 4 + #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_LATIN1_CHR('a'), &_Py_ID(b1), &_Py_ID(b2), &_Py_ID(b3), }, + .ob_item = { _Py_LATIN1_CHR('a'), &_Py_ID(kw1), &_Py_ID(kw2), }, }; #undef NUM_KEYWORDS #define KWTUPLE (&_kwtuple.ob_base.ob_base) @@ -2777,20 +2777,19 @@ vararg_with_multiple_defaults(PyObject *module, PyObject *const *args, Py_ssize_ # define KWTUPLE NULL #endif // !Py_BUILD_CORE - static const char * const _keywords[] = {"a", "b1", "b2", "b3", NULL}; + static const char * const _keywords[] = {"a", "kw1", "kw2", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, .fname = "vararg_with_multiple_defaults", .kwtuple = KWTUPLE, }; #undef KWTUPLE - PyObject *argsbuf[5]; + PyObject *argsbuf[4]; Py_ssize_t noptargs = Py_MIN(nargs, 1) + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; PyObject *a; PyObject *__clinic_args = NULL; - int b1 = 0; - int b2 = 0; - int b3 = 0; + int kw1 = 0; + int kw2 = 0; args = _PyArg_UnpackKeywordsWithVararg(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, 1, argsbuf); if (!args) { @@ -2802,29 +2801,20 @@ vararg_with_multiple_defaults(PyObject *module, PyObject *const *args, Py_ssize_ goto skip_optional_kwonly; } if (args[2]) { - b1 = PyObject_IsTrue(args[2]); - if (b1 < 0) { - goto exit; - } - if (!--noptargs) { - goto skip_optional_kwonly; - } - } - if (args[3]) { - b2 = PyObject_IsTrue(args[3]); - if (b2 < 0) { + kw1 = PyObject_IsTrue(args[2]); + if (kw1 < 0) { goto exit; } if (!--noptargs) { goto skip_optional_kwonly; } } - b3 = PyObject_IsTrue(args[4]); - if (b3 < 0) { + kw2 = PyObject_IsTrue(args[3]); + if (kw2 < 0) { goto exit; } skip_optional_kwonly: - return_value = vararg_with_multiple_defaults_impl(module, a, __clinic_args, b1, b2, b3); + return_value = vararg_with_multiple_defaults_impl(module, a, __clinic_args, kw1, kw2); exit: Py_XDECREF(__clinic_args); @@ -3507,4 +3497,4 @@ _testclinic_TestClass_get_defining_class_arg(PyObject *self, PyTypeObject *cls, exit: return return_value; } -/*[clinic end generated code: output=051098e3597dc951 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=dd2556e39e89b5f6 input=a9049054013a1b77]*/ From a4cd8909fdd33ccac6ff46d9a3868c98c807cdee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 1 Aug 2024 17:38:19 +0200 Subject: [PATCH 07/13] update global objects --- .../internal/pycore_global_objects_fini_generated.h | 3 --- Include/internal/pycore_global_strings.h | 3 --- Include/internal/pycore_runtime_init_generated.h | 3 --- Include/internal/pycore_unicodeobject_generated.h | 12 ------------ 4 files changed, 21 deletions(-) diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index da3cac4f07d9b1..d9b46df507dfd7 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -801,9 +801,6 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(attribute)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(authorizer_callback)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(autocommit)); - _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(b1)); - _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(b2)); - _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(b3)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(backtick)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(base)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(before)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 6797a51b56d647..10773d7a6c7e3f 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -290,9 +290,6 @@ struct _Py_global_strings { STRUCT_FOR_ID(attribute) STRUCT_FOR_ID(authorizer_callback) STRUCT_FOR_ID(autocommit) - STRUCT_FOR_ID(b1) - STRUCT_FOR_ID(b2) - STRUCT_FOR_ID(b3) STRUCT_FOR_ID(backtick) STRUCT_FOR_ID(base) STRUCT_FOR_ID(before) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index 17177d147e0320..618f8d0a36b6c3 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -799,9 +799,6 @@ extern "C" { INIT_ID(attribute), \ INIT_ID(authorizer_callback), \ INIT_ID(autocommit), \ - INIT_ID(b1), \ - INIT_ID(b2), \ - INIT_ID(b3), \ INIT_ID(backtick), \ INIT_ID(base), \ INIT_ID(before), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index 96fca115ef0473..f848a002c3b5d1 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -960,18 +960,6 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); - string = &_Py_ID(b1); - _PyUnicode_InternStatic(interp, &string); - assert(_PyUnicode_CheckConsistency(string, 1)); - assert(PyUnicode_GET_LENGTH(string) != 1); - string = &_Py_ID(b2); - _PyUnicode_InternStatic(interp, &string); - assert(_PyUnicode_CheckConsistency(string, 1)); - assert(PyUnicode_GET_LENGTH(string) != 1); - string = &_Py_ID(b3); - _PyUnicode_InternStatic(interp, &string); - assert(_PyUnicode_CheckConsistency(string, 1)); - assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(backtick); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); From c21014e6f74beb9b93914f459b88febaddb13c2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 2 Aug 2024 12:47:32 +0200 Subject: [PATCH 08/13] fix parsing --- Python/getargs.c | 71 ++++++++++++++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 26 deletions(-) diff --git a/Python/getargs.c b/Python/getargs.c index 0b3fabcfd065f8..4a8ba065f4bf8a 100644 --- a/Python/getargs.c +++ b/Python/getargs.c @@ -2536,7 +2536,12 @@ _PyArg_UnpackKeywordsWithVararg(PyObject *const *args, Py_ssize_t nargs, return NULL; } - /* create varargs tuple */ + // Create the tuple of variadic arguments and place it at 'buf[vararg]'. + // The function has at most 'maxpos' positional arguments, possibly with + // default values or not and possibly positional-only. Since 'args' is + // an array of 'nargs' arguments representing the positional arguments + // of the call, there are only 'nargs - maxpos' arguments that will + // be considered as variadic and gathered into a single tuple. varargssize = nargs - maxpos; if (varargssize < 0) { varargssize = 0; @@ -2545,19 +2550,23 @@ _PyArg_UnpackKeywordsWithVararg(PyObject *const *args, Py_ssize_t nargs, if (!buf[vararg]) { return NULL; } - - /* copy tuple args */ - for (i = 0; i < nargs; i++) { - if (i >= vararg) { - PyTuple_SET_ITEM(buf[vararg], i - vararg, Py_NewRef(args[i])); - continue; - } - else { - buf[i] = args[i]; - } + for (i = 0; i < vararg; ++i) { + // copy the first arguments until the 'vararg' index + buf[i] = args[i]; } - - /* copy keyword args using kwtuple to drive process */ + for (i = vararg; i < nargs; ++i) { + // remaining arguments are all considered as variadic ones + PyTuple_SET_ITEM(buf[vararg], i - vararg, Py_NewRef(args[i])); + } + // We need to place the keyword arguments correctly in "buf". + // + // The buffer is always of the following form: + // + // buf = [x1, ..., xN] (*args) [k1, ..., kM] + // + // where x1, ..., xN are the positional arguments, '*args' is a tuple + // containing the variadic arguments (it will be untouched now) and + // k1, ..., kM are the keyword arguments that are not positionals. for (i = Py_MAX((int)nargs, posonly) - Py_SAFE_DOWNCAST(varargssize, Py_ssize_t, int); i < maxargs; i++) { PyObject *current_arg; if (nkwargs) { @@ -2574,26 +2583,36 @@ _PyArg_UnpackKeywordsWithVararg(PyObject *const *args, Py_ssize_t nargs, else { current_arg = NULL; } - - /* If an arguments is passed in as a keyword argument, - * it should be placed before `buf[vararg]`. + /* + * Assume that an argument at index `i` is passed in + * as a keyword argument. Depending on `i`, it should + * be placed before `buf[vararg]` or at `buf[i + 1]`. + * + * It is placed at `buf[vararg]` if `nargs < vararg` + * and `i < vararg`, e.g.: + * + * def f(a, /, b, *args): pass * - * For example: - * def f(a, /, b, *args): - * pass - * f(1, b=2) + * The `buf` array for "f(1, b=2)" is: * - * This `buf` array should be: [1, 2, NULL]. - * In this case, nargs < vararg. + * buf = [1, 2, NULL]. * - * Otherwise, we leave a place at `buf[vararg]` for vararg tuple - * so the index is `i + 1`. */ - if (nargs < vararg && i != vararg) { + * On the other hand, if the argument is a real + * keyword argument (namely `i > vararg`), then + * it is placed at `buf[i + 1]` since `buf[vararg]` + * is already taken, e.g.: + * + * def g(a, b, *args, c, d): ... + * + * The `buf` array for "f(a=1, b=2, c=3, d=4)" is: + * + * buf = [1, 2, NULL, 3, 4]. + */ + if (nargs < vararg && i < vararg) { if (current_arg != NULL) { // It might happen that in the previous iteration, // we did "buf[i + 1] = current_arg" and that in // this current loop iteration, current_arg == NULL. - // We do not want to put a NULL on a previously valid value. buf[i] = current_arg; } } From c8accdc87e7061513b8f9f951914a57f28c68426 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 2 Aug 2024 12:47:47 +0200 Subject: [PATCH 09/13] add tests --- Lib/test/test_clinic.py | 116 +++++++++++++++++++++-- Modules/_testclinic.c | 52 ++++++++++- Modules/clinic/_testclinic.c.h | 163 ++++++++++++++++++++++++++++++--- 3 files changed, 307 insertions(+), 24 deletions(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index c286f140b21aa1..46eea8833ac073 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -2,6 +2,7 @@ # Copyright 2012-2013 by Larry Hastings. # Licensed to the PSF under a contributor agreement. +from itertools import combinations, permutations from functools import partial from test import support, test_tools from test.support import os_helper @@ -2913,6 +2914,23 @@ def check_depr_kwd(self, pnames, fn, *args, name=None, **kwds): ) self.check_depr(regex, fn, *args, **kwds) + def check_unordered_calls( + self, + c_actual_func, py_expect_func, + base_args, more_args, + base_kwargs, more_kwargs + ): + for r in range(len(more_kwargs) + 1): + for morekwds in map(dict, combinations(more_kwargs.items(), r=r)): + for kwargs in permutations((base_kwargs | morekwds).items()): + kwargs = dict(kwargs) + for s in range(1 + len(more_args)): + args = tuple(base_args) + more_args[:s] + with self.subTest(args=args, kwargs=kwargs): + actual = c_actual_func(*args, **kwargs) + expect = py_expect_func(*args, **kwargs) + self.assertEqual(actual, expect) + def test_objects_converter(self): with self.assertRaises(TypeError): ac_tester.objects_converter() @@ -3333,6 +3351,18 @@ def test_vararg(self): ac_tester.vararg(1, b=2) self.assertEqual(ac_tester.vararg(1, 2, 3, 4), (1, (2, 3, 4))) + def test_vararg_with_multiple_pos(self): + # vararg_with_multiple_pos(a, b, *args) + func = ac_tester.vararg_with_multiple_pos + with self.assertRaises(TypeError): + func() + + self.assertTupleEqual(func(1, 2), (1, 2, ())) + self.assertTupleEqual(func(1, b=2), (1, 2, ())) + self.assertTupleEqual(func(a=1, b=2), (1, 2, ())) + self.assertTupleEqual(func(1, 2, 'c'), (1, 2, ('c',))) + self.assertTupleEqual(func(1, 2, 'c', 'd'), (1, 2, ('c', 'd'))) + def test_vararg_with_default(self): with self.assertRaises(TypeError): ac_tester.vararg_with_default() @@ -3342,15 +3372,85 @@ def test_vararg_with_default(self): self.assertEqual(ac_tester.vararg_with_default(a=1, b=False), (1, (), False)) self.assertEqual(ac_tester.vararg_with_default(b=False, a=1), (1, (), False)) - def test_vararg_with_multiple_defaults(self): - func = ac_tester.vararg_with_multiple_defaults + def test_vararg_with_more_defaults(self): + # vararg_with_more_defaults(a, *args, kw1=False, kw2=False) with self.assertRaises(TypeError): - func() - self.assertEqual(func(1, kw1=True), (1, (), True, False)) - self.assertEqual(func(1, 2, 3, 4), (1, (2, 3, 4), False, False)) - self.assertEqual(func(1, 2, 3, 4, kw1=True), (1, (2, 3, 4), True, False)) - self.assertEqual(func(a=1, kw1=True), (1, (), True, False)) - self.assertEqual(func(kw1=True, a=1), (1, (), True, False)) + ac_tester.vararg_with_more_defaults() + + check = self.assertTupleEqual + fn = ac_tester.vararg_with_more_defaults + + check(fn(1), (1, (), False, False)) + + check(fn(1, 'x'), (1, ('x',), False, False)) + check(fn(1, 'x', kw1=True), (1, ('x',), True, False)) + check(fn(1, 'x', kw2=True), (1, ('x',), False, True)) + check(fn(1, 'x', kw1=True, kw2=True), (1, ('x',), True, True)) + check(fn(1, 'x', kw2=True, kw1=True), (1, ('x',), True, True)) + + check(fn(1, 'x', 'y'), (1, ('x', 'y'), False, False)) + check(fn(1, 'x', 'y', kw1=True), (1, ('x', 'y'), True, False)) + check(fn(1, 'x', 'y', kw2=True), (1, ('x', 'y'), False, True)) + check(fn(1, 'x', 'y', kw1=True, kw2=True), (1, ('x', 'y'), True, True)) + check(fn(1, 'x', 'y', kw2=True, kw1=True), (1, ('x', 'y'), True, True)) + + check(fn(1, kw1=True), (1, (), True, False)) + check(fn(1, kw2=True), (1, (), False, True)) + check(fn(1, kw1=True, kw2=True), (1, (), True, True)) + check(fn(1, kw2=True, kw1=True), (1, (), True, True)) + + check(fn(a=1), (1, (), False, False)) + check(fn(a=1, kw1=True), (1, (), True, False)) + check(fn(kw1=True, a=1), (1, (), True, False)) + check(fn(a=1, kw2=True), (1, (), False, True)) + check(fn(kw2=True, a=1), (1, (), False, True)) + + for kwds in permutations(dict(a=1, kw1=True, kw2=True).items()): + check(fn(**dict(kwds)), (1, (), True, True)) + + def test_vararg_with_more_defaults_and_pos(self): + # vararg_with_more_defaults_and_pos(a, b, *args, kw1=False, kw2=False) + with self.assertRaises(TypeError): + ac_tester.vararg_with_more_defaults_and_pos() + + check = self.assertTupleEqual + fn = ac_tester.vararg_with_more_defaults_and_pos + + check(fn(1, 2), (1, 2, (), False, False)) + + check(fn(1, 2, kw1=True), (1, 2, (), True, False)) + check(fn(1, 2, kw2=True), (1, 2, (), False, True)) + check(fn(1, 2, kw1=True, kw2=True), (1, 2, (), True, True)) + check(fn(1, 2, kw2=True, kw1=True), (1, 2, (), True, True)) + + check(fn(1, b=1), (1, 1, (), False, False)) + check(fn(1, b=1, kw1=True), (1, 1, (), True, False)) + check(fn(1, kw1=True, b=1), (1, 1, (), True, False)) + check(fn(1, b=1, kw2=True), (1, 1, (), False, True)) + check(fn(1, kw2=True, b=1), (1, 1, (), False, True)) + + check(fn(1, 2, 'x'), (1, 2, ('x',), False, False)) + check(fn(1, 2, 'x', 'y'), (1, 2, ('x', 'y'), False, False)) + check(fn(1, 2, 'x', kw1=True), (1, 2, ('x',), True, False)) + check(fn(1, 2, 'x', 'y', kw1=True), (1, 2, ('x', 'y'), True, False)) + check(fn(1, 2, 'x', kw2=True), (1, 2, ('x',), False, True)) + check(fn(1, 2, 'x', 'y', kw2=True), (1, 2, ('x', 'y'), False, True)) + check(fn(1, 2, 'x', kw1=True, kw2=True), (1, 2, ('x',), True, True)) + check(fn(1, 2, 'x', 'y', kw1=True, kw2=True), (1, 2, ('x', 'y'), True, True)) + check(fn(1, 2, 'x', kw2=True, kw1=True), (1, 2, ('x',), True, True)) + check(fn(1, 2, 'x', 'y', kw2=True, kw1=True), (1, 2, ('x', 'y'), True, True)) + + check(fn(a=1, b=2), (1, 2, (), False, False)) + check(fn(b=2, a=1), (1, 2, (), False, False)) + + for kwds in permutations(dict(a=1, b=2, kw1=True).items()): + check(fn(**dict(kwds)), (1, 2, (), True, False)) + for kwds in permutations(dict(a=1, b=2, kw2=True).items()): + check(fn(**dict(kwds)), (1, 2, (), False, True)) + for kwds in permutations(dict(b=2, kw1=True, kw2=True).items()): + check(fn(1, **dict(kwds)), (1, 2, (), True, True)) + for kwds in permutations(dict(a=1, b=2, kw1=True, kw2=True).items()): + check(fn(**dict(kwds)), (1, 2, (), True, True)) def test_vararg_with_only_defaults(self): self.assertEqual(ac_tester.vararg_with_only_defaults(), ((), None)) diff --git a/Modules/_testclinic.c b/Modules/_testclinic.c index a15cf4dfefd041..454947191d37b4 100644 --- a/Modules/_testclinic.c +++ b/Modules/_testclinic.c @@ -1014,6 +1014,24 @@ vararg_impl(PyObject *module, PyObject *a, PyObject *args) } +/*[clinic input] +vararg_with_multiple_pos + + a: object + b: object + *args: object + +[clinic start generated code]*/ + +static PyObject * +vararg_with_multiple_pos_impl(PyObject *module, PyObject *a, PyObject *b, + PyObject *args) +/*[clinic end generated code: output=7fe8cbc4165d8592 input=49b49a877d24f459]*/ +{ + return pack_arguments_newref(3, a, b, args); +} + + /*[clinic input] vararg_with_default @@ -1034,7 +1052,7 @@ vararg_with_default_impl(PyObject *module, PyObject *a, PyObject *args, /*[clinic input] -vararg_with_multiple_defaults +vararg_with_more_defaults a: object *args: object @@ -1044,15 +1062,37 @@ vararg_with_multiple_defaults [clinic start generated code]*/ static PyObject * -vararg_with_multiple_defaults_impl(PyObject *module, PyObject *a, - PyObject *args, int kw1, int kw2) -/*[clinic end generated code: output=ae7ee8d22dfc7fbf input=534d91e23e6d360b]*/ +vararg_with_more_defaults_impl(PyObject *module, PyObject *a, PyObject *args, + int kw1, int kw2) +/*[clinic end generated code: output=efb60dafc084a301 input=d84a0e8641b30838]*/ { PyObject *obj_kw1 = kw1 ? Py_True : Py_False; PyObject *obj_kw2 = kw2 ? Py_True : Py_False; return pack_arguments_newref(4, a, args, obj_kw1, obj_kw2); } +/*[clinic input] +vararg_with_more_defaults_and_pos + + a: object + b: object + *args: object + kw1: bool = False + kw2: bool = False + +[clinic start generated code]*/ + +static PyObject * +vararg_with_more_defaults_and_pos_impl(PyObject *module, PyObject *a, + PyObject *b, PyObject *args, int kw1, + int kw2) +/*[clinic end generated code: output=9828348aee06bd21 input=447044ec4b59bb45]*/ + +{ + PyObject *obj_kw1 = kw1 ? Py_True : Py_False; + PyObject *obj_kw2 = kw2 ? Py_True : Py_False; + return pack_arguments_newref(5, a, b, args, obj_kw1, obj_kw2); +} /*[clinic input] vararg_with_only_defaults @@ -1927,8 +1967,10 @@ static PyMethodDef tester_methods[] = { POSONLY_VARARG_METHODDEF VARARG_AND_POSONLY_METHODDEF VARARG_METHODDEF + VARARG_WITH_MULTIPLE_POS_METHODDEF VARARG_WITH_DEFAULT_METHODDEF - VARARG_WITH_MULTIPLE_DEFAULTS_METHODDEF + VARARG_WITH_MORE_DEFAULTS_METHODDEF + VARARG_WITH_MORE_DEFAULTS_AND_POS_METHODDEF VARARG_WITH_ONLY_DEFAULTS_METHODDEF GH_32092_OOB_METHODDEF GH_32092_KW_PASS_METHODDEF diff --git a/Modules/clinic/_testclinic.c.h b/Modules/clinic/_testclinic.c.h index d671081c05d138..acc506e4cde4a5 100644 --- a/Modules/clinic/_testclinic.c.h +++ b/Modules/clinic/_testclinic.c.h @@ -2674,6 +2674,66 @@ vararg(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwna return return_value; } +PyDoc_STRVAR(vararg_with_multiple_pos__doc__, +"vararg_with_multiple_pos($module, /, a, b, *args)\n" +"--\n" +"\n"); + +#define VARARG_WITH_MULTIPLE_POS_METHODDEF \ + {"vararg_with_multiple_pos", _PyCFunction_CAST(vararg_with_multiple_pos), METH_FASTCALL|METH_KEYWORDS, vararg_with_multiple_pos__doc__}, + +static PyObject * +vararg_with_multiple_pos_impl(PyObject *module, PyObject *a, PyObject *b, + PyObject *args); + +static PyObject * +vararg_with_multiple_pos(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_LATIN1_CHR('a'), _Py_LATIN1_CHR('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[] = {"a", "b", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "vararg_with_multiple_pos", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[3]; + PyObject *a; + PyObject *b; + PyObject *__clinic_args = NULL; + + args = _PyArg_UnpackKeywordsWithVararg(args, nargs, NULL, kwnames, &_parser, 2, 2, 0, 2, argsbuf); + if (!args) { + goto exit; + } + a = args[0]; + b = args[1]; + __clinic_args = args[2]; + return_value = vararg_with_multiple_pos_impl(module, a, b, __clinic_args); + +exit: + Py_XDECREF(__clinic_args); + return return_value; +} + PyDoc_STRVAR(vararg_with_default__doc__, "vararg_with_default($module, /, a, *args, b=False)\n" "--\n" @@ -2742,21 +2802,20 @@ vararg_with_default(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P return return_value; } -PyDoc_STRVAR(vararg_with_multiple_defaults__doc__, -"vararg_with_multiple_defaults($module, /, a, *args, kw1=False,\n" -" kw2=False)\n" +PyDoc_STRVAR(vararg_with_more_defaults__doc__, +"vararg_with_more_defaults($module, /, a, *args, kw1=False, kw2=False)\n" "--\n" "\n"); -#define VARARG_WITH_MULTIPLE_DEFAULTS_METHODDEF \ - {"vararg_with_multiple_defaults", _PyCFunction_CAST(vararg_with_multiple_defaults), METH_FASTCALL|METH_KEYWORDS, vararg_with_multiple_defaults__doc__}, +#define VARARG_WITH_MORE_DEFAULTS_METHODDEF \ + {"vararg_with_more_defaults", _PyCFunction_CAST(vararg_with_more_defaults), METH_FASTCALL|METH_KEYWORDS, vararg_with_more_defaults__doc__}, static PyObject * -vararg_with_multiple_defaults_impl(PyObject *module, PyObject *a, - PyObject *args, int kw1, int kw2); +vararg_with_more_defaults_impl(PyObject *module, PyObject *a, PyObject *args, + int kw1, int kw2); static PyObject * -vararg_with_multiple_defaults(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +vararg_with_more_defaults(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) @@ -2780,7 +2839,7 @@ vararg_with_multiple_defaults(PyObject *module, PyObject *const *args, Py_ssize_ static const char * const _keywords[] = {"a", "kw1", "kw2", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, - .fname = "vararg_with_multiple_defaults", + .fname = "vararg_with_more_defaults", .kwtuple = KWTUPLE, }; #undef KWTUPLE @@ -2814,7 +2873,89 @@ vararg_with_multiple_defaults(PyObject *module, PyObject *const *args, Py_ssize_ goto exit; } skip_optional_kwonly: - return_value = vararg_with_multiple_defaults_impl(module, a, __clinic_args, kw1, kw2); + return_value = vararg_with_more_defaults_impl(module, a, __clinic_args, kw1, kw2); + +exit: + Py_XDECREF(__clinic_args); + return return_value; +} + +PyDoc_STRVAR(vararg_with_more_defaults_and_pos__doc__, +"vararg_with_more_defaults_and_pos($module, /, a, b, *args, kw1=False,\n" +" kw2=False)\n" +"--\n" +"\n"); + +#define VARARG_WITH_MORE_DEFAULTS_AND_POS_METHODDEF \ + {"vararg_with_more_defaults_and_pos", _PyCFunction_CAST(vararg_with_more_defaults_and_pos), METH_FASTCALL|METH_KEYWORDS, vararg_with_more_defaults_and_pos__doc__}, + +static PyObject * +vararg_with_more_defaults_and_pos_impl(PyObject *module, PyObject *a, + PyObject *b, PyObject *args, int kw1, + int kw2); + +static PyObject * +vararg_with_more_defaults_and_pos(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_LATIN1_CHR('a'), _Py_LATIN1_CHR('b'), &_Py_ID(kw1), &_Py_ID(kw2), }, + }; + #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", "kw1", "kw2", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "vararg_with_more_defaults_and_pos", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[5]; + Py_ssize_t noptargs = Py_MIN(nargs, 2) + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 2; + PyObject *a; + PyObject *b; + PyObject *__clinic_args = NULL; + int kw1 = 0; + int kw2 = 0; + + args = _PyArg_UnpackKeywordsWithVararg(args, nargs, NULL, kwnames, &_parser, 2, 2, 0, 2, argsbuf); + if (!args) { + goto exit; + } + a = args[0]; + b = args[1]; + __clinic_args = args[2]; + if (!noptargs) { + goto skip_optional_kwonly; + } + if (args[3]) { + kw1 = PyObject_IsTrue(args[3]); + if (kw1 < 0) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_kwonly; + } + } + kw2 = PyObject_IsTrue(args[4]); + if (kw2 < 0) { + goto exit; + } +skip_optional_kwonly: + return_value = vararg_with_more_defaults_and_pos_impl(module, a, b, __clinic_args, kw1, kw2); exit: Py_XDECREF(__clinic_args); @@ -3497,4 +3638,4 @@ _testclinic_TestClass_get_defining_class_arg(PyObject *self, PyTypeObject *cls, exit: return return_value; } -/*[clinic end generated code: output=dd2556e39e89b5f6 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=fbc17fb342e02e52 input=a9049054013a1b77]*/ From 659d8233a1ba81a9e4b12850c9b5e3b094d1dbae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 2 Aug 2024 12:58:03 +0200 Subject: [PATCH 10/13] remove unused code --- Lib/test/test_clinic.py | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 46eea8833ac073..35b8ac6c148534 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -2,8 +2,8 @@ # Copyright 2012-2013 by Larry Hastings. # Licensed to the PSF under a contributor agreement. -from itertools import combinations, permutations from functools import partial +from itertools import permutations from test import support, test_tools from test.support import os_helper from test.support.os_helper import TESTFN, unlink, rmtree @@ -2914,23 +2914,6 @@ def check_depr_kwd(self, pnames, fn, *args, name=None, **kwds): ) self.check_depr(regex, fn, *args, **kwds) - def check_unordered_calls( - self, - c_actual_func, py_expect_func, - base_args, more_args, - base_kwargs, more_kwargs - ): - for r in range(len(more_kwargs) + 1): - for morekwds in map(dict, combinations(more_kwargs.items(), r=r)): - for kwargs in permutations((base_kwargs | morekwds).items()): - kwargs = dict(kwargs) - for s in range(1 + len(more_args)): - args = tuple(base_args) + more_args[:s] - with self.subTest(args=args, kwargs=kwargs): - actual = c_actual_func(*args, **kwargs) - expect = py_expect_func(*args, **kwargs) - self.assertEqual(actual, expect) - def test_objects_converter(self): with self.assertRaises(TypeError): ac_tester.objects_converter() From b70263a6fdd70ece96955d9dcfea759d349162b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 2 Aug 2024 13:00:08 +0200 Subject: [PATCH 11/13] fixup ws --- Modules/_testclinic.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Modules/_testclinic.c b/Modules/_testclinic.c index 454947191d37b4..6e94027a977934 100644 --- a/Modules/_testclinic.c +++ b/Modules/_testclinic.c @@ -1071,6 +1071,7 @@ vararg_with_more_defaults_impl(PyObject *module, PyObject *a, PyObject *args, return pack_arguments_newref(4, a, args, obj_kw1, obj_kw2); } + /*[clinic input] vararg_with_more_defaults_and_pos @@ -1094,6 +1095,7 @@ vararg_with_more_defaults_and_pos_impl(PyObject *module, PyObject *a, return pack_arguments_newref(5, a, b, args, obj_kw1, obj_kw2); } + /*[clinic input] vararg_with_only_defaults From a59ff6643df6f895dc346219cc64a98d4837ca01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 4 Aug 2024 10:04:21 +0200 Subject: [PATCH 12/13] simplify buffer's creation --- Python/getargs.c | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/Python/getargs.c b/Python/getargs.c index 4a8ba065f4bf8a..897bc072a32890 100644 --- a/Python/getargs.c +++ b/Python/getargs.c @@ -2546,18 +2546,12 @@ _PyArg_UnpackKeywordsWithVararg(PyObject *const *args, Py_ssize_t nargs, if (varargssize < 0) { varargssize = 0; } - buf[vararg] = PyTuple_New(varargssize); - if (!buf[vararg]) { - return NULL; - } - for (i = 0; i < vararg; ++i) { - // copy the first arguments until the 'vararg' index - buf[i] = args[i]; - } - for (i = vararg; i < nargs; ++i) { - // remaining arguments are all considered as variadic ones - PyTuple_SET_ITEM(buf[vararg], i - vararg, Py_NewRef(args[i])); - } + + // copy the first arguments until the 'vararg' index + memcpy(buf, args, vararg * sizeof(PyObject *)); + // remaining arguments are all considered as variadic ones + buf[vararg] = _PyTuple_FromArray(&args[vararg], varargssize); + // We need to place the keyword arguments correctly in "buf". // // The buffer is always of the following form: From bbd353c006232585b91d7e708322c53a779eff32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 4 Aug 2024 10:06:15 +0200 Subject: [PATCH 13/13] Use multi-line comments to have collapsible sections --- Python/getargs.c | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/Python/getargs.c b/Python/getargs.c index 897bc072a32890..62e3a84d6eb201 100644 --- a/Python/getargs.c +++ b/Python/getargs.c @@ -2536,12 +2536,14 @@ _PyArg_UnpackKeywordsWithVararg(PyObject *const *args, Py_ssize_t nargs, return NULL; } - // Create the tuple of variadic arguments and place it at 'buf[vararg]'. - // The function has at most 'maxpos' positional arguments, possibly with - // default values or not and possibly positional-only. Since 'args' is - // an array of 'nargs' arguments representing the positional arguments - // of the call, there are only 'nargs - maxpos' arguments that will - // be considered as variadic and gathered into a single tuple. + /* + * Create the tuple of variadic arguments and place it at 'buf[vararg]'. + * The function has at most 'maxpos' positional arguments, possibly with + * default values or not and possibly positional-only. Since 'args' is + * an array of 'nargs' arguments representing the positional arguments + * of the call, there are only 'nargs - maxpos' arguments that will + * be considered as variadic and gathered into a single tuple. + */ varargssize = nargs - maxpos; if (varargssize < 0) { varargssize = 0; @@ -2552,15 +2554,17 @@ _PyArg_UnpackKeywordsWithVararg(PyObject *const *args, Py_ssize_t nargs, // remaining arguments are all considered as variadic ones buf[vararg] = _PyTuple_FromArray(&args[vararg], varargssize); - // We need to place the keyword arguments correctly in "buf". - // - // The buffer is always of the following form: - // - // buf = [x1, ..., xN] (*args) [k1, ..., kM] - // - // where x1, ..., xN are the positional arguments, '*args' is a tuple - // containing the variadic arguments (it will be untouched now) and - // k1, ..., kM are the keyword arguments that are not positionals. + /* + * We need to place the keyword arguments correctly in "buf". + * + * The buffer is always of the following form: + * + * buf = [x1, ..., xN] (*args) [k1, ..., kM] + * + * where x1, ..., xN are the positional arguments, '*args' is a tuple + * containing the variadic arguments (it will be untouched now) and + * k1, ..., kM are the keyword arguments that are not positionals. + */ for (i = Py_MAX((int)nargs, posonly) - Py_SAFE_DOWNCAST(varargssize, Py_ssize_t, int); i < maxargs; i++) { PyObject *current_arg; if (nkwargs) {