Skip to content

Commit 58e8cf2

Browse files
gh-121332: Make AST node constructor check _attributes instead of hardcoding attributes (#121334)
1 parent 44937d1 commit 58e8cf2

File tree

4 files changed

+67
-37
lines changed

4 files changed

+67
-37
lines changed

Lib/test/test_ast.py

+13-9
Original file line numberDiff line numberDiff line change
@@ -1386,15 +1386,7 @@ class MyNode(ast.AST):
13861386
self.assertEqual(node.y, 1)
13871387

13881388
y = object()
1389-
# custom attributes are currently not supported and raise a warning
1390-
# because the allowed attributes are hard-coded !
1391-
msg = (
1392-
"MyNode.__init__ got an unexpected keyword argument 'y'. "
1393-
"Support for arbitrary keyword arguments is deprecated and "
1394-
"will be removed in Python 3.15"
1395-
)
1396-
with self.assertWarnsRegex(DeprecationWarning, re.escape(msg)):
1397-
repl = copy.replace(node, y=y)
1389+
repl = copy.replace(node, y=y)
13981390
# assert that there is no side-effect
13991391
self.assertEqual(node.x, 0)
14001392
self.assertEqual(node.y, 1)
@@ -3250,6 +3242,18 @@ class FieldsAndTypes(ast.AST):
32503242
obj = FieldsAndTypes(a=1)
32513243
self.assertEqual(obj.a, 1)
32523244

3245+
def test_custom_attributes(self):
3246+
class MyAttrs(ast.AST):
3247+
_attributes = ("a", "b")
3248+
3249+
obj = MyAttrs(a=1, b=2)
3250+
self.assertEqual(obj.a, 1)
3251+
self.assertEqual(obj.b, 2)
3252+
3253+
with self.assertWarnsRegex(DeprecationWarning,
3254+
r"MyAttrs.__init__ got an unexpected keyword argument 'c'."):
3255+
obj = MyAttrs(c=3)
3256+
32533257
def test_fields_and_types_no_default(self):
32543258
class FieldsAndTypesNoDefault(ast.AST):
32553259
_fields = ('a',)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix constructor of :mod:`ast` nodes with custom ``_attributes``. Previously,
2+
passing custom attributes would raise a :py:exc:`DeprecationWarning`. Passing
3+
arguments to the constructor that are not in ``_fields`` or ``_attributes``
4+
remains deprecated. Patch by Jelle Zijlstra.

Parser/asdl_c.py

+25-14
Original file line numberDiff line numberDiff line change
@@ -880,7 +880,7 @@ def visitModule(self, mod):
880880
881881
Py_ssize_t i, numfields = 0;
882882
int res = -1;
883-
PyObject *key, *value, *fields, *remaining_fields = NULL;
883+
PyObject *key, *value, *fields, *attributes = NULL, *remaining_fields = NULL;
884884
if (PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self), state->_fields, &fields) < 0) {
885885
goto cleanup;
886886
}
@@ -947,22 +947,32 @@ def visitModule(self, mod):
947947
goto cleanup;
948948
}
949949
}
950-
else if (
951-
PyUnicode_CompareWithASCIIString(key, "lineno") != 0 &&
952-
PyUnicode_CompareWithASCIIString(key, "col_offset") != 0 &&
953-
PyUnicode_CompareWithASCIIString(key, "end_lineno") != 0 &&
954-
PyUnicode_CompareWithASCIIString(key, "end_col_offset") != 0
955-
) {
956-
if (PyErr_WarnFormat(
957-
PyExc_DeprecationWarning, 1,
958-
"%.400s.__init__ got an unexpected keyword argument '%U'. "
959-
"Support for arbitrary keyword arguments is deprecated "
960-
"and will be removed in Python 3.15.",
961-
Py_TYPE(self)->tp_name, key
962-
) < 0) {
950+
else {
951+
// Lazily initialize "attributes"
952+
if (attributes == NULL) {
953+
attributes = PyObject_GetAttr((PyObject*)Py_TYPE(self), state->_attributes);
954+
if (attributes == NULL) {
955+
res = -1;
956+
goto cleanup;
957+
}
958+
}
959+
int contains = PySequence_Contains(attributes, key);
960+
if (contains == -1) {
963961
res = -1;
964962
goto cleanup;
965963
}
964+
else if (contains == 0) {
965+
if (PyErr_WarnFormat(
966+
PyExc_DeprecationWarning, 1,
967+
"%.400s.__init__ got an unexpected keyword argument '%U'. "
968+
"Support for arbitrary keyword arguments is deprecated "
969+
"and will be removed in Python 3.15.",
970+
Py_TYPE(self)->tp_name, key
971+
) < 0) {
972+
res = -1;
973+
goto cleanup;
974+
}
975+
}
966976
}
967977
res = PyObject_SetAttr(self, key, value);
968978
if (res < 0) {
@@ -1045,6 +1055,7 @@ def visitModule(self, mod):
10451055
Py_DECREF(field_types);
10461056
}
10471057
cleanup:
1058+
Py_XDECREF(attributes);
10481059
Py_XDECREF(fields);
10491060
Py_XDECREF(remaining_fields);
10501061
return res;

Python/Python-ast.c

+25-14
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)