[dart2js] Implementation of extractTypeVariables

Change-Id: I80272dade861e30440f64fe36c1bf4bb5cd2eb8a
Reviewed-on: https://dart-review.googlesource.com/54625
Commit-Queue: Stephen Adams <sra@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
diff --git a/pkg/compiler/lib/src/common_elements.dart b/pkg/compiler/lib/src/common_elements.dart
index 5675a1c..ff7d0d7 100644
--- a/pkg/compiler/lib/src/common_elements.dart
+++ b/pkg/compiler/lib/src/common_elements.dart
@@ -1129,6 +1129,10 @@
   FunctionEntity get convertRtiToRuntimeType =>
       _findHelperFunction('convertRtiToRuntimeType');
 
+  FunctionEntity _extractTypeArguments;
+  FunctionEntity get extractTypeArguments => _extractTypeArguments ??=
+      _findLibraryMember(internalLibrary, 'extractTypeArguments');
+
   FunctionEntity get toStringForNativeObject =>
       _findHelperFunction('toStringForNativeObject');
 
diff --git a/pkg/compiler/lib/src/kernel/element_map.dart b/pkg/compiler/lib/src/kernel/element_map.dart
index f00dd89..fe468dd 100644
--- a/pkg/compiler/lib/src/kernel/element_map.dart
+++ b/pkg/compiler/lib/src/kernel/element_map.dart
@@ -125,6 +125,7 @@
 /// Interface that translates between Kernel IR nodes and entities used for
 /// computing the [WorldImpact] for members.
 abstract class KernelToElementMapForImpact extends KernelToElementMap {
+  ElementEnvironment get elementEnvironment;
   NativeBasicData get nativeBasicData;
 
   /// Adds libraries in [component] to the set of libraries.
diff --git a/pkg/compiler/lib/src/ssa/builder_kernel.dart b/pkg/compiler/lib/src/ssa/builder_kernel.dart
index 1438d60..6836904 100644
--- a/pkg/compiler/lib/src/ssa/builder_kernel.dart
+++ b/pkg/compiler/lib/src/ssa/builder_kernel.dart
@@ -3203,6 +3203,13 @@
       return;
     }
     FunctionEntity function = _elementMap.getMember(target);
+
+    if (options.strongMode &&
+        function == _commonElements.extractTypeArguments &&
+        handleExtractTypeArguments(node, sourceInformation)) {
+      return;
+    }
+
     TypeMask typeMask = _typeInferenceMap.getReturnTypeOf(function);
 
     List<DartType> typeArguments =
@@ -3405,6 +3412,69 @@
     }
   }
 
+  /// Replace calls to `extractTypeArguments` with equivalent code. Returns
+  /// `true` if `extractTypeArguments` is handled.
+  bool handleExtractTypeArguments(
+      ir.StaticInvocation invocation, SourceInformation sourceInformation) {
+    // Expand calls as follows:
+    //
+    //     r = extractTypeArguments<Map>(e, f)
+    // -->
+    //     interceptor = getInterceptor(e);
+    //     T1 = getRuntimeTypeArgumentIntercepted(interceptor, e, 'Map', 0);
+    //     T2 = getRuntimeTypeArgumentIntercepted(interceptor, e, 'Map', 1);
+    //     r = f<T1, T2>();
+    //
+    // TODO(sra): Should we add a check before the variable extraction? We could
+    // add a type check (which would permit `null`), or add an is-check with an
+    // explicit throw.
+
+    if (invocation.arguments.positional.length != 2) return false;
+    if (invocation.arguments.named.isNotEmpty) return false;
+    var types = invocation.arguments.types;
+    if (types.length != 1) return false;
+
+    // The type should be a single type name.
+    ir.DartType type = types.first;
+    DartType typeValue =
+        localsHandler.substInContext(_elementMap.getDartType(type));
+    if (typeValue is! InterfaceType) return false;
+    InterfaceType interfaceType = typeValue;
+    if (!interfaceType.treatAsRaw) return false;
+
+    ClassEntity cls = interfaceType.element;
+    InterfaceType thisType = _elementMap.elementEnvironment.getThisType(cls);
+
+    List<HInstruction> arguments =
+        _visitPositionalArguments(invocation.arguments);
+
+    HInstruction object = arguments[0];
+    HInstruction closure = arguments[1];
+    HInstruction interceptor = _interceptorFor(object, sourceInformation);
+
+    List<HInstruction> inputs = <HInstruction>[closure];
+    List<DartType> typeArguments = <DartType>[];
+
+    thisType.typeArguments.forEach((_typeVariable) {
+      TypeVariableType variable = _typeVariable;
+      typeArguments.add(variable);
+      HInstruction readType = new HTypeInfoReadVariable.intercepted(
+          variable, interceptor, object, commonMasks.dynamicType);
+      add(readType);
+      inputs.add(readType);
+    });
+
+    // TODO(sra): In compliance mode, insert a check that [closure] is a
+    // function of N type arguments.
+
+    Selector selector =
+        new Selector.callClosure(0, const <String>[], typeArguments.length);
+    push(new HInvokeClosure(
+        selector, inputs, commonMasks.dynamicType, typeArguments));
+
+    return true;
+  }
+
   void handleInvokeStaticForeign(
       ir.StaticInvocation invocation, ir.Procedure target) {
     String name = target.name.name;
diff --git a/pkg/compiler/lib/src/ssa/kernel_impact.dart b/pkg/compiler/lib/src/ssa/kernel_impact.dart
index 735c58e..7b93861 100644
--- a/pkg/compiler/lib/src/ssa/kernel_impact.dart
+++ b/pkg/compiler/lib/src/ssa/kernel_impact.dart
@@ -397,6 +397,11 @@
       handleNew(node, node.target, isConst: node.isConst);
     } else {
       FunctionEntity target = elementMap.getMethod(node.target);
+      if (_options.strongMode &&
+          target == commonElements.extractTypeArguments) {
+        _handleExtractTypeArguments(node, target);
+        return;
+      }
       List<DartType> typeArguments = _visitArguments(node.arguments);
       impactBuilder.registerStaticUse(new StaticUse.staticInvoke(
           target, elementMap.getCallStructure(node.arguments), typeArguments));
@@ -426,6 +431,35 @@
     }
   }
 
+  void _handleExtractTypeArguments(
+      ir.StaticInvocation node, FunctionEntity target) {
+    // extractTypeArguments<Map>(obj, fn) has additional impacts:
+    //
+    //   1. All classes implementing Map need to carry type arguments (similar
+    //      to checking `o is Map<K, V>`).
+    //
+    //   2. There is an invocation of fn with some number of type arguments.
+    //
+    List<DartType> typeArguments = _visitArguments(node.arguments);
+    impactBuilder.registerStaticUse(new StaticUse.staticInvoke(
+        target, elementMap.getCallStructure(node.arguments), typeArguments));
+
+    if (typeArguments.length != 1) return;
+    DartType matchedType = typeArguments.first;
+
+    if (matchedType is! InterfaceType) return;
+    InterfaceType interfaceType = matchedType;
+    ClassEntity cls = interfaceType.element;
+    InterfaceType thisType = elementMap.elementEnvironment.getThisType(cls);
+
+    impactBuilder.registerTypeUse(new TypeUse.isCheck(thisType));
+
+    Selector selector = new Selector.callClosure(
+        0, const <String>[], thisType.typeArguments.length);
+    impactBuilder.registerDynamicUse(
+        new ConstrainedDynamicUse(selector, null, thisType.typeArguments));
+  }
+
   @override
   void visitStaticGet(ir.StaticGet node) {
     ir.Member target = node.target;
diff --git a/pkg/compiler/lib/src/universe/selector.dart b/pkg/compiler/lib/src/universe/selector.dart
index 263d4ac..3d03614 100644
--- a/pkg/compiler/lib/src/universe/selector.dart
+++ b/pkg/compiler/lib/src/universe/selector.dart
@@ -170,9 +170,10 @@
   factory Selector.call(Name name, CallStructure callStructure) =>
       new Selector(SelectorKind.CALL, name, callStructure);
 
-  factory Selector.callClosure(int arity, [List<String> namedArguments]) =>
+  factory Selector.callClosure(int arity,
+          [List<String> namedArguments, int typeArgumentCount = 0]) =>
       new Selector(SelectorKind.CALL, Names.call,
-          new CallStructure(arity, namedArguments));
+          new CallStructure(arity, namedArguments, typeArgumentCount));
 
   factory Selector.callClosureFrom(Selector selector) =>
       new Selector(SelectorKind.CALL, Names.call, selector.callStructure);
diff --git a/sdk/lib/_internal/js_runtime/lib/internal_patch.dart b/sdk/lib/_internal/js_runtime/lib/internal_patch.dart
index fddf0aa..f10fb5b 100644
--- a/sdk/lib/_internal/js_runtime/lib/internal_patch.dart
+++ b/sdk/lib/_internal/js_runtime/lib/internal_patch.dart
@@ -5,9 +5,9 @@
 import 'dart:core' hide Symbol;
 import 'dart:core' as core;
 import 'dart:_js_primitives' show printString;
-import 'dart:_js_helper' show patch;
+import 'dart:_js_helper' show patch, NoInline;
 import 'dart:_interceptors' show JSArray;
-import 'dart:_foreign_helper' show JS;
+import 'dart:_foreign_helper' show JS, JS_GET_FLAG;
 
 @patch
 class Symbol implements core.Symbol {
@@ -49,10 +49,18 @@
 }
 
 @patch
+@NoInline()
 Object extractTypeArguments<T>(T instance, Function extract) {
-  // TODO(31371): Implement this correctly for Dart 2.0.
+  // In Dart 2.0 this function is recognized and replaced with calls to
+  // js_runtime.
+  if (JS_GET_FLAG('STRONG_MODE')) throw new UnimplementedError();
+
   // In Dart 1.0, instantiating the generic with dynamic (which this does),
   // gives you an object that can be used anywhere a more specific type is
   // expected, so this works for now.
+
+  // This call to [extract] is also required for Dart 2.0 to model that the
+  // function is called and the returned value flows to the result of
+  // extractTypeArguments.
   return extract();
 }
diff --git a/sdk/lib/_internal/js_runtime/lib/js_rti.dart b/sdk/lib/_internal/js_runtime/lib/js_rti.dart
index 9a0627c..44df4af 100644
--- a/sdk/lib/_internal/js_runtime/lib/js_rti.dart
+++ b/sdk/lib/_internal/js_runtime/lib/js_rti.dart
@@ -575,7 +575,6 @@
   // Interceptor is needed for JSArray and native classes.
   // TODO(sra): It could be a more specialized interceptor since [object] is not
   // `null` or a primitive.
-  // TODO(9586): Move type info for static functions onto an interceptor.
   var interceptor = getInterceptor(object);
   var isSubclass = getField(interceptor, isField);
   // When we read the field and it is not there, [isSubclass] will be `null`.
@@ -592,7 +591,6 @@
   // Interceptor is needed for JSArray and native classes.
   // TODO(sra): It could be a more specialized interceptor since [object] is not
   // `null` or a primitive.
-  // TODO(9586): Move type info for static functions onto an interceptor.
   var interceptor = getInterceptor(object);
   var isSubclass = getField(interceptor, isField);
   // When we read the field and it is not there, [isSubclass] will be `null`.
diff --git a/tests/compiler/dart2js_extra/dart2js_extra.status b/tests/compiler/dart2js_extra/dart2js_extra.status
index c44b6e2..3d8c648 100644
--- a/tests/compiler/dart2js_extra/dart2js_extra.status
+++ b/tests/compiler/dart2js_extra/dart2js_extra.status
@@ -189,6 +189,9 @@
 typevariable_typedef_test: Fail, OK # Tests expected output of Type.toString().
 
 [ $compiler == dart2js && !$strong ]
+extract_type_arguments_1_test: RuntimeError # Uses function type variables
+extract_type_arguments_2_test: RuntimeError # Uses function type variables
+extract_type_arguments_3_test: RuntimeError # Uses function type variables
 generic_method_dynamic_is_test: RuntimeError # Test against function type variables is only supported in strong mode.
 generic_method_dynamic_type_test: SkipByDesign # Requires strong mode support for function type variables.
 generic_method_static_is_test: RuntimeError # Test against function type variables is only supported in strong mode.
diff --git a/tests/compiler/dart2js_extra/extract_type_arguments_1_test.dart b/tests/compiler/dart2js_extra/extract_type_arguments_1_test.dart
new file mode 100644
index 0000000..74d5081
--- /dev/null
+++ b/tests/compiler/dart2js_extra/extract_type_arguments_1_test.dart
@@ -0,0 +1,18 @@
+// 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=--omit-implicit-checks
+
+import 'package:expect/expect.dart';
+import 'dart:_internal' show extractTypeArguments;
+
+class C<T> {}
+
+main() {
+  test(new C<int>(), int);
+  test(new C<String>(), String);
+}
+
+test(dynamic a, T) {
+  Expect.equals(T, extractTypeArguments<C>(a, <T>() => T));
+}
diff --git a/tests/compiler/dart2js_extra/extract_type_arguments_2_test.dart b/tests/compiler/dart2js_extra/extract_type_arguments_2_test.dart
new file mode 100644
index 0000000..bf8c2f9
--- /dev/null
+++ b/tests/compiler/dart2js_extra/extract_type_arguments_2_test.dart
@@ -0,0 +1,20 @@
+// 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=--omit-implicit-checks
+
+import 'package:expect/expect.dart';
+import 'dart:_internal' show extractTypeArguments;
+
+class CC<A, B, C, D, E> {}
+
+class X {}
+
+main() {
+  test(new CC<X, X, X, X, int>(), int);
+  test(new CC<X, X, X, X, String>(), String);
+}
+
+test(dynamic a, T) {
+  Expect.equals(T, extractTypeArguments<CC>(a, <_1, _2, _3, _4, T>() => T));
+}
diff --git a/tests/compiler/dart2js_extra/extract_type_arguments_3_test.dart b/tests/compiler/dart2js_extra/extract_type_arguments_3_test.dart
new file mode 100644
index 0000000..10fa04a
--- /dev/null
+++ b/tests/compiler/dart2js_extra/extract_type_arguments_3_test.dart
@@ -0,0 +1,11 @@
+// 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=--omit-implicit-checks
+
+import 'package:expect/expect.dart';
+import 'dart:_internal' show extractTypeArguments;
+
+main() {
+  Expect.equals(int, extractTypeArguments<List>('hello'.codeUnits, <T>() => T));
+}
diff --git a/tests/language_2/language_2_dart2js.status b/tests/language_2/language_2_dart2js.status
index 4be878c..66550b4 100644
--- a/tests/language_2/language_2_dart2js.status
+++ b/tests/language_2/language_2_dart2js.status
@@ -876,7 +876,6 @@
 checked_setter2_test: RuntimeError # Issue 31128
 checked_setter_test: RuntimeError # Issue 31128
 const_dynamic_type_literal_test/03: Pass # but it shouldn't until we fix issue 17207
-extract_type_arguments_test: Crash # Issue 31371
 function_propagation_test: RuntimeError
 generic_test/01: MissingCompileTimeError # front end does not validate `extends`
 instantiate_tearoff_of_call_test: RuntimeError
@@ -1016,7 +1015,6 @@
 external_test/20: MissingRuntimeError
 external_test/21: CompileTimeError
 external_test/24: CompileTimeError
-extract_type_arguments_test: RuntimeError # Issue 31371
 f_bounded_quantification4_test: RuntimeError
 f_bounded_quantification_test/01: MissingCompileTimeError
 f_bounded_quantification_test/02: MissingCompileTimeError
@@ -1567,7 +1565,6 @@
 external_test/20: MissingRuntimeError
 external_test/21: CompileTimeError
 external_test/24: CompileTimeError
-extract_type_arguments_test: RuntimeError # Issue 31371
 f_bounded_quantification4_test: RuntimeError # Issue 12605
 f_bounded_quantification_test/01: MissingCompileTimeError
 f_bounded_quantification_test/02: MissingCompileTimeError