Add environment.bind operation.

Change-Id: I2fd5d4b87e7fdd1fe75f064ec82c43a8334c2138
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/105722
Reviewed-by: Mayank Patke <fishythefish@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Stephen Adams <sra@google.com>
diff --git a/sdk/lib/_internal/js_runtime/lib/rti.dart b/sdk/lib/_internal/js_runtime/lib/rti.dart
index c56ccf1..b9eb479 100644
--- a/sdk/lib/_internal/js_runtime/lib/rti.dart
+++ b/sdk/lib/_internal/js_runtime/lib/rti.dart
@@ -53,13 +53,15 @@
   /// `this` type environment.
   Rti _eval(String recipe) => _rtiEval(this, recipe);
 
-  /// Method called from generated code to extend `this` type environment with a
-  /// function type parameter.
-  Rti _bind1(Rti type) => _rtiBind1(this, type);
+  /// Method called from generated code to extend `this` type environment (an
+  /// interface or binding Rti) with function type arguments (a singleton
+  /// argument or tuple of arguments).
+  Rti _bind(Rti typeOrTuple) => _rtiBind(this, typeOrTuple);
 
-  /// Method called from generated code to extend `this` type environment with a
-  /// tuple of function type parameters.
-  Rti _bind(Rti typeTuple) => _rtiBind(this, typeTuple);
+  /// Method called from generated code to extend `this` type (as a singleton
+  /// type environment) with function type arguments (a singleton argument or
+  /// tuple of arguments).
+  Rti _bind1(Rti typeOrTuple) => _rtiBind1(this, typeOrTuple);
 
   // Precomputed derived types. These fields are used to hold derived types that
   // are computed eagerly.
@@ -151,10 +153,15 @@
     return JS('JSUnmodifiableArray', '#', _getRest(rti));
   }
 
-  /// On [Rti]s that are type environments, derived types are cached on the
+  /// On [Rti]s that are type environments*, derived types are cached on the
   /// environment to ensure fast canonicalization. Ground-term types (i.e. not
   /// dependent on class or function type parameters) are cached in the
   /// universe. This field starts as `null` and the cache is created on demand.
+  ///
+  /// *Any Rti can be a type environment, since we use the type for a function
+  /// type environment. The ambiguity between 'generic class is the environment'
+  /// and 'generic class is a singleton type argument' is resolved by using
+  /// different indexing in the recipe.
   Object _evalCache;
 
   static Object _getEvalCache(Rti rti) => rti._evalCache;
@@ -162,6 +169,22 @@
     rti._evalCache = value;
   }
 
+  /// On [Rti]s that are type environments*, extended environments are cached on
+  /// the base environment to ensure fast canonicalization.
+  ///
+  /// This field starts as `null` and the cache is created on demand.
+  ///
+  /// *This is valid only on kindInterface and kindBinding Rtis. The ambiguity
+  /// between 'generic class is the base environment' and 'generic class is a
+  /// singleton type argument' is resolved [TBD] (either (1) a bind1 cache, or
+  /// (2)using `env._eval("@<0>")._bind(args)` in place of `env._bind1(args)`).
+  Object _bindCache;
+
+  static Object _getBindCache(Rti rti) => rti._evalCache;
+  static void _setBindCache(Rti rti, value) {
+    rti._evalCache = value;
+  }
+
   static Rti allocate() {
     return new Rti();
   }
@@ -180,14 +203,17 @@
 }
 
 Rti _rtiEval(Rti environment, String recipe) {
+  // TODO(sra): return _Universe.eval(the-universe, environment, recipe);
   throw UnimplementedError('_rtiEval');
 }
 
-Rti _rtiBind1(Rti environment, Rti type) {
+Rti _rtiBind1(Rti environment, Rti types) {
+  // TODO(sra): return _Universe.bind1(the-universe, environment, types);
   throw UnimplementedError('_rtiBind1');
 }
 
 Rti _rtiBind(Rti environment, Rti typeTuple) {
+  // TODO(sra): return _Universe.bind(the-universe, environment, types);
   throw UnimplementedError('_rtiBind');
 }
 
@@ -197,7 +223,9 @@
 
 String _rtiToString(Rti rti, List<String> genericContext) {
   int kind = Rti._getKind(rti);
+
   if (kind == Rti.kindDynamic) return 'dynamic';
+
   if (kind == Rti.kindInterface) {
     String name = Rti._getInterfaceName(rti);
     var arguments = Rti._getInterfaceTypeArguments(rti);
@@ -211,9 +239,43 @@
     }
     return name;
   }
+
   return '?';
 }
 
+String _rtiToDebugString(Rti rti) {
+  String arrayToString(Object array) {
+    String s = '[', sep = '';
+    for (int i = 0; i < _Utils.arrayLength(array); i++) {
+      s += sep + _rtiToDebugString(_castToRti(_Utils.arrayAt(array, i)));
+      sep = ', ';
+    }
+    return s + ']';
+  }
+
+  int kind = Rti._getKind(rti);
+
+  if (kind == Rti.kindDynamic) return 'dynamic';
+
+  if (kind == Rti.kindInterface) {
+    String name = Rti._getInterfaceName(rti);
+    var arguments = Rti._getInterfaceTypeArguments(rti);
+    if (_Utils.arrayLength(arguments) == 0) {
+      return 'interface("$name")';
+    } else {
+      return 'interface("$name", ${arrayToString(arguments)})';
+    }
+  }
+
+  if (kind == Rti.kindBinding) {
+    var base = Rti._getBindingBase(rti);
+    var arguments = Rti._getBindingArguments(rti);
+    return 'binding(${_rtiToDebugString(base)}, ${arrayToString(arguments)})';
+  }
+
+  return 'other(kind=$kind)';
+}
+
 /// Class of static methods for the universe of Rti objects.
 ///
 /// The universe is the manager object for the Rti instances.
@@ -272,6 +334,27 @@
     return rti;
   }
 
+  static Rti bind(Object universe, Rti environment, Rti argumentsRti) {
+    var cache = Rti._getBindCache(environment);
+    if (cache == null) {
+      cache = JS('', 'new Map()');
+      Rti._setBindCache(environment, cache);
+    }
+    var argumentsRecipe = Rti._getCanonicalRecipe(argumentsRti);
+    var probe = _cacheGet(cache, argumentsRecipe);
+    if (probe != null) return _castToRti(probe);
+    var argumentsArray;
+    if (Rti._getKind(argumentsRti) == Rti.kindBinding) {
+      argumentsArray = Rti._getBindingArguments(argumentsRti);
+    } else {
+      argumentsArray = JS('', '[]');
+      _Utils.arrayPush(argumentsArray, argumentsRti);
+    }
+    var rti = _lookupBindingRti(universe, environment, argumentsArray);
+    _cacheSet(cache, argumentsRecipe, rti);
+    return rti;
+  }
+
   static Rti evalTypeVariable(Object universe, Rti environment, String name) {
     throw UnimplementedError('_Universe.evalTypeVariable("$name")');
   }
@@ -376,6 +459,7 @@
     return s;
   }
 
+  /// [arguments] becomes owned by the created Rti.
   static Rti _lookupBindingRti(Object universe, Rti base, Object arguments) {
     var newBase = base;
     var newArguments = arguments;
@@ -773,6 +857,10 @@
   static JSArray arrayConcat(Object a1, Object a2) =>
       JS('JSArray', '#.concat(#)', a1, a2);
 
+  static void arrayPush(Object array, Object value) {
+    JS('', '#.push(#)', array, value);
+  }
+
   static String substring(String s, int start, int end) =>
       JS('String', '#.substring(#, #)', s, start, end);
 
@@ -789,8 +877,7 @@
 }
 
 String testingRtiToDebugString(rti) {
-  // TODO(sra): Create entty point for structural formatting of Rti tree.
-  return 'Rti';
+  return _rtiToDebugString(_castToRti(rti));
 }
 
 Object testingCreateUniverse() {
@@ -812,3 +899,8 @@
 Object testingEnvironmentEval(universe, environment, String recipe) {
   return _Universe.evalInEnvironment(universe, _castToRti(environment), recipe);
 }
+
+Object testingEnvironmentBind(universe, environment, arguments) {
+  return _Universe.bind(
+      universe, _castToRti(environment), _castToRti(arguments));
+}
diff --git a/tests/compiler/dart2js_extra/rti/bind_test.dart b/tests/compiler/dart2js_extra/rti/bind_test.dart
new file mode 100644
index 0000000..ca75c20
--- /dev/null
+++ b/tests/compiler/dart2js_extra/rti/bind_test.dart
@@ -0,0 +1,48 @@
+// Copyright (c) 2019, 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.
+
+import 'dart:_rti' as rti;
+import "package:expect/expect.dart";
+
+void checkRtiIdentical(Object rti1, Object rti2) {
+  var format = rti.testingRtiToString;
+  Expect.isTrue(
+      identical(rti1, rti2), 'identical(${format(rti1)}, ${format(rti2)}');
+}
+
+test1() {
+  var universe = rti.testingCreateUniverse();
+
+  // Extend environment in one step
+  var env1a = rti.testingUniverseEval(universe, 'Foo');
+  var args1 = rti.testingUniverseEval(universe, '@<aa,bb>');
+  var env1b = rti.testingEnvironmentBind(universe, env1a, args1);
+
+  var rti1 = rti.testingEnvironmentEval(universe, env1b, 'A<0,1,2>');
+  Expect.equals('A<Foo, aa, bb>', rti.testingRtiToString(rti1));
+
+  Expect.equals('binding(interface("Foo"), [interface("aa"), interface("bb")])',
+      rti.testingRtiToDebugString(env1b));
+
+  // Extend environment in two steps
+  var env2a = rti.testingUniverseEval(universe, 'Foo');
+  var args2a = rti.testingUniverseEval(universe, 'aa');
+  var env2b = rti.testingEnvironmentBind(universe, env2a, args2a);
+  var args2b = rti.testingUniverseEval(universe, 'bb');
+  var env2c = rti.testingEnvironmentBind(universe, env2b, args2b);
+
+  var rti2 = rti.testingEnvironmentEval(universe, env2c, 'A<0,1,2>');
+  Expect.equals('A<Foo, aa, bb>', rti.testingRtiToString(rti2));
+
+  Expect.equals('binding(interface("Foo"), [interface("aa")])',
+      rti.testingRtiToDebugString(env2b));
+  Expect.equals('binding(interface("Foo"), [interface("aa"), interface("bb")])',
+      rti.testingRtiToDebugString(env2c));
+
+  checkRtiIdentical(env1b, env2c);
+}
+
+main() {
+  test1();
+}