self.assertEqual(e.a, 2)
self.assertEqual(C2.__subclasses__(), [D])
- try:
+ with self.assertRaisesRegex(TypeError,
+ "cannot delete '__bases__' attribute of immutable type"):
del D.__bases__
- except (TypeError, AttributeError):
- pass
- else:
- self.fail("shouldn't be able to delete .__bases__")
-
- try:
+ with self.assertRaisesRegex(TypeError, 'can only assign non-empty tuple'):
D.__bases__ = ()
- except TypeError as msg:
- if str(msg) == "a new-style class can't have only classic bases":
- self.fail("wrong error message for .__bases__ = ()")
- else:
- self.fail("shouldn't be able to set .__bases__ to ()")
-
- try:
+ with self.assertRaisesRegex(TypeError, 'can only assign tuple'):
+ D.__bases__ = [C]
+ with self.assertRaisesRegex(TypeError, 'duplicate base class'):
+ D.__bases__ = (C, C)
+ with self.assertRaisesRegex(TypeError, 'inheritance cycle'):
D.__bases__ = (D,)
- except TypeError:
- pass
- else:
- # actually, we'll have crashed by here...
- self.fail("shouldn't be able to create inheritance cycles")
+ with self.assertRaisesRegex(TypeError, 'inheritance cycle'):
+ D.__bases__ = (E,)
- try:
- D.__bases__ = (C, C)
- except TypeError:
- pass
- else:
- self.fail("didn't detect repeated base classes")
+ class A:
+ __slots__ = ()
+ def __repr__(self):
+ return '<A>'
+ class A_with_dict:
+ __slots__ = ('__dict__',)
+ def __repr__(self):
+ return '<A_with_dict>'
+ class A_with_dict_weakref:
+ def __repr__(self):
+ return '<A_with_dict_weakref>'
+ class A_with_slots:
+ __slots__ = ('x',)
+ def __repr__(self):
+ return '<A_with_slots>'
+ class A_with_slots_dict:
+ __slots__ = ('x', '__dict__')
+ def __repr__(self):
+ return '<A_with_slots_dict>'
- try:
- D.__bases__ = (E,)
- except TypeError:
- pass
- else:
- self.fail("shouldn't be able to create inheritance cycles")
+ class B:
+ __slots__ = ()
+ b = B()
+ r = repr(b)
+ with self.assertRaisesRegex(TypeError, 'layout differs'):
+ B.__bases__ = (int,)
+ with self.assertRaisesRegex(TypeError, 'layout differs'):
+ B.__bases__ = (A_with_dict_weakref,)
+ with self.assertRaisesRegex(TypeError, 'layout differs'):
+ B.__bases__ = (A_with_dict,)
+ with self.assertRaisesRegex(TypeError, 'layout differs'):
+ B.__bases__ = (A_with_slots,)
+ B.__bases__ = (A,)
+ self.assertNotHasAttr(b, '__dict__')
+ self.assertNotHasAttr(b, '__weakref__')
+ self.assertEqual(repr(b), '<A>')
+ B.__bases__ = (object,)
+ self.assertEqual(repr(b), r)
+
+ class B_with_dict_weakref:
+ pass
+ b = B_with_dict_weakref()
+ with self.assertRaisesRegex(TypeError, 'layout differs'):
+ B.__bases__ = (A_with_slots,)
+ B_with_dict_weakref.__bases__ = (A_with_dict_weakref,)
+ self.assertEqual(repr(b), '<A_with_dict_weakref>')
+ B_with_dict_weakref.__bases__ = (A_with_dict,)
+ self.assertEqual(repr(b), '<A_with_dict>')
+ B_with_dict_weakref.__bases__ = (A,)
+ self.assertEqual(repr(b), '<A>')
+ B_with_dict_weakref.__bases__ = (object,)
+
+ class B_with_slots:
+ __slots__ = ('x',)
+ b = B_with_slots()
+ with self.assertRaisesRegex(TypeError, 'layout differs'):
+ B_with_slots.__bases__ = (A_with_dict_weakref,)
+ with self.assertRaisesRegex(TypeError, 'layout differs'):
+ B_with_slots.__bases__ = (A_with_dict,)
+ B_with_slots.__bases__ = (A,)
+ self.assertEqual(repr(b), '<A>')
+
+ class B_with_slots_dict:
+ __slots__ = ('x', '__dict__')
+ b = B_with_slots_dict()
+ with self.assertRaisesRegex(TypeError, 'layout differs'):
+ B_with_slots_dict.__bases__ = (A_with_dict_weakref,)
+ B_with_slots_dict.__bases__ = (A_with_dict,)
+ self.assertEqual(repr(b), '<A_with_dict>')
+ B_with_slots_dict.__bases__ = (A,)
+ self.assertEqual(repr(b), '<A>')
+
+ class B_with_slots_dict_weakref:
+ __slots__ = ('x', '__dict__', '__weakref__')
+ b = B_with_slots_dict_weakref()
+ with self.assertRaisesRegex(TypeError, 'layout differs'):
+ B_with_slots_dict_weakref.__bases__ = (A_with_slots_dict,)
+ with self.assertRaisesRegex(TypeError, 'layout differs'):
+ B_with_slots_dict_weakref.__bases__ = (A_with_slots,)
+ B_with_slots_dict_weakref.__bases__ = (A_with_dict_weakref,)
+ self.assertEqual(repr(b), '<A_with_dict_weakref>')
+ B_with_slots_dict_weakref.__bases__ = (A_with_dict,)
+ self.assertEqual(repr(b), '<A_with_dict>')
+ B_with_slots_dict_weakref.__bases__ = (A,)
+ self.assertEqual(repr(b), '<A>')
+
+ class C_with_slots(A_with_slots):
+ __slots__ = ()
+ c = C_with_slots()
+ with self.assertRaisesRegex(TypeError, 'layout differs'):
+ C_with_slots.__bases__ = (A_with_slots_dict,)
+ with self.assertRaisesRegex(TypeError, 'layout differs'):
+ C_with_slots.__bases__ = (A_with_dict_weakref,)
+ with self.assertRaisesRegex(TypeError, 'layout differs'):
+ C_with_slots.__bases__ = (A_with_dict,)
+ with self.assertRaisesRegex(TypeError, 'layout differs'):
+ C_with_slots.__bases__ = (A,)
+ C_with_slots.__bases__ = (A_with_slots,)
+ self.assertEqual(repr(c), '<A_with_slots>')
+
+ class C_with_slots_dict(A_with_slots):
+ pass
+ c = C_with_slots_dict()
+ with self.assertRaisesRegex(TypeError, 'layout differs'):
+ C_with_slots_dict.__bases__ = (A_with_dict_weakref,)
+ with self.assertRaisesRegex(TypeError, 'layout differs'):
+ C_with_slots_dict.__bases__ = (A_with_dict,)
+ with self.assertRaisesRegex(TypeError, 'layout differs'):
+ C_with_slots_dict.__bases__ = (A,)
+ C_with_slots_dict.__bases__ = (A_with_slots_dict,)
+ self.assertEqual(repr(c), '<A_with_slots_dict>')
+ C_with_slots_dict.__bases__ = (A_with_slots,)
+ self.assertEqual(repr(c), '<A_with_slots>')
+
+ class A_int(int):
+ __slots__ = ()
+ def __repr__(self):
+ return '<A_int>'
+ class B_int(int):
+ __slots__ = ()
+ b = B_int(42)
+ with self.assertRaisesRegex(TypeError, 'layout differs'):
+ B_int.__bases__ = (object,)
+ with self.assertRaisesRegex(TypeError, 'layout differs'):
+ B_int.__bases__ = (tuple,)
+ with self.assertRaisesRegex(TypeError, 'is not an acceptable base type'):
+ B_int.__bases__ = (bool,)
+ B_int.__bases__ = (A_int,)
+ self.assertEqual(repr(b), '<A_int>')
+ B_int.__bases__ = (int,)
+ self.assertEqual(repr(b), '42')
+
+ class A_tuple(tuple):
+ __slots__ = ()
+ def __repr__(self):
+ return '<A_tuple>'
+ class B_tuple(tuple):
+ __slots__ = ()
+ b = B_tuple((1, 2))
+ with self.assertRaisesRegex(TypeError, 'layout differs'):
+ B_tuple.__bases__ = (object,)
+ with self.assertRaisesRegex(TypeError, 'layout differs'):
+ B_tuple.__bases__ = (int,)
+ B_tuple.__bases__ = (A_tuple,)
+ self.assertEqual(repr(b), '<A_tuple>')
+ B_tuple.__bases__ = (tuple,)
+ self.assertEqual(repr(b), '(1, 2)')
def test_assign_bases_many_subclasses(self):
# This is intended to check that typeobject.c:queue_slot_update() can
class D(C):
pass
- try:
+ with self.assertRaisesRegex(TypeError, 'layout differs'):
L.__bases__ = (dict,)
- except TypeError:
- pass
- else:
- self.fail("shouldn't turn list subclass into dict subclass")
- try:
+ with self.assertRaisesRegex(TypeError, 'immutable type'):
list.__bases__ = (dict,)
- except TypeError:
- pass
- else:
- self.fail("shouldn't be able to assign to list.__bases__")
- try:
+ with self.assertRaisesRegex(TypeError, 'layout differs'):
D.__bases__ = (C, list)
- except TypeError:
- pass
- else:
- self.fail("best_base calculation found wanting")
def test_unsubclassable_types(self):
with self.assertRaises(TypeError):
static PyTypeObject *find_best_base(PyObject *);
static int mro_internal(PyTypeObject *, int, PyObject **);
static int type_is_subtype_base_chain(PyTypeObject *, PyTypeObject *);
-static int compatible_for_assignment(PyTypeObject *, PyTypeObject *, const char *);
+static int compatible_for_assignment(PyTypeObject *, PyTypeObject *, const char *, int);
static int add_subclass(PyTypeObject*, PyTypeObject*);
static int add_all_subclasses(PyTypeObject *type, PyObject *bases);
static void remove_subclass(PyTypeObject *, PyTypeObject *);
if (*best_base == NULL)
return -1;
- if (!compatible_for_assignment(type->tp_base, *best_base, "__bases__")) {
+ if (!compatible_for_assignment(type, *best_base, "__bases__", 0)) {
return -1;
}
return (parent != NULL &&
child->tp_basicsize == parent->tp_basicsize &&
child->tp_itemsize == parent->tp_itemsize &&
- child->tp_dictoffset == parent->tp_dictoffset &&
- child->tp_weaklistoffset == parent->tp_weaklistoffset &&
- ((child->tp_flags & Py_TPFLAGS_HAVE_GC) ==
- (parent->tp_flags & Py_TPFLAGS_HAVE_GC)) &&
(child->tp_dealloc == subtype_dealloc ||
child->tp_dealloc == parent->tp_dealloc));
}
}
static int
-compatible_for_assignment(PyTypeObject* oldto, PyTypeObject* newto, const char* attr)
+compatible_flags(int setclass, PyTypeObject *origto, PyTypeObject *newto, unsigned long flags)
+{
+ /* For __class__ assignment, the flags should be the same.
+ For __bases__ assignment, the new base flags can only be set
+ if the original class flags are set.
+ */
+ return setclass ? (origto->tp_flags & flags) == (newto->tp_flags & flags)
+ : !(~(origto->tp_flags & flags) & (newto->tp_flags & flags));
+}
+
+static int
+compatible_for_assignment(PyTypeObject *origto, PyTypeObject *newto,
+ const char *attr, int setclass)
{
PyTypeObject *newbase, *oldbase;
+ PyTypeObject *oldto = setclass ? origto : origto->tp_base;
- if (newto->tp_free != oldto->tp_free) {
+ if (setclass && newto->tp_free != oldto->tp_free) {
PyErr_Format(PyExc_TypeError,
"%s assignment: "
"'%s' deallocator differs from '%s'",
oldto->tp_name);
return 0;
}
+ if (!compatible_flags(setclass, origto, newto,
+ Py_TPFLAGS_HAVE_GC |
+ Py_TPFLAGS_INLINE_VALUES |
+ Py_TPFLAGS_PREHEADER))
+ {
+ goto differs;
+ }
+ /* For __class__ assignment, tp_dictoffset and tp_weaklistoffset should
+ be the same for old and new types.
+ For __bases__ assignment, they can only be set in the new base
+ if they are set in the original class with the same value.
+ */
+ if ((setclass || newto->tp_dictoffset)
+ && origto->tp_dictoffset != newto->tp_dictoffset)
+ {
+ goto differs;
+ }
+ if ((setclass || newto->tp_weaklistoffset)
+ && origto->tp_weaklistoffset != newto->tp_weaklistoffset)
+ {
+ goto differs;
+ }
/*
It's tricky to tell if two arbitrary types are sufficiently compatible as
to be interchangeable; e.g., even if they have the same tp_basicsize, they
!same_slots_added(newbase, oldbase))) {
goto differs;
}
- if ((oldto->tp_flags & Py_TPFLAGS_INLINE_VALUES) !=
- ((newto->tp_flags & Py_TPFLAGS_INLINE_VALUES)))
- {
- goto differs;
- }
- /* The above does not check for the preheader */
- if ((oldto->tp_flags & Py_TPFLAGS_PREHEADER) ==
- ((newto->tp_flags & Py_TPFLAGS_PREHEADER)))
- {
- return 1;
- }
+ return 1;
differs:
PyErr_Format(PyExc_TypeError,
"%s assignment: "
return -1;
}
- if (compatible_for_assignment(oldto, newto, "__class__")) {
+ if (compatible_for_assignment(oldto, newto, "__class__", 1)) {
/* Changing the class will change the implicit dict keys,
* so we must materialize the dictionary first. */
if (oldto->tp_flags & Py_TPFLAGS_INLINE_VALUES) {