Skip to content

Commit 173546c

Browse files
committed
don't support namedtuple helpers when unnamed fields are present
Signed-off-by: Filipe Laíns <[email protected]>
1 parent 48b5c2a commit 173546c

File tree

3 files changed

+52
-11
lines changed

3 files changed

+52
-11
lines changed

Lib/test/test_structseq.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os
22
import time
33
import unittest
4+
import _testcapi
45

56

67
class StructSeqTest(unittest.TestCase):
@@ -155,6 +156,14 @@ def test_tuple_asdict(self):
155156
'tm_isdst': 0,
156157
})
157158

159+
def test_tuple_attributes_unnamed(self):
160+
for key in ['_fields', '_field_defaults']:
161+
self.assertNotIn(key, dir(_testcapi.MixedNamedTuple))
162+
163+
def test_asdict_unnamed(self):
164+
t = _testcapi.MixedNamedTuple((0, 1))
165+
self.assertRaises(ValueError, t._asdict)
166+
158167

159168
if __name__ == "__main__":
160169
unittest.main()

Modules/_testcapimodule.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3946,13 +3946,28 @@ static struct PyModuleDef _testcapimodule = {
39463946
NULL
39473947
};
39483948

3949+
static PyStructSequence_Field MixedNamedTuple_fields[] = {
3950+
{"something", "some named entry"},
3951+
{NULL, "some unnamed entry"},
3952+
{0}
3953+
};
3954+
3955+
static PyStructSequence_Desc MixedNamedTuple_desc = {
3956+
.name = "_testcpi.MixedNamedTuple",
3957+
.doc = PyDoc_STR("Example of a named tuple with both named and unnamed fields."),
3958+
.fields = MixedNamedTuple_fields,
3959+
.n_in_sequence = 2
3960+
};
3961+
3962+
39493963
/* Per PEP 489, this module will not be converted to multi-phase initialization
39503964
*/
39513965

39523966
PyMODINIT_FUNC
39533967
PyInit__testcapi(void)
39543968
{
39553969
PyObject *m;
3970+
PyTypeObject *type;
39563971

39573972
m = PyModule_Create(&_testcapimodule);
39583973
if (m == NULL)
@@ -4047,6 +4062,13 @@ PyInit__testcapi(void)
40474062
(PyObject *) &ContainerNoGC_type) < 0)
40484063
return NULL;
40494064

4065+
MixedNamedTuple_desc.fields[1].name = PyStructSequence_UnnamedField;
4066+
type = PyStructSequence_NewType(&MixedNamedTuple_desc);
4067+
if (type == NULL)
4068+
return NULL;
4069+
if (PyModule_AddType(m, type) < 0)
4070+
return NULL;
4071+
40504072
/* Include tests from the _testcapi/ directory */
40514073
if (_PyTestCapi_Init_Vectorcall(m) < 0) {
40524074
return NULL;

Objects/structseq.c

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,12 @@ structseq_asdict(PyStructSequence* self, PyObject *Py_UNUSED(ignored))
376376
n_visible_fields = VISIBLE_SIZE(self);
377377
n_unnamed_fields = UNNAMED_FIELDS(self);
378378

379+
if (n_unnamed_fields != 0) {
380+
PyErr_SetString(PyExc_ValueError, "named tuples with both named and "
381+
"unnamed fields don't support _asdict");
382+
return NULL;
383+
}
384+
379385
dict = PyDict_New();
380386
if (!dict)
381387
return NULL;
@@ -434,7 +440,7 @@ initialize_structseq_dict(PyStructSequence_Desc *desc, PyObject* dict,
434440
SET_DICT_FROM_SIZE(real_length_key, n_members);
435441
SET_DICT_FROM_SIZE(unnamed_fields_key, n_unnamed_members);
436442

437-
// Prepare and set __match_args__ and _fields
443+
// Prepare and set __match_args__
438444
Py_ssize_t i, k;
439445
PyObject* keys = PyTuple_New(desc->n_in_sequence);
440446
if (keys == NULL) {
@@ -461,20 +467,24 @@ initialize_structseq_dict(PyStructSequence_Desc *desc, PyObject* dict,
461467
goto error;
462468
}
463469

464-
if (PyDict_SetItemString(dict, named_fields_list_key, keys) < 0) {
465-
goto error;
466-
}
470+
// Set _field and _field_defaults when we have no unnammed members
471+
if (n_unnamed_members == 0) {
472+
if (PyDict_SetItemString(dict, named_fields_list_key, keys) < 0) {
473+
goto error;
474+
}
467475

468-
// Set _fields_defaults to an empty dir, as we don't support defaults
469-
defaults = PyDict_New();
470-
if (!defaults)
471-
goto error;
476+
// Set _fields_defaults to an empty dir, as we don't support defaults
477+
defaults = PyDict_New();
478+
if (!defaults)
479+
goto error;
472480

473-
if (PyDict_SetItemString(dict, named_fields_defaults_key, defaults) < 0) {
474-
goto error;
481+
if (PyDict_SetItemString(dict, named_fields_defaults_key, defaults) < 0) {
482+
goto error;
483+
}
484+
485+
Py_DECREF(defaults);
475486
}
476487

477-
Py_DECREF(defaults);
478488
Py_DECREF(keys);
479489
return 0;
480490

0 commit comments

Comments
 (0)