Fix deferred type literals and typedefs

Closes https://github.com/dart-lang/sdk/issues/33890

Change-Id: I3a154d4de46747f3ad905de889f14188fdfff50a
Reviewed-on: https://dart-review.googlesource.com/65442
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Sigmund Cherem <sigmund@google.com>
diff --git a/pkg/compiler/lib/src/deferred_load.dart b/pkg/compiler/lib/src/deferred_load.dart
index 8b87744..e72eca9 100644
--- a/pkg/compiler/lib/src/deferred_load.dart
+++ b/pkg/compiler/lib/src/deferred_load.dart
@@ -15,6 +15,7 @@
         ConstantValue,
         ConstructedConstantValue,
         DeferredConstantValue,
+        TypeConstantValue,
         DeferredGlobalConstantValue,
         InstantiationConstantValue;
 import 'elements/types.dart';
@@ -193,6 +194,10 @@
   Iterable<ImportEntity> memberImportsTo(
       MemberEntity element, LibraryEntity library);
 
+  /// Returns every [ImportEntity] that imports [element] into [library].
+  Iterable<ImportEntity> typedefImportsTo(
+      TypedefEntity element, LibraryEntity library);
+
   /// Collects all direct dependencies of [element].
   ///
   /// The collected dependent elements and constants are are added to
@@ -410,18 +415,9 @@
         }
       }
       constant.getDependencies().forEach((ConstantValue dependency) {
-        if (dependency is DeferredConstantValue) {
-          /// New deferred-imports are only discovered when we are visiting the
-          /// main output unit (size == 0) or code reachable from a deferred
-          /// import (size == 1). After that, we are rediscovering the
-          /// same nodes we have already seen.
-          if (newSet.length <= 1) {
-            queue.addConstant(
-                dependency, importSets.singleton(dependency.import));
-          }
-        } else {
-          _updateConstantRecursive(dependency, oldSet, newSet, queue);
-        }
+        // Constants are not allowed to refer to deferred constants, so
+        // no need to check for a deferred type literal here.
+        _updateConstantRecursive(dependency, oldSet, newSet, queue);
       });
     } else {
       assert(
@@ -505,16 +501,21 @@
     // dependencies are already visited as dependencies of the enclosing member.
   }
 
+  /// Whether to enqueue a deferred dependency.
+  ///
+  /// Due to the nature of the algorithm, some dependencies may be visited more
+  /// than once. However, we know that new deferred-imports are only discovered
+  /// when we are visiting the main output unit (size == 0) or code reachable
+  /// from a deferred import (size == 1). After that, we are rediscovering the
+  /// same nodes we have already seen.
+  _shouldAddDeferredDependency(ImportSet newSet) => newSet.length <= 1;
+
   void _processDependencies(LibraryEntity library, Dependencies dependencies,
       ImportSet oldSet, ImportSet newSet, WorkQueue queue) {
     for (ClassEntity cls in dependencies.classes) {
       Iterable<ImportEntity> imports = classImportsTo(cls, library);
       if (_isExplicitlyDeferred(imports)) {
-        /// New deferred-imports are only discovered when we are visiting the
-        /// main output unit (size == 0) or code reachable from a deferred
-        /// import (size == 1). After that, we are rediscovering the
-        /// same nodes we have already seen.
-        if (newSet.length <= 1) {
+        if (_shouldAddDeferredDependency(newSet)) {
           for (ImportEntity deferredImport in imports) {
             queue.addClass(cls, importSets.singleton(deferredImport));
           }
@@ -527,11 +528,7 @@
     for (MemberEntity member in dependencies.members) {
       Iterable<ImportEntity> imports = memberImportsTo(member, library);
       if (_isExplicitlyDeferred(imports)) {
-        /// New deferred-imports are only discovered when we are visiting the
-        /// main output unit (size == 0) or code reachable from a deferred
-        /// import (size == 1). After that, we are rediscovering the
-        /// same nodes we have already seen.
-        if (newSet.length <= 1) {
+        if (_shouldAddDeferredDependency(newSet)) {
           for (ImportEntity deferredImport in imports) {
             queue.addMember(member, importSets.singleton(deferredImport));
           }
@@ -546,14 +543,36 @@
     }
 
     for (ConstantValue dependency in dependencies.constants) {
+      // TODO(sigmund): either delete DeferredConstantValue or use it to
+      // represent deferred TypeConstantValues below.
       if (dependency is DeferredConstantValue) {
-        if (newSet.length <= 1) {
+        if (_shouldAddDeferredDependency(newSet)) {
           queue.addConstant(
               dependency, importSets.singleton(dependency.import));
         }
-      } else {
-        _updateConstantRecursive(dependency, oldSet, newSet, queue);
+        continue;
       }
+
+      if (dependency is TypeConstantValue) {
+        var type = dependency.representedType;
+        var imports = const <ImportEntity>[];
+        if (type is InterfaceType) {
+          imports = classImportsTo(type.element, library);
+        } else if (type is TypedefType) {
+          imports = typedefImportsTo(type.element, library);
+        }
+        if (_isExplicitlyDeferred(imports)) {
+          if (_shouldAddDeferredDependency(newSet)) {
+            for (ImportEntity deferredImport in imports) {
+              queue.addConstant(
+                  dependency, importSets.singleton(deferredImport));
+            }
+          }
+          continue;
+        }
+      }
+
+      _updateConstantRecursive(dependency, oldSet, newSet, queue);
     }
   }
 
diff --git a/pkg/compiler/lib/src/kernel/deferred_load.dart b/pkg/compiler/lib/src/kernel/deferred_load.dart
index b312027..7b0a987 100644
--- a/pkg/compiler/lib/src/kernel/deferred_load.dart
+++ b/pkg/compiler/lib/src/kernel/deferred_load.dart
@@ -48,6 +48,13 @@
   }
 
   @override
+  Iterable<ImportEntity> typedefImportsTo(
+      TypedefEntity element, LibraryEntity library) {
+    ir.Typedef node = _elementMap.getTypedefNode(element);
+    return _findImportsTo(node, node.name, node.enclosingLibrary, library);
+  }
+
+  @override
   Iterable<ImportEntity> memberImportsTo(
       Entity element, LibraryEntity library) {
     ir.Member node = _elementMap.getMemberDefinition(element).node;
diff --git a/pkg/compiler/lib/src/kernel/element_map.dart b/pkg/compiler/lib/src/kernel/element_map.dart
index 7be52d1..23a83dd 100644
--- a/pkg/compiler/lib/src/kernel/element_map.dart
+++ b/pkg/compiler/lib/src/kernel/element_map.dart
@@ -192,6 +192,9 @@
   /// Returns the [ir.Library] corresponding to [library].
   ir.Library getLibraryNode(LibraryEntity library);
 
+  /// Returns the node that defines [typedef].
+  ir.Typedef getTypedefNode(covariant TypedefEntity typedef);
+
   /// Returns the definition information for [member].
   MemberDefinition getMemberDefinition(covariant MemberEntity member);
 
diff --git a/pkg/compiler/lib/src/kernel/element_map_impl.dart b/pkg/compiler/lib/src/kernel/element_map_impl.dart
index cdbfe78..524c487 100644
--- a/pkg/compiler/lib/src/kernel/element_map_impl.dart
+++ b/pkg/compiler/lib/src/kernel/element_map_impl.dart
@@ -844,6 +844,10 @@
     return _classes.getData(cls).definition;
   }
 
+  ir.Typedef _getTypedefNode(covariant IndexedTypedef typedef) {
+    return _typedefs.getData(typedef).node;
+  }
+
   @override
   ImportEntity getImport(ir.LibraryDependency node) {
     ir.Library library = node.parent;
@@ -1439,6 +1443,11 @@
     return _getClassDefinition(cls);
   }
 
+  @override
+  ir.Typedef getTypedefNode(TypedefEntity typedef) {
+    return _getTypedefNode(typedef);
+  }
+
   /// Returns the element type of a async/sync*/async* function.
   @override
   DartType getFunctionAsyncOrSyncStarElementType(ir.FunctionNode functionNode) {
diff --git a/tests/compiler/dart2js_extra/deferred/type_literal_lib.dart b/tests/compiler/dart2js_extra/deferred/type_literal_lib.dart
new file mode 100644
index 0000000..867c399
--- /dev/null
+++ b/tests/compiler/dart2js_extra/deferred/type_literal_lib.dart
@@ -0,0 +1,7 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+class A {}
+
+class B {}
diff --git a/tests/compiler/dart2js_extra/deferred/type_literal_test.dart b/tests/compiler/dart2js_extra/deferred/type_literal_test.dart
new file mode 100644
index 0000000..d578d8d
--- /dev/null
+++ b/tests/compiler/dart2js_extra/deferred/type_literal_test.dart
@@ -0,0 +1,21 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Regression test for Issue #33890.
+//
+// This fails if type literal constants are not deferred, but their RTI
+// representation is. This test however will not detect if we accidentally start
+// building the RTI representation in the main deferred unit (which is what was
+// happening before the bug was introduced).
+
+import 'type_literal_lib.dart' deferred as a;
+import 'package:expect/expect.dart';
+
+main() async {
+  await a.loadLibrary();
+  Expect.isFalse(confuse(a.A) == confuse(a.B));
+}
+
+@NoInline()
+confuse(x) => x;
diff --git a/tests/compiler/dart2js_extra/deferred/typedef_lib.dart b/tests/compiler/dart2js_extra/deferred/typedef_lib.dart
new file mode 100644
index 0000000..2bccab5
--- /dev/null
+++ b/tests/compiler/dart2js_extra/deferred/typedef_lib.dart
@@ -0,0 +1,10 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+class A {}
+
+class B {}
+
+typedef C(A a);
+typedef D(B a);
diff --git a/tests/compiler/dart2js_extra/deferred/typedef_test.dart b/tests/compiler/dart2js_extra/deferred/typedef_test.dart
new file mode 100644
index 0000000..1ad34a3
--- /dev/null
+++ b/tests/compiler/dart2js_extra/deferred/typedef_test.dart
@@ -0,0 +1,23 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Regression test for Issue #33890.
+//
+// This fails if type literal constants are not deferred, but their RTI
+// representation is. This test however will not detect if we accidentally start
+// building the RTI representation in the main deferred unit (which is what was
+// happening before the bug was introduced).
+
+import 'typedef_lib.dart' deferred as a;
+import 'package:expect/expect.dart';
+
+main() async {
+  await a.loadLibrary();
+  print(a.A); // unclear why without this the definition of A and B gets pulled
+  print(a.B); // into the main output unit.
+  Expect.isFalse(confuse(a.C) == confuse(a.D));
+}
+
+@NoInline()
+confuse(x) => x;