@@ -32,6 +32,7 @@ import {
3232import { die , toPythonIdentifier } from './python/util' ;
3333import { toPythonVersionRange , toReleaseVersion } from './version-utils' ;
3434import { assertSpecIsRosettaCompatible } from '../rosetta-assembly' ;
35+ import { topologicalSort } from '../toposort' ;
3536
3637import { TargetName } from './index' ;
3738
@@ -367,7 +368,7 @@ interface ISortableType {
367368 dependsOn ( resolver : TypeResolver ) : PythonType [ ] ;
368369}
369370
370- function isSortableType ( arg : unknown ) : arg is ISortableType {
371+ function isSortableType < T > ( arg : T ) : arg is T & ISortableType {
371372 return ( arg as Partial < ISortableType > ) . dependsOn !== undefined ;
372373}
373374
@@ -450,8 +451,10 @@ abstract class BasePythonClassType implements PythonType, ISortableType {
450451 public emit ( code : CodeMaker , context : EmitContext ) {
451452 context = nestedContext ( context , this . fqn ) ;
452453
453- const classParams = this . getClassParams ( context ) ;
454- openSignature ( code , 'class' , this . pythonName , classParams ) ;
454+ this . sortBasesForMro ( ) ;
455+
456+ const baseClasses = this . getClassParams ( context ) ;
457+ openSignature ( code , 'class' , this . pythonName , baseClasses ) ;
455458
456459 this . generator . emitDocString ( code , this . apiLocation , this . docs , {
457460 documentableItem : `class-${ this . pythonName } ` ,
@@ -479,6 +482,95 @@ abstract class BasePythonClassType implements PythonType, ISortableType {
479482 }
480483 }
481484
485+ /**
486+ * Sort the this.bases array for proper Python MRO.
487+ *
488+ * If we don't do this, there is a chance that a superclass and subclass (or interface)
489+ * have the parents in different order, which leads to an MRO violation.
490+ *
491+ * See: <https://docs.python.org/3/howto/mro.html>
492+ *
493+ * The bottom line is: child classes higher up in the hierarchy first.
494+ * As an example, in the hierarchy:
495+ *
496+ * ```
497+ * ┌─────┐
498+ * │ A │
499+ * └─────┘
500+ * ▲
501+ * ├─────┐
502+ * │ ┌─────┐
503+ * │ │ C │
504+ * │ └─────┘
505+ * │ ▲
506+ * ├─────┘
507+ * ┌─────┐
508+ * │ B │
509+ * └─────┘
510+ * ```
511+ *
512+ * The order should be `class B(C, A)`, and not the other way around.
513+ *
514+ * This is yet another instance of topological sort.
515+ */
516+ protected sortBasesForMro ( ) {
517+ if ( this . bases . length <= 1 ) {
518+ return ;
519+ }
520+
521+ // Record original order
522+ const originalOrder = new Map < spec . TypeReference , number > ( ) ;
523+ for ( let i = 0 ; i < this . bases . length ; i ++ ) {
524+ originalOrder . set ( this . bases [ i ] , i ) ;
525+ }
526+
527+ // Classify into sortable and non-sortable types
528+ const sortableBases : spec . NamedTypeReference [ ] = [ ] ;
529+ const nonSortableBases : spec . TypeReference [ ] = [ ] ;
530+ for ( const baseRef of this . bases ) {
531+ if ( spec . isNamedTypeReference ( baseRef ) ) {
532+ sortableBases . push ( baseRef ) ;
533+ } else {
534+ nonSortableBases . push ( baseRef ) ;
535+ }
536+ }
537+
538+ // Toposort, in reverse (highest in the tree last)
539+ const typeSystem = this . generator . reflectAssembly . system ;
540+ const sorted = topologicalSort (
541+ sortableBases ,
542+ ( t ) => t . fqn ,
543+ ( d ) => typeBaseFqns ( typeSystem . findFqn ( d . fqn ) ) ,
544+ ) ;
545+ sorted . reverse ( ) ;
546+
547+ // Inside a trance, we keep original sort order in the source declarations.
548+ for ( const tranche of sorted ) {
549+ tranche . sort ( ( a , b ) => originalOrder . get ( a ) ! - originalOrder . get ( b ) ! ) ;
550+ }
551+
552+ // Replace original bases
553+ this . bases . splice (
554+ 0 ,
555+ this . bases . length ,
556+ ...sorted . flatMap ( ( xs ) => xs ) ,
557+ ...nonSortableBases ,
558+ ) ;
559+
560+ function typeBaseFqns ( x : reflect . Type ) : string [ ] {
561+ if ( x . isClassType ( ) ) {
562+ return [
563+ ...( x . base ? [ x . base ?. fqn ] : [ ] ) ,
564+ ...x . getInterfaces ( ) . map ( ( i ) => i . fqn ) ,
565+ ] ;
566+ }
567+ if ( x . isInterfaceType ( ) ) {
568+ return x . getInterfaces ( ) . map ( ( i ) => i . fqn ) ;
569+ }
570+ return [ ] ;
571+ }
572+ }
573+
482574 protected boundResolver ( resolver : TypeResolver ) : TypeResolver {
483575 if ( this . fqn == null ) {
484576 return resolver ;
@@ -1024,6 +1116,8 @@ abstract class BaseProperty implements PythonBase {
10241116
10251117class Interface extends BasePythonClassType {
10261118 public emit ( code : CodeMaker , context : EmitContext ) {
1119+ this . sortBasesForMro ( ) ;
1120+
10271121 context = nestedContext ( context , this . fqn ) ;
10281122 emitList ( code , '@jsii.interface(' , [ `jsii_type="${ this . fqn } "` ] , ')' ) ;
10291123
@@ -1114,6 +1208,8 @@ class Struct extends BasePythonClassType {
11141208 }
11151209
11161210 public emit ( code : CodeMaker , context : EmitContext ) {
1211+ this . sortBasesForMro ( ) ;
1212+
11171213 context = nestedContext ( context , this . fqn ) ;
11181214 const baseInterfaces = this . getClassParams ( context ) ;
11191215
0 commit comments