Skip to content

Commit c73914a

Browse files
authored
bpo-36290: Fix keytword collision handling in AST node constructors (GH-12382)
1 parent 59f5022 commit c73914a

File tree

5 files changed

+66
-6
lines changed

5 files changed

+66
-6
lines changed

Lib/ast.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,13 @@ def __instancecheck__(cls, inst):
524524
return type.__instancecheck__(cls, inst)
525525

526526
def _new(cls, *args, **kwargs):
527+
for key in kwargs:
528+
if key not in cls._fields:
529+
# arbitrary keyword arguments are accepted
530+
continue
531+
pos = cls._fields.index(key)
532+
if pos < len(args):
533+
raise TypeError(f"{cls.__name__} got multiple values for argument {key!r}")
527534
if cls in _const_types:
528535
return Constant(*args, **kwargs)
529536
return Constant.__new__(cls, *args, **kwargs)

Lib/test/test_ast.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,15 @@ def test_classattrs(self):
402402
self.assertRaises(TypeError, ast.Num, 1, None, 2)
403403
self.assertRaises(TypeError, ast.Num, 1, None, 2, lineno=0)
404404

405+
# Arbitrary keyword arguments are supported
406+
self.assertEqual(ast.Constant(1, foo='bar').foo, 'bar')
407+
self.assertEqual(ast.Num(1, foo='bar').foo, 'bar')
408+
409+
with self.assertRaisesRegex(TypeError, "Num got multiple values for argument 'n'"):
410+
ast.Num(1, n=2)
411+
with self.assertRaisesRegex(TypeError, "Constant got multiple values for argument 'value'"):
412+
ast.Constant(1, value=2)
413+
405414
self.assertEqual(ast.Num(42).n, 42)
406415
self.assertEqual(ast.Num(4.25).n, 4.25)
407416
self.assertEqual(ast.Num(4.25j).n, 4.25j)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
AST nodes are now raising :exc:`TypeError` on conflicting keyword arguments.
2+
Patch contributed by Rémi Lapeyre.

Parser/asdl_c.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -695,8 +695,9 @@ def visitModule(self, mod):
695695
}
696696
if (fields) {
697697
numfields = PySequence_Size(fields);
698-
if (numfields == -1)
698+
if (numfields == -1) {
699699
goto cleanup;
700+
}
700701
}
701702
702703
res = 0; /* if no error occurs, this stays 0 to the end */
@@ -717,15 +718,35 @@ def visitModule(self, mod):
717718
}
718719
res = PyObject_SetAttr(self, name, PyTuple_GET_ITEM(args, i));
719720
Py_DECREF(name);
720-
if (res < 0)
721+
if (res < 0) {
721722
goto cleanup;
723+
}
722724
}
723725
if (kw) {
724726
i = 0; /* needed by PyDict_Next */
725727
while (PyDict_Next(kw, &i, &key, &value)) {
728+
int contains = PySequence_Contains(fields, key);
729+
if (contains == -1) {
730+
res = -1;
731+
goto cleanup;
732+
} else if (contains == 1) {
733+
Py_ssize_t p = PySequence_Index(fields, key);
734+
if (p == -1) {
735+
res = -1;
736+
goto cleanup;
737+
}
738+
if (p < PyTuple_GET_SIZE(args)) {
739+
PyErr_Format(PyExc_TypeError,
740+
"%.400s got multiple values for argument '%U'",
741+
Py_TYPE(self)->tp_name, key);
742+
res = -1;
743+
goto cleanup;
744+
}
745+
}
726746
res = PyObject_SetAttr(self, key, value);
727-
if (res < 0)
747+
if (res < 0) {
728748
goto cleanup;
749+
}
729750
}
730751
}
731752
cleanup:

Python/Python-ast.c

Lines changed: 24 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)