Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 98f3373

Browse files
committed
A tweaked version of Jeremy's patch #642489, to produce better error
messages about MRO conflicts. (The tweaks include correcting spelling errors, some refactoring to get the name of classic classes, and a style nit or two.)
1 parent 7da3432 commit 98f3373

1 file changed

Lines changed: 138 additions & 4 deletions

File tree

Objects/typeobject.c

Lines changed: 138 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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

666685
static 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+
678783
static int
679784
pmerge(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

Comments
 (0)