self.assertIs(repl.id, 'y')
self.assertIs(repl.ctx, context)
+ def test_replace_accept_missing_field_with_default(self):
+ node = ast.FunctionDef(name="foo", args=ast.arguments())
+ self.assertIs(node.returns, None)
+ self.assertEqual(node.decorator_list, [])
+ node2 = copy.replace(node, name="bar")
+ self.assertEqual(node2.name, "bar")
+ self.assertIs(node2.returns, None)
+ self.assertEqual(node2.decorator_list, [])
+
def test_replace_reject_known_custom_instance_fields_commits(self):
node = ast.parse('x').body[0].value
node.extra = extra = object() # add instance 'extra' field
--- /dev/null
+Fix bug with applying :func:`copy.replace` to :mod:`ast` objects. Attributes
+that default to ``None`` were incorrectly treated as required for manually
+created AST nodes.
Py_DECREF(unused);
}
}
+
+ // Discard fields from 'expecting' that default to None
+ PyObject *field_types = NULL;
+ if (PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self),
+ &_Py_ID(_field_types),
+ &field_types) < 0)
+ {
+ Py_DECREF(expecting);
+ return -1;
+ }
+ if (field_types != NULL) {
+ Py_ssize_t pos = 0;
+ PyObject *field_name, *field_type;
+ while (PyDict_Next(field_types, &pos, &field_name, &field_type)) {
+ if (_PyUnion_Check(field_type)) {
+ // optional field
+ if (PySet_Discard(expecting, field_name) < 0) {
+ Py_DECREF(expecting);
+ Py_DECREF(field_types);
+ return -1;
+ }
+ }
+ }
+ Py_DECREF(field_types);
+ }
+
// Now 'expecting' contains the fields or attributes
// that would not be filled inside ast_type_replace().
Py_ssize_t m = PySet_GET_SIZE(expecting);
Py_DECREF(unused);
}
}
+
+ // Discard fields from 'expecting' that default to None
+ PyObject *field_types = NULL;
+ if (PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self),
+ &_Py_ID(_field_types),
+ &field_types) < 0)
+ {
+ Py_DECREF(expecting);
+ return -1;
+ }
+ if (field_types != NULL) {
+ Py_ssize_t pos = 0;
+ PyObject *field_name, *field_type;
+ while (PyDict_Next(field_types, &pos, &field_name, &field_type)) {
+ if (_PyUnion_Check(field_type)) {
+ // optional field
+ if (PySet_Discard(expecting, field_name) < 0) {
+ Py_DECREF(expecting);
+ Py_DECREF(field_types);
+ return -1;
+ }
+ }
+ }
+ Py_DECREF(field_types);
+ }
+
// Now 'expecting' contains the fields or attributes
// that would not be filled inside ast_type_replace().
Py_ssize_t m = PySet_GET_SIZE(expecting);