]> granicus.if.org Git - python/commitdiff
Merge issue 1294232 patch from 3.2
authorNick Coghlan <ncoghlan@gmail.com>
Sun, 23 Oct 2011 12:36:42 +0000 (22:36 +1000)
committerNick Coghlan <ncoghlan@gmail.com>
Sun, 23 Oct 2011 12:36:42 +0000 (22:36 +1000)
1  2 
Include/object.h
Lib/test/test_descr.py
Misc/NEWS
Objects/typeobject.c
Python/bltinmodule.c

Simple merge
index 2a9f88083b9ffd6b561ea6d80664d951f7220dca,b214996aa9f0816a57860440d5c2d22fdd86eda1..15219db5703be151860a378c3a959b7ec286287f
@@@ -625,6 -625,174 +625,174 @@@ class ClassPropertiesAndMethods(unittes
          # The most derived metaclass of D is A rather than type.
          class D(B, C):
              pass
 -        new_calls[:] = []
+         self.assertIs(A, type(D))
+         # issue1294232: correct metaclass calculation
+         new_calls = []  # to check the order of __new__ calls
+         class AMeta(type):
+             @staticmethod
+             def __new__(mcls, name, bases, ns):
+                 new_calls.append('AMeta')
+                 return super().__new__(mcls, name, bases, ns)
+             @classmethod
+             def __prepare__(mcls, name, bases):
+                 return {}
+         class BMeta(AMeta):
+             @staticmethod
+             def __new__(mcls, name, bases, ns):
+                 new_calls.append('BMeta')
+                 return super().__new__(mcls, name, bases, ns)
+             @classmethod
+             def __prepare__(mcls, name, bases):
+                 ns = super().__prepare__(name, bases)
+                 ns['BMeta_was_here'] = True
+                 return ns
+         class A(metaclass=AMeta):
+             pass
+         self.assertEqual(['AMeta'], new_calls)
 -        new_calls[:] = []
++        new_calls.clear()
+         class B(metaclass=BMeta):
+             pass
+         # BMeta.__new__ calls AMeta.__new__ with super:
+         self.assertEqual(['BMeta', 'AMeta'], new_calls)
 -        new_calls[:] = []
++        new_calls.clear()
+         class C(A, B):
+             pass
+         # The most derived metaclass is BMeta:
+         self.assertEqual(['BMeta', 'AMeta'], new_calls)
 -        new_calls[:] = []
++        new_calls.clear()
+         # BMeta.__prepare__ should've been called:
+         self.assertIn('BMeta_was_here', C.__dict__)
+         # The order of the bases shouldn't matter:
+         class C2(B, A):
+             pass
+         self.assertEqual(['BMeta', 'AMeta'], new_calls)
 -        new_calls[:] = []
++        new_calls.clear()
+         self.assertIn('BMeta_was_here', C2.__dict__)
+         # Check correct metaclass calculation when a metaclass is declared:
+         class D(C, metaclass=type):
+             pass
+         self.assertEqual(['BMeta', 'AMeta'], new_calls)
 -        new_calls[:] = []
++        new_calls.clear()
+         self.assertIn('BMeta_was_here', D.__dict__)
+         class E(C, metaclass=AMeta):
+             pass
+         self.assertEqual(['BMeta', 'AMeta'], new_calls)
 -        prepare_calls[:] = []
++        new_calls.clear()
+         self.assertIn('BMeta_was_here', E.__dict__)
+         # Special case: the given metaclass isn't a class,
+         # so there is no metaclass calculation.
+         marker = object()
+         def func(*args, **kwargs):
+             return marker
+         class X(metaclass=func):
+             pass
+         class Y(object, metaclass=func):
+             pass
+         class Z(D, metaclass=func):
+             pass
+         self.assertIs(marker, X)
+         self.assertIs(marker, Y)
+         self.assertIs(marker, Z)
+         # The given metaclass is a class,
+         # but not a descendant of type.
+         prepare_calls = []  # to track __prepare__ calls
+         class ANotMeta:
+             def __new__(mcls, *args, **kwargs):
+                 new_calls.append('ANotMeta')
+                 return super().__new__(mcls)
+             @classmethod
+             def __prepare__(mcls, name, bases):
+                 prepare_calls.append('ANotMeta')
+                 return {}
+         class BNotMeta(ANotMeta):
+             def __new__(mcls, *args, **kwargs):
+                 new_calls.append('BNotMeta')
+                 return super().__new__(mcls)
+             @classmethod
+             def __prepare__(mcls, name, bases):
+                 prepare_calls.append('BNotMeta')
+                 return super().__prepare__(name, bases)
+         class A(metaclass=ANotMeta):
+             pass
+         self.assertIs(ANotMeta, type(A))
+         self.assertEqual(['ANotMeta'], prepare_calls)
 -        new_calls[:] = []
++        prepare_calls.clear()
+         self.assertEqual(['ANotMeta'], new_calls)
 -        prepare_calls[:] = []
++        new_calls.clear()
+         class B(metaclass=BNotMeta):
+             pass
+         self.assertIs(BNotMeta, type(B))
+         self.assertEqual(['BNotMeta', 'ANotMeta'], prepare_calls)
 -        new_calls[:] = []
++        prepare_calls.clear()
+         self.assertEqual(['BNotMeta', 'ANotMeta'], new_calls)
 -        new_calls[:] = []
++        new_calls.clear()
+         class C(A, B):
+             pass
+         self.assertIs(BNotMeta, type(C))
+         self.assertEqual(['BNotMeta', 'ANotMeta'], new_calls)
 -        prepare_calls[:] = []
++        new_calls.clear()
+         self.assertEqual(['BNotMeta', 'ANotMeta'], prepare_calls)
 -        new_calls[:] = []
++        prepare_calls.clear()
+         class C2(B, A):
+             pass
+         self.assertIs(BNotMeta, type(C2))
+         self.assertEqual(['BNotMeta', 'ANotMeta'], new_calls)
 -        prepare_calls[:] = []
++        new_calls.clear()
+         self.assertEqual(['BNotMeta', 'ANotMeta'], prepare_calls)
 -        new_calls[:] = []
++        prepare_calls.clear()
+         # This is a TypeError, because of a metaclass conflict:
+         # BNotMeta is neither a subclass, nor a superclass of type
+         with self.assertRaises(TypeError):
+             class D(C, metaclass=type):
+                 pass
+         class E(C, metaclass=ANotMeta):
+             pass
+         self.assertIs(BNotMeta, type(E))
+         self.assertEqual(['BNotMeta', 'ANotMeta'], new_calls)
 -        prepare_calls[:] = []
++        new_calls.clear()
+         self.assertEqual(['BNotMeta', 'ANotMeta'], prepare_calls)
 -        new_calls[:] = []
++        prepare_calls.clear()
+         class F(object(), C):
+             pass
+         self.assertIs(BNotMeta, type(F))
+         self.assertEqual(['BNotMeta', 'ANotMeta'], new_calls)
 -        prepare_calls[:] = []
++        new_calls.clear()
+         self.assertEqual(['BNotMeta', 'ANotMeta'], prepare_calls)
 -        new_calls[:] = []
++        prepare_calls.clear()
+         class F2(C, object()):
+             pass
+         self.assertIs(BNotMeta, type(F2))
+         self.assertEqual(['BNotMeta', 'ANotMeta'], new_calls)
 -        prepare_calls[:] = []
++        new_calls.clear()
+         self.assertEqual(['BNotMeta', 'ANotMeta'], prepare_calls)
++        prepare_calls.clear()
+         # TypeError: BNotMeta is neither a
+         # subclass, nor a superclass of int
+         with self.assertRaises(TypeError):
+             class X(C, int()):
+                 pass
+         with self.assertRaises(TypeError):
+             class X(int(), C):
+                 pass
  
      def test_module_subclasses(self):
          # Testing Python subclass of module...
diff --cc Misc/NEWS
index b9ff2fa52e2ef828cbd5c47a2993128cc3c4628e,c078623d3b4ca0a39d3fef4867af92a5b9593bc0..8e6eff79034b3eaf84295658a2f03f807a2b6340
+++ b/Misc/NEWS
@@@ -10,14 -10,10 +10,18 @@@ What's New in Python 3.3 Alpha 1
  Core and Builtins
  -----------------
  
+ - Issue #1294232: In a few cases involving metaclass inheritance, the
+   interpreter would sometimes invoke the wrong metaclass when building a new
+   class object. These cases now behave correctly. Patch by Daniel Urban.
 +- Issue #12753: Add support for Unicode name aliases and named sequences.
 +  Both :func:`unicodedata.lookup()` and '\N{...}' now resolve aliases,
 +  and :func:`unicodedata.lookup()` resolves named sequences too.
 +
 +- Issue #12170: The count(), find(), rfind(), index() and rindex() methods
 +  of bytes and bytearray objects now accept an integer between 0 and 255
 +  as their first argument.  Patch by Petri Lehtinen.
 +
  - Issue #12604: VTRACE macro expanded to no-op in _sre.c to avoid compiler
    warnings. Patch by Josh Triplett and Petri Lehtinen.
  
Simple merge
index e68f02500f5e81f1b60913bb366868269473f535,9c50b88da8dfb0db98b6248d020fbd1c418dd6a3..fd242b7408e6ca4b29b87e0a74b331ccee8eba08
@@@ -38,10 -35,10 +38,11 @@@ _Py_IDENTIFIER(flush)
  static PyObject *
  builtin___build_class__(PyObject *self, PyObject *args, PyObject *kwds)
  {
-     PyObject *func, *name, *bases, *mkw, *meta, *prep, *ns, *cell;
+     PyObject *func, *name, *bases, *mkw, *meta, *winner, *prep, *ns, *cell;
      PyObject *cls = NULL;
-     Py_ssize_t nargs;
+     Py_ssize_t nargs, nbases;
+     int isclass;
 +    _Py_IDENTIFIER(__prepare__);
  
      assert(args != NULL);
      if (!PyTuple_Check(args)) {
              meta = (PyObject *) (base0->ob_type);
          }
          Py_INCREF(meta);
+         isclass = 1;  /* meta is really a class */
+     }
++
+     if (isclass) {
+         /* meta is really a class, so check for a more derived
+            metaclass, or possible metaclass conflicts: */
+         winner = (PyObject *)_PyType_CalculateMetaclass((PyTypeObject *)meta,
+                                                         bases);
+         if (winner == NULL) {
+             Py_DECREF(meta);
+             Py_XDECREF(mkw);
+             Py_DECREF(bases);
+             return NULL;
+         }
+         if (winner != meta) {
+             Py_DECREF(meta);
+             meta = winner;
+             Py_INCREF(meta);
+         }
      }
 -    prep = PyObject_GetAttrString(meta, "__prepare__");
+     /* else: meta is not a class, so we cannot do the metaclass
+        calculation, so we will use the explicitly given object as it is */
 +    prep = _PyObject_GetAttrId(meta, &PyId___prepare__);
      if (prep == NULL) {
          if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
              PyErr_Clear();