[dartdevc] fix mixin super constructor calls in kernel backend

Kernel and Analyzer have slightly different rules for creating implicit
constructors, which DDC's kernel backend did not account for. This
resulted in a mismatch between which super constructor calls it thought
were necessary, vs which constructors were generated.

Both DDC backends are now less sensitive to the representations, and
the kernel backend checks for field initializers in mixin declarations,
rather than relying on the (nonexistent) implicit constructor node.

Change-Id: I01a6ae11ecf78193d7db227ba0b7adeb27d514d5
Reviewed-on: https://dart-review.googlesource.com/c/88432
Reviewed-by: Vijay Menon <vsm@google.com>
Commit-Queue: Jenny Messerly <jmesserly@google.com>
diff --git a/pkg/dev_compiler/lib/src/analyzer/code_generator.dart b/pkg/dev_compiler/lib/src/analyzer/code_generator.dart
index 15f4159..6edbe05 100644
--- a/pkg/dev_compiler/lib/src/analyzer/code_generator.dart
+++ b/pkg/dev_compiler/lib/src/analyzer/code_generator.dart
@@ -1299,7 +1299,7 @@
     }
 
     var supertype = classElem.isMixin ? types.objectType : classElem.supertype;
-    var hasUnnamedSuper = _hasUnnamedConstructor(supertype.element);
+    var hasUnnamedSuper = _hasUnnamedInheritedConstructor(supertype.element);
 
     void emitMixinConstructors(JS.Expression className, [InterfaceType mixin]) {
       var supertype = classElem.supertype;
@@ -2384,24 +2384,23 @@
         [className, _constructorName(name), args ?? []]);
   }
 
+  bool _hasUnnamedInheritedConstructor(ClassElement e) {
+    if (e == null) return false;
+    return _hasUnnamedConstructor(e) || _hasUnnamedSuperConstructor(e);
+  }
+
   bool _hasUnnamedSuperConstructor(ClassElement e) {
-    var supertype = e.supertype;
-    // Object or mixin declaration.
-    if (supertype == null) return false;
-    if (_hasUnnamedConstructor(supertype.element)) return true;
     for (var mixin in e.mixins) {
       if (_hasUnnamedConstructor(mixin.element)) return true;
     }
-    return false;
+    return _hasUnnamedInheritedConstructor(e.supertype?.element);
   }
 
   bool _hasUnnamedConstructor(ClassElement e) {
     if (e.type.isObject) return false;
     var ctor = e.unnamedConstructor;
-    if (ctor == null) return false;
-    if (!ctor.isSynthetic) return true;
-    if (e.fields.any((f) => !f.isStatic && !f.isSynthetic)) return true;
-    return _hasUnnamedSuperConstructor(e);
+    if (ctor != null && !ctor.isSynthetic) return true;
+    return e.fields.any((f) => !f.isStatic && !f.isSynthetic);
   }
 
   /// Initialize fields. They follow the sequence:
diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart
index 3a2c138..6a31922 100644
--- a/pkg/dev_compiler/lib/src/kernel/compiler.dart
+++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart
@@ -750,7 +750,7 @@
         .map((m) => hierarchy.getClassAsInstanceOf(c, m).asInterfaceType)
         .toList();
 
-    var hasUnnamedSuper = _hasUnnamedConstructor(superclass);
+    var hasUnnamedSuper = _hasUnnamedInheritedConstructor(superclass);
 
     void emitMixinConstructors(JS.Expression className, InterfaceType mixin) {
       JS.Statement mixinCtor;
@@ -1510,18 +1510,21 @@
         [className, _constructorName(name), args ?? []]);
   }
 
-  bool _hasUnnamedSuperConstructor(Class c) {
+  bool _hasUnnamedInheritedConstructor(Class c) {
     if (c == null) return false;
-    return _hasUnnamedConstructor(c.superclass) ||
-        _hasUnnamedConstructor(c.mixedInClass);
+    return _hasUnnamedConstructor(c) || _hasUnnamedSuperConstructor(c);
+  }
+
+  bool _hasUnnamedSuperConstructor(Class c) {
+    return _hasUnnamedConstructor(c.mixedInClass) ||
+        _hasUnnamedInheritedConstructor(c.superclass);
   }
 
   bool _hasUnnamedConstructor(Class c) {
     if (c == null || c == coreTypes.objectClass) return false;
     var ctor = unnamedConstructor(c);
     if (ctor != null && !ctor.isSynthetic) return true;
-    if (c.fields.any((f) => !f.isStatic)) return true;
-    return _hasUnnamedSuperConstructor(c);
+    return c.fields.any((f) => !f.isStatic);
   }
 
   /// Initialize fields. They follow the sequence:
@@ -1598,8 +1601,11 @@
   /// then we need to emit a special hidden default constructor for use by
   /// mixins.
   bool _usesMixinNew(Class mixin) {
-    return mixin.superclass?.superclass == null &&
-        mixin.constructors.every((c) => c.isExternal);
+    // TODO(jmesserly): mixin declarations don't get implicit constructor nodes,
+    // even if they have fields, so we need to ensure they're getting generated.
+    return mixin.isMixinDeclaration && _hasUnnamedConstructor(mixin) ||
+        mixin.superclass?.superclass == null &&
+            mixin.constructors.every((c) => c.isExternal);
   }
 
   JS.Statement _addConstructorToClass(
diff --git a/tests/language_2/mixin_super_test.dart b/tests/language_2/mixin_super_test.dart
index 75ad654..77f72d6 100644
--- a/tests/language_2/mixin_super_test.dart
+++ b/tests/language_2/mixin_super_test.dart
@@ -54,6 +54,25 @@
   }
 }
 
+abstract class Base {
+  static String log = '';
+  Base() {
+    log += 'Base()\n';
+  }
+}
+
+mixin Foo on Base {
+  var x = Base.log += 'Foo.x\n';
+}
+
+mixin Bar on Base {
+  var y = Base.log += 'Bar.y\n';
+}
+
+class Derived extends Base with Foo, Bar {
+  String get log => Base.log;
+}
+
 main() {
   Expect.equals(
       "S<int,String,bool>.foo\n"
@@ -77,4 +96,9 @@
       "N<bool>.foo\n"
       "MNA3<int, String, bool>.foo\n",
       MNA3<int, String, bool>().foo());
+  Expect.equals(
+      "Bar.y\n"
+      "Foo.x\n"
+      "Base()\n",
+      Derived().log);
 }