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();
+}