Handle generic jsinterop classes as type arguments.

Change-Id: I89d8a77d632cf6d4f8adffa84f042a1c03a79f26
Reviewed-on: https://dart-review.googlesource.com/56665
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/compiler/lib/src/js_backend/runtime_types.dart b/pkg/compiler/lib/src/js_backend/runtime_types.dart
index 1281eb9..743ef5c 100644
--- a/pkg/compiler/lib/src/js_backend/runtime_types.dart
+++ b/pkg/compiler/lib/src/js_backend/runtime_types.dart
@@ -2146,12 +2146,21 @@
 
   jsAst.Expression visitInterfaceType(InterfaceType type, Emitter emitter) {
     jsAst.Expression name = getJavaScriptClassName(type.element, emitter);
-    if (_nativeData.isJsInteropClass(type.element)) {
-      return getJsInteropTypeArguments(type.typeArguments.length, name: name);
+    jsAst.Expression result;
+    if (type.treatAsRaw) {
+      result = name;
+    } else {
+      // Visit all type arguments. This is done even for jsinterop classes to
+      // enforce the invariant that [onVariable] is called for each type
+      // variable in the type.
+      result = visitList(type.typeArguments, emitter, head: name);
+      if (_nativeData.isJsInteropClass(type.element)) {
+        // Replace type arguments of generic jsinterop classes with 'any' type.
+        result =
+            getJsInteropTypeArguments(type.typeArguments.length, name: name);
+      }
     }
-    return type.treatAsRaw
-        ? name
-        : visitList(type.typeArguments, emitter, head: name);
+    return result;
   }
 
   jsAst.Expression visitList(List<DartType> types, Emitter emitter,
diff --git a/pkg/compiler/lib/src/ssa/codegen.dart b/pkg/compiler/lib/src/ssa/codegen.dart
index 37288e6..671f01b 100644
--- a/pkg/compiler/lib/src/ssa/codegen.dart
+++ b/pkg/compiler/lib/src/ssa/codegen.dart
@@ -3092,6 +3092,7 @@
   }
 
   void visitTypeInfoExpression(HTypeInfoExpression node) {
+    DartType type = node.dartType;
     List<js.Expression> arguments = <js.Expression>[];
     for (HInstruction input in node.inputs) {
       use(input);
@@ -3103,19 +3104,19 @@
         int index = 0;
         js.Expression result = _rtiEncoder.getTypeRepresentation(
             _emitter.emitter,
-            node.dartType,
+            type,
             (TypeVariableType variable) => arguments[index++]);
         assert(
             index == node.inputs.length,
-            "Not all input is read for type ${node.dartType}: "
+            "Not all input is read for type ${type}: "
             "$index of ${node.inputs}.");
         push(result);
         return;
 
       case TypeInfoExpressionKind.INSTANCE:
         // We expect only flat types for the INSTANCE representation.
-        assert((node.dartType as InterfaceType).typeArguments.length ==
-            arguments.length);
+        assert(
+            (type as InterfaceType).typeArguments.length == arguments.length);
         _registry.registerInstantiatedClass(_commonElements.listClass);
         push(new js.ArrayInitializer(arguments)
             .withSourceInformation(node.sourceInformation));
diff --git a/tests/compiler/dart2js/rti/emission/jsinterop_generic_factory_args.dart b/tests/compiler/dart2js/rti/emission/jsinterop_generic_factory_args.dart
new file mode 100644
index 0000000..de9bc8b
--- /dev/null
+++ b/tests/compiler/dart2js/rti/emission/jsinterop_generic_factory_args.dart
@@ -0,0 +1,56 @@
+// 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.
+
+// dart2jsOptions=--strong
+
+@JS()
+library foo;
+
+/*class: global#JavaScriptObject:checks=[$asA,$isA]*/
+
+import 'package:expect/expect.dart';
+import 'package:js/js.dart';
+
+/*kernel.class: A:checkedTypeArgument,checks=[],typeArgument*/
+/*strong.class: A:checkedInstance,checkedTypeArgument,checks=[],typeArgument*/
+@JS()
+@anonymous
+class A<T> {
+  external factory A();
+}
+
+/*class: Class1:checks=[],instance*/
+class Class1<T> {
+  method() {
+    // This caused and assertion failure in codegen.
+    test1(new List<A<T>>.from([]));
+  }
+}
+
+/*class: Class2:checks=[],instance*/
+class Class2<T> {
+  method() {
+    // This caused and assertion failure in codegen.
+    test2(new List<A<T> Function()>.from([]));
+  }
+}
+
+main() {
+  new Class1<int>().method();
+  new Class1<String>().method();
+  new Class2<int>().method();
+  new Class2<String>().method();
+}
+
+test1(o) {
+  Expect.isTrue(o is List<A<int>>, "Expected $o to be List<A<int>>");
+  Expect.isTrue(o is List<A<String>>, "Expected $o to be List<A<String>>");
+}
+
+test2(o) {
+  Expect.isTrue(o is List<A<int> Function()>,
+      "Expected $o to be List<A<int> Function()>");
+  Expect.isTrue(o is List<A<String> Function()>,
+      "Expected $o to be List<A<String> Function()>");
+}