8000 [3.14] gh-133783: Fix __replace__ on AST nodes for optional attribute… · python/cpython@856e590 · GitHub
[go: up one dir, main page]

Skip to content

Commit 856e590

Browse files
[3.14] gh-133783: Fix __replace__ on AST nodes for optional attributes (GH-133797) (#133842)
gh-133783: Fix __replace__ on AST nodes for optional attributes (GH-133797) (cherry picked from commit 7dddb4e) Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
1 parent 13c94d0 commit 856e590

File tree

4 files changed

+64
-0
lines changed

4 files changed

+64
-0
lines changed

Lib/test/test_ast/test_ast.py

Lines changed: 9 additions & 0 deletions
< 10000 td data-grid-cell-id="diff-a6d9c051aefcdf8f9606dd2ecc5a172a5716e2b38eff96097fc2b96528ed19d0-1317-1320-1" data-selected="false" role="gridcell" style="background-color:var(--diffBlob-additionNum-bgColor, var(--diffBlob-addition-bgColor-num));text-align:center" tabindex="-1" valign="top" class="focusable-grid-cell diff-line-number position-relative left-side">1320
Original file line numberDiff line numberDiff line change
@@ -1315,6 +1315,15 @@ def test_replace_reject_missing_field(self):
13151315
self.assertIs(repl.id, 'y')
13161316
self.assertIs(repl.ctx, context)
13171317

1318+
def test_replace_accept_missing_field_with_default(self):
1319+
node = ast.FunctionDef(name="foo", args=ast.arguments())
+
self.assertIs(node.returns, None)
1321+
self.assertEqual(node.decorator_list, [])
1322+
node2 = copy.replace(node, name="bar")
1323+
self.assertEqual(node2.name, "bar")
1324+
self.assertIs(node2.returns, None)
1325+
self.assertEqual(node2.decorator_list, [])
1326+
13181327
def test_replace_reject_known_custom_instance_fields_commits(self):
13191328
node = ast.parse('x').body[0].value
13201329
node.extra = extra = object() # add instance 'extra' field
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix bug with applying :func:`copy.replace` to :mod:`ast` objects. Attributes
2+
that default to ``None`` were incorrectly treated as required for manually
3+
created AST nodes.

Parser/asdl_c.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1244,6 +1244,32 @@ def visitModule(self, mod):
12441244
Py_DECREF(unused);
12451245
}
12461246
}
1247+
1248+
// Discard fields from 'expecting' that default to None
1249+
PyObject *field_types = NULL;
1250+
if (PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self),
1251+
&_Py_ID(_field_types),
1252+
&field_types) < 0)
1253+
{
1254+
Py_DECREF(expecting);
1255+
return -1;
1256+
}
1257+
if (field_types != NULL) {
1258+
Py_ssize_t pos = 0;
1259+
PyObject *field_name, *field_type;
1260+
while (PyDict_Next(field_types, &pos, &field_name, &field_type)) {
1261+
if (_PyUnion_Check(field_type)) {
1262+
// optional field
1263+
if (PySet_Discard(expecting, field_name) < 0) {
1264+
Py_DECREF(expecting);
1265+
Py_DECREF(field_types);
1266+
return -1;
1267+
}
1268+
}
1269+
}
1270+
Py_DECREF(field_types);
1271+
}
1272+
12471273
// Now 'expecting' contains the fields or attributes
12481274
// that would not be filled inside ast_type_replace().
12491275
Py_ssize_t m = PySet_GET_SIZE(expecting);

Python/Python-ast.c

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

0 commit comments

Comments
 (0)
0