@@ -661,6 +661,25 @@ classic_mro(PyObject *cls)
661661 David A. Moon, Keith Playford, and P. Tucker Withington.
662662 (OOPSLA 1996)
663663
664+ Some notes about the rules implied by C3:
665+
666+ No duplicate bases.
667+ It isn't legal to repeat a class in a list of base classes.
668+
669+ The next three properties are the 3 constraints in "C3".
670+
671+ Local precendece order.
672+ If A precedes B in C's MRO, then A will precede B in the MRO of all
673+ subclasses of C.
674+
675+ Monotonicity.
676+ The MRO of a class must be an extension without reordering of the
677+ MRO of each of its superclasses.
678+
679+ Extended Precedence Graph (EPG).
680+ Linearization is consistent if there is a path in the EPG from
681+ each class to all its successors in the linearization. See
682+ the paper for definition of EPG.
664683 */
665684
666685static int
@@ -675,6 +694,92 @@ tail_contains(PyObject *list, int whence, PyObject *o) {
675694 return 0 ;
676695}
677696
697+ static PyObject *
698+ class_name (PyObject * cls )
699+ {
700+ PyObject * name = PyObject_GetAttrString (cls , "__name__" );
701+ if (name == NULL ) {
702+ PyErr_Clear ();
703+ Py_XDECREF (name );
704+ name = PyObject_Repr (cls );
705+ }
706+ if (name == NULL )
707+ return NULL ;
708+ if (!PyString_Check (name )) {
709+ Py_DECREF (name );
710+ return NULL ;
711+ }
712+ return name ;
713+ }
714+
715+ static int
716+ check_duplicates (PyObject * list )
717+ {
718+ int i , j , n ;
719+ /* Let's use a quadratic time algorithm,
720+ assuming that the bases lists is short.
721+ */
722+ n = PyList_GET_SIZE (list );
723+ for (i = 0 ; i < n ; i ++ ) {
724+ PyObject * o = PyList_GET_ITEM (list , i );
725+ for (j = i + 1 ; j < n ; j ++ ) {
726+ if (PyList_GET_ITEM (list , j ) == o ) {
727+ o = class_name (o );
728+ PyErr_Format (PyExc_TypeError ,
729+ "duplicate base class %s" ,
730+ o ? PyString_AS_STRING (o ) : "?" );
731+ Py_XDECREF (o );
732+ return -1 ;
733+ }
734+ }
735+ }
736+ return 0 ;
737+ }
738+
739+ /* Raise a TypeError for an MRO order disagreement.
740+
741+ It's hard to produce a good error message. In the absence of better
742+ insight into error reporting, report the classes that were candidates
743+ to be put next into the MRO. There is some conflict between the
744+ order in which they should be put in the MRO, but it's hard to
745+ diagnose what constraint can't be satisfied.
746+ */
747+
748+ static void
749+ set_mro_error (PyObject * to_merge , int * remain )
750+ {
751+ int i , n , off , to_merge_size ;
752+ char buf [1000 ];
753+ PyObject * k , * v ;
754+ PyObject * set = PyDict_New ();
755+
756+ to_merge_size = PyList_GET_SIZE (to_merge );
757+ for (i = 0 ; i < to_merge_size ; i ++ ) {
758+ PyObject * L = PyList_GET_ITEM (to_merge , i );
759+ if (remain [i ] < PyList_GET_SIZE (L )) {
760+ PyObject * c = PyList_GET_ITEM (L , remain [i ]);
761+ if (PyDict_SetItem (set , c , Py_None ) < 0 )
762+ return ;
763+ }
764+ }
765+ n = PyDict_Size (set );
766+
767+ off = PyOS_snprintf (buf , sizeof (buf ), "MRO conflict among bases" );
768+ i = 0 ;
769+ while (PyDict_Next (set , & i , & k , & v ) && off < sizeof (buf )) {
770+ PyObject * name = class_name (k );
771+ off += PyOS_snprintf (buf + off , sizeof (buf ) - off , " %s" ,
772+ name ? PyString_AS_STRING (name ) : "?" );
773+ Py_XDECREF (name );
774+ if (-- n && off + 1 < sizeof (buf )) {
775+ buf [off ++ ] = ',' ;
776+ buf [off ] = '\0' ;
777+ }
778+ }
779+ PyErr_SetString (PyExc_TypeError , buf );
780+ Py_DECREF (set );
781+ }
782+
678783static int
679784pmerge (PyObject * acc , PyObject * to_merge ) {
680785 int i , j , to_merge_size ;
@@ -683,6 +788,10 @@ pmerge(PyObject *acc, PyObject* to_merge) {
683788
684789 to_merge_size = PyList_GET_SIZE (to_merge );
685790
791+ /* remain stores an index into each sublist of to_merge.
792+ remain[i] is the index of the next base in to_merge[i]
793+ that is not included in acc.
794+ */
686795 remain = PyMem_MALLOC (SIZEOF_INT * to_merge_size );
687796 if (remain == NULL )
688797 return -1 ;
@@ -701,11 +810,19 @@ pmerge(PyObject *acc, PyObject* to_merge) {
701810 continue ;
702811 }
703812
813+ /* Choose next candidate for MRO.
814+
815+ The input sequences alone can determine the choice.
816+ If not, choose the class which appears in the MRO
817+ of the earliest direct superclass of the new class.
818+ */
819+
704820 candidate = PyList_GET_ITEM (cur_list , remain [i ]);
705821 for (j = 0 ; j < to_merge_size ; j ++ ) {
706822 PyObject * j_lst = PyList_GET_ITEM (to_merge , j );
707- if (tail_contains (j_lst , remain [j ], candidate ))
823+ if (tail_contains (j_lst , remain [j ], candidate )) {
708824 goto skip ; /* continue outer loop */
825+ }
709826 }
710827 ok = PyList_Append (acc , candidate );
711828 if (ok < 0 ) {
@@ -722,10 +839,12 @@ pmerge(PyObject *acc, PyObject* to_merge) {
722839 skip : ;
723840 }
724841
725- PyMem_FREE ( remain );
726- if ( empty_cnt == to_merge_size )
842+ if ( empty_cnt == to_merge_size ) {
843+ PyMem_FREE ( remain );
727844 return 0 ;
728- PyErr_SetString (PyExc_TypeError , "MRO order disagreement" );
845+ }
846+ set_mro_error (to_merge , remain );
847+ PyMem_FREE (remain );
729848 return -1 ;
730849}
731850
@@ -741,6 +860,15 @@ mro_implementation(PyTypeObject *type)
741860 return NULL ;
742861 }
743862
863+ /* Find a superclass linearization that honors the constraints
864+ of the explicit lists of bases and the constraints implied by
865+ each base class.
866+
867+ to_merge is a list of lists, where each list is a superclass
868+ linearization implied by a base class. The last element of
869+ to_merge is the declared list of bases.
870+ */
871+
744872 bases = type -> tp_bases ;
745873 n = PyTuple_GET_SIZE (bases );
746874
@@ -769,6 +897,12 @@ mro_implementation(PyTypeObject *type)
769897 Py_DECREF (to_merge );
770898 return NULL ;
771899 }
900+ /* This is just a basic sanity check. */
901+ if (check_duplicates (bases_aslist ) < 0 ) {
902+ Py_DECREF (to_merge );
903+ Py_DECREF (bases_aslist );
904+ return NULL ;
905+ }
772906 PyList_SET_ITEM (to_merge , n , bases_aslist );
773907
774908 result = Py_BuildValue ("[O]" , (PyObject * )type );
0 commit comments