[ddc] Implement new record types

Update the format of the shape keys to better match the format used in
the dart:_rti library. This change applies to the current runtime type 
system as well.

Change-Id: I87d2af2aaf2b9dbe012fae60a64718d264a3a18c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/295721
Reviewed-by: Mayank Patke <fishythefish@google.com>
Reviewed-by: Mark Zhou <markzipan@google.com>
Commit-Queue: Nicholas Shahan <nshahan@google.com>
diff --git a/pkg/compiler/test/inference/type_combination_test.dart b/pkg/compiler/test/inference/type_combination_test.dart
index 8d772b0..3e90b7a 100644
--- a/pkg/compiler/test/inference/type_combination_test.dart
+++ b/pkg/compiler/test/inference/type_combination_test.dart
@@ -785,7 +785,7 @@
   patternClass = elementEnvironment.lookupClass(coreLibrary, 'Pattern');
 
   final trustedGetRuntimeTypeInterface = elementEnvironment.lookupClass(
-      commonElements.rtiLibrary, 'TrustedGetRuntimeType')!;
+      commonElements.jsHelperLibrary!, 'TrustedGetRuntimeType')!;
 
   nonPrimitive1 =
       TypeMask.nonNullSubtype(closedWorld.commonElements.mapClass, closedWorld);
diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart
index 451816c..98d3e2d 100644
--- a/pkg/dev_compiler/lib/src/kernel/compiler.dart
+++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart
@@ -3523,10 +3523,10 @@
     // RecordType names are already sorted alphabetically in kernel.
     var positionals = positionalTypeReps.length;
     var names = type.named.map((e) => e.name);
-    var shape = '$positionals ${names.join(" ")}';
+    var shapeKey = _recordShapeKey(positionals, names);
 
     return runtimeCall('recordTypeLiteral(#, #, #, [#])', [
-      js.string(shape),
+      js.string(shapeKey),
       js.number(positionals),
       names.isEmpty ? js.call('void 0') : js.stringArray(names),
       [
@@ -6912,17 +6912,27 @@
         [_emitType(keyType), _emitType(valueType), entries]));
   }
 
+  /// Returns the key used for shape lookup at runtime.
+  ///
+  /// See `shapes` in dart:_runtime (records.dart) for a description.
+  String _recordShapeKey(
+      int positionalElementCount, Iterable<String> namedElementNames) {
+    var elementCount = positionalElementCount + namedElementNames.length;
+    return '$elementCount;${namedElementNames.join(',')}';
+  }
+
   @override
   js_ast.Expression visitRecordLiteral(RecordLiteral node) {
     var names = node.named.map((element) => element.name);
-    var recipe = '${node.positional.length} ${names.join(" ")}';
+    var positionalElementCount = node.positional.length;
+    var shapeKey = _recordShapeKey(positionalElementCount, names);
     var shapeExpr = runtimeCall('recordLiteral(#, #, #, [#])', [
-      js.string(recipe),
-      js.number(node.positional.length),
+      js.string(shapeKey),
+      js.number(positionalElementCount),
       names.isEmpty ? js.call('void 0') : js.stringArray(names),
       [
-        ...node.positional.map(_visitExpression),
-        ...node.named.map((e) => _visitExpression(e.value))
+        for (var positional in node.positional) _visitExpression(positional),
+        for (var named in node.named) _visitExpression(named.value),
       ]
     ]);
     return shapeExpr;
@@ -7276,10 +7286,11 @@
   js_ast.Expression visitRecordConstant(RecordConstant node) {
     // RecordConstant names are already sorted alphabetically in kernel.
     var names = node.named.keys;
-    var shape = '${node.positional.length} ${names.join(" ")}';
+    var positionalElementCount = node.positional.length;
+    var shapeKey = _recordShapeKey(positionalElementCount, names);
     return runtimeCall('recordLiteral(#, #, #, [#])', [
-      js.string(shape),
-      js.number(node.positional.length),
+      js.string(shapeKey),
+      js.number(positionalElementCount),
       names.isEmpty ? js.call('void 0') : js.stringArray(names),
       [
         ...node.positional.map(visitConstant),
diff --git a/pkg/dev_compiler/lib/src/kernel/type_recipe_generator.dart b/pkg/dev_compiler/lib/src/kernel/type_recipe_generator.dart
index d0b7736..0c473ba 100644
--- a/pkg/dev_compiler/lib/src/kernel/type_recipe_generator.dart
+++ b/pkg/dev_compiler/lib/src/kernel/type_recipe_generator.dart
@@ -157,9 +157,8 @@
       Set.unmodifiable(_visitedInterfaceTypes);
 
   @override
-  String defaultDartType(DartType node) {
-    throw UnimplementedError('Unknown DartType: $node');
-  }
+  String defaultDartType(DartType node) =>
+      throw UnimplementedError('Unknown DartType: $node');
 
   @override
   String visitDynamicType(DynamicType node) => Recipe.pushDynamicString;
@@ -263,11 +262,24 @@
   }
 
   @override
-  // Just emit the recipe for dynamic as a temporary workaround to unblock
-  // the use of record types landing in the sdk.
-  // See: https://github.com/dart-lang/sdk/issues/51904
-  // TODO(nshahan): Implement valid record type recipes.
-  String visitRecordType(RecordType node) => Recipe.pushDynamicString;
+  String visitRecordType(RecordType node) {
+    var recipeBuffer = StringBuffer(Recipe.startRecordString);
+    // Add the names of the named elements.
+    recipeBuffer.writeAll(
+        node.named.map((element) => element.name), Recipe.separatorString);
+    // Add all element types.
+    recipeBuffer.write(Recipe.startFunctionArgumentsString);
+    var elementTypes = [
+      ...node.positional,
+      ...node.named.map((element) => element.type)
+    ];
+    recipeBuffer.writeAll(elementTypes.map((element) => element.accept(this)),
+        Recipe.separatorString);
+    recipeBuffer.write(Recipe.endFunctionArgumentsString);
+    // Add the records nullability.
+    recipeBuffer.write(_nullabilityRecipe(node));
+    return recipeBuffer.toString();
+  }
 
   @override
   String visitTypeParameterType(TypeParameterType node) {
diff --git a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart
index 23611af..e920806 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart
@@ -13,6 +13,12 @@
   throw ArgumentError.value(value);
 }
 
+/// Only used during the development of the new runtime type system in branches
+/// that should never be executed.
+// TODO(48585): Remove after switching to the new runtime type system.
+Never throwUnimplementedInOldRti() => throw UnimplementedError(
+    'This code path is not support with the old runtime type system.');
+
 throwUnimplementedError(String message) {
   throw UnimplementedError(message);
 }
diff --git a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/records.dart b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/records.dart
index 15b2800..6cc4078 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/records.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/records.dart
@@ -96,9 +96,18 @@
 
 /// Cache used to canonicalize all Record shapes in the program.
 ///
-/// These are keyed by a distinct shape recipe String, which consists of an
-/// integer followed by space-separated named labels.
-final _shapes = JS('!', 'new Map()');
+/// [Shape]s are keyed by a distinct shape key [String], that consists of the
+/// total number of elements followed by semicolon and then a comma-separated
+/// list of the named element names in sorted order.
+///
+/// Shape key examples:
+///
+///   | Record                              | Shape Key     |
+///   -------------------------------------------------------
+///   | (false, "hello")                    | "2;"          |
+///   | (name: "Fosse", legs: 4)            | "2;legs,name" |
+///   | ("hello", name: "Cello", legs: 4)   | "3;legs,name" |
+final shapes = JS('!', 'new Map()');
 
 /// Cache used to canonicalize all Record representation classes in the program.
 ///
@@ -107,27 +116,30 @@
 final _records = JS('!', 'new Map()');
 
 /// Returns a canonicalized shape for the provided number of [positionals] and
-/// [named] elements as described by the [shapeRecipe].
-Shape registerShape(@notNull String shapeRecipe, @notNull int positionals,
-    List<String>? named) {
-  var cached = JS<Shape?>('', '#.get(#)', _shapes, shapeRecipe);
+/// [named] elements.
+///
+/// The [shapeKey] must agree with the number of [positionals] and the [named]
+/// elements list. See [shapes] for a description of the shape key format.
+Shape registerShape(
+    @notNull String shapeKey, @notNull int positionals, List<String>? named) {
+  var cached = JS<Shape?>('', '#.get(#)', shapes, shapeKey);
   if (cached != null) {
     return cached;
   }
 
   var shape = Shape(positionals, named);
-  JS('', '#.set(#, #)', _shapes, shapeRecipe, shape);
+  JS('', '#.set(#, #)', shapes, shapeKey, shape);
   return shape;
 }
 
 /// Returns a canonicalized Record class with the provided number of
 /// [positionals] and [named] elements.
 ///
-/// The class can be used to construct record values of the shape described by
-/// the [shapeRecipe].
-Object registerRecord(@notNull String shapeRecipe, @notNull int positionals,
-    List<String>? named) {
-  var cached = JS('', '#.get(#)', _records, shapeRecipe);
+/// The [shapeKey] must agree with the number of [positionals] and the [named]
+/// elements list. See [shapes] for a description of the shape key format.
+Object registerRecord(
+    @notNull String shapeKey, @notNull int positionals, List<String>? named) {
+  var cached = JS('', '#.get(#)', _records, shapeKey);
   if (cached != null) {
     return cached;
   }
@@ -168,19 +180,19 @@
     }
   }
 
-  JS('', '#.set(#, #)', _records, shapeRecipe, newRecord);
+  JS('', '#.set(#, #)', _records, shapeKey, newRecord);
   return newRecord;
 }
 
-/// Creates a shape and binds it to [values].
+/// Creates a record consisting of [values] with the shape described by the
+/// number of [positionals] and [named] elements.
 ///
-/// [shapeRecipe] consists of a space-separated list of elements, where the
-/// first element is the number of positional elements, followed by every
-/// named element in sorted order.
-Object recordLiteral(@notNull String shapeRecipe, @notNull int positionals,
+/// The [shapeKey] must agree with the number of [positionals] and the [named]
+/// elements list. See [shapes] for a description of the shape key format.
+Object recordLiteral(@notNull String shapeKey, @notNull int positionals,
     List<String>? named, @notNull List values) {
-  var shape = registerShape(shapeRecipe, positionals, named);
-  var record = registerRecord(shapeRecipe, positionals, named);
+  var shape = registerShape(shapeKey, positionals, named);
+  var record = registerRecord(shapeKey, positionals, named);
   return JS('!', 'new #(#, #)', record, shape, values);
 }
 
diff --git a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/types.dart b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/types.dart
index 3933478..11b00c4 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/types.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/types.dart
@@ -2283,12 +2283,11 @@
 
 /// Creates a shape and binds it to [types].
 ///
-/// [shapeRecipe] consists of a space-separated list of elements, where the
-/// first element is the number of positional elements, followed by every
-/// named element in sorted order.
-RecordType recordTypeLiteral(@notNull String shapeRecipe,
-    @notNull int positionals, List<String>? named, @notNull List types) {
-  var shape = registerShape(shapeRecipe, positionals, named);
+/// The [shapeKey] must agree with the number of [positionals] and the [named]
+/// elements list. See [shapes] for a description of the shape key format.
+RecordType recordTypeLiteral(@notNull String shapeKey, @notNull int positionals,
+    List<String>? named, @notNull List types) {
+  var shape = registerShape(shapeKey, positionals, named);
   return recordType(shape, types);
 }
 
diff --git a/sdk/lib/_internal/js_dev_runtime/private/interceptors.dart b/sdk/lib/_internal/js_dev_runtime/private/interceptors.dart
index 739c89d..39384f4 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/interceptors.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/interceptors.dart
@@ -30,7 +30,7 @@
  * The interceptor class for [bool].
  */
 @JsPeerInterface(name: 'Boolean')
-final class JSBool extends Interceptor implements bool {
+final class JSBool extends Interceptor implements bool, TrustedGetRuntimeType {
   const JSBool();
 
   // Note: if you change this, also change the function [S].
diff --git a/sdk/lib/_internal/js_dev_runtime/private/js_array.dart b/sdk/lib/_internal/js_dev_runtime/private/js_array.dart
index 041d595..c8cf132 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/js_array.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/js_array.dart
@@ -11,7 +11,8 @@
  * argument added to each member.
  */
 @JsPeerInterface(name: 'Array')
-class JSArray<E> extends JavaScriptObject implements List<E>, JSIndexable<E> {
+class JSArray<E> extends JavaScriptObject
+    implements List<E>, JSIndexable<E>, TrustedGetRuntimeType {
   const JSArray();
 
   /**
diff --git a/sdk/lib/_internal/js_dev_runtime/private/js_helper.dart b/sdk/lib/_internal/js_dev_runtime/private/js_helper.dart
index a4d8848..3c52934 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/js_helper.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/js_helper.dart
@@ -7,7 +7,7 @@
 import 'dart:async' show Zone;
 import 'dart:collection';
 
-import 'dart:_foreign_helper' show JS, JSExportName;
+import 'dart:_foreign_helper' show JS, JS_CLASS_REF, JS_GET_FLAG, JSExportName;
 
 import 'dart:_interceptors';
 import 'dart:_internal'
@@ -19,6 +19,7 @@
         patch;
 
 import 'dart:_native_typed_data';
+import 'dart:_rti' as rti show pairwiseIsTest, evaluateRtiForRecord, Rti;
 import 'dart:_runtime' as dart;
 
 part 'annotations.dart';
@@ -863,15 +864,84 @@
   return Zone.current.bindUnaryCallbackGuarded(callback);
 }
 
-/// [createRecordTypePredicate] is currently unused by DDC.
-Object? createRecordTypePredicate(Object? partialShapeTag, Object? fieldRtis) {
-  throw UnimplementedError('createRecordTypePredicate');
+/// Returns a JavaScript predicate that tests if the argument is a record with
+/// the given shape and fields types.
+///
+/// Only called from the `dart:_rti` library but requires specific knowledge of
+/// the record representation in DDC. There is a duplicate version of this
+/// method in the dart2js version of this library.
+///
+/// The shape is determined by the number of fields and the [partialShapeTag].
+/// [fieldRtis] contains the Rti type objects for each field in order of
+/// positionals followed by the sorted named elements.
+Object? createRecordTypePredicate(String partialShapeTag, JSArray fieldRtis) {
+  if (JS_GET_FLAG('NEW_RUNTIME_TYPES')) {
+    var shapeKey =
+        JS<String>('!', '#.length + ";" + #', fieldRtis, partialShapeTag);
+    return (obj) {
+      return JS<bool>(
+              '!', '# instanceof #', obj, JS_CLASS_REF(dart.RecordImpl)) &&
+          JS<dart.RecordImpl>('!', '#', obj).shape ==
+              JS<dart.Shape?>('', '#.get(#)', dart.shapes, shapeKey) &&
+          rti.pairwiseIsTest(fieldRtis, JS<JSArray>('!', '#.values', obj));
+    };
+  } else {
+    dart.throwUnimplementedInOldRti();
+  }
 }
 
-/// Entrypoint for rti library. Calls rti.evaluateRtiForRecord with components
-/// of the record.
+/// Returns the Rti for the provided [record].
 ///
-/// [getRtiForRecord] is currently unused by DDC.
-Never getRtiForRecord(Object? record) {
-  throw UnimplementedError('getRtiForRecord');
+/// Only called from the `dart:_rti` library but requires specific knowledge of
+/// the record representation in DDC. There is a duplicate version of this
+/// method in the dart2js version of this library.
+///
+/// Calls [rti.evaluateRtiForRecord] with components of the [record].
+rti.Rti getRtiForRecord(Object? record) {
+  if (JS_GET_FLAG('NEW_RUNTIME_TYPES')) {
+    var recordObj = JS<dart.RecordImpl>('!', '#', record);
+    var recipeBuffer = StringBuffer('+');
+    var named = recordObj.shape.named;
+    if (named != null) recipeBuffer.writeAll(named, ',');
+    recipeBuffer.write('(');
+    var elementCount = recordObj.values.length;
+    recipeBuffer.writeAll([for (var i = 1; i <= elementCount; i++) i], ',');
+    recipeBuffer.write(')');
+
+    return rti.evaluateRtiForRecord(recipeBuffer.toString(), recordObj.values);
+  } else {
+    dart.throwUnimplementedInOldRti();
+  }
 }
+
+/// A marker interface for classes with 'trustworthy' implementations of `get
+/// runtimeType`.
+///
+/// Generally, overrides of `get runtimeType` are not used in displaying the
+/// types of irritants in TypeErrors or computing the structural `runtimeType`
+/// of records. Instead the Rti (aka 'true') type is used.
+///
+/// The 'true' type is sometimes confusing because it shows implementation
+/// details, e.g. the true type of `42` is `JSInt` and `2.1` is `JSNumNotInt`.
+///
+/// For a limited number of implementation classes we tell a 'white lie' that
+/// the value is of another type, e.g. that `42` is an `int` and `2.1` is
+/// `double`. This is achieved by overriding `get runtimeType` to return the
+/// desired type, and marking the implementation class type with `implements
+/// [TrustedGetRuntimeType]`.
+///
+/// [TrustedGetRuntimeType] is not exposed outside the `dart:` libraries so
+/// users cannot tell lies.
+///
+/// The `Type` returned by a trusted `get runtimeType` must be an instance of
+/// the system `Type`, which is guaranteed by using a type literal. Type
+/// literals can be generic and dependent on type variables, e.g. `List<E>`.
+///
+/// Care needs to taken to ensure that the runtime does not get caught telling
+/// lies. Generally, a class's `runtimeType` lies by returning an abstract
+/// supertype of the class.  Since both the the marker interface and `get
+/// runtimeType` are inherited, there should be no way in which a user can
+/// extend the class or implement interface of the class.
+// TODO(48585): Move this class back to the dart:_rti library when old DDC
+// runtime type system has been removed.
+abstract class TrustedGetRuntimeType {}
diff --git a/sdk/lib/_internal/js_dev_runtime/private/js_number.dart b/sdk/lib/_internal/js_dev_runtime/private/js_number.dart
index 9a28f3a..3de5597 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/js_number.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/js_number.dart
@@ -16,7 +16,8 @@
 ///
 /// These are made available as extension methods on `Number` in JS.
 @JsPeerInterface(name: 'Number')
-final class JSNumber extends Interceptor implements double {
+final class JSNumber extends Interceptor
+    implements double, TrustedGetRuntimeType {
   const JSNumber();
 
   @notNull
diff --git a/sdk/lib/_internal/js_dev_runtime/private/js_string.dart b/sdk/lib/_internal/js_dev_runtime/private/js_string.dart
index a99d26f..2f9e026 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/js_string.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/js_string.dart
@@ -12,7 +12,7 @@
  */
 @JsPeerInterface(name: 'String')
 final class JSString extends Interceptor
-    implements String, JSIndexable<String> {
+    implements String, JSIndexable<String>, TrustedGetRuntimeType {
   const JSString();
 
   @notNull
@@ -207,8 +207,10 @@
         case 0x0D:
         case 0x20:
         case 0x85:
-        case 0xA0: return true;
-        default: return false;
+        case 0xA0:
+          return true;
+        default:
+          return false;
       }
     }
     switch (codeUnit) {
@@ -229,8 +231,10 @@
       case 0x202F:
       case 0x205F:
       case 0x3000:
-      case 0xFEFF: return true;
-      default: return false;
+      case 0xFEFF:
+        return true;
+      default:
+        return false;
     }
   }
 
diff --git a/sdk/lib/_internal/js_runtime/lib/interceptors.dart b/sdk/lib/_internal/js_runtime/lib/interceptors.dart
index 7f91159..79b283d 100644
--- a/sdk/lib/_internal/js_runtime/lib/interceptors.dart
+++ b/sdk/lib/_internal/js_runtime/lib/interceptors.dart
@@ -41,7 +41,8 @@
         throwConcurrentModificationError,
         lookupAndCacheInterceptor,
         StringMatch,
-        firstMatchAfter;
+        firstMatchAfter,
+        TrustedGetRuntimeType;
 
 import 'dart:_foreign_helper'
     show
@@ -56,8 +57,7 @@
     show
         createRuntimeType,
         getRuntimeTypeOfArray,
-        getRuntimeTypeOfInterceptorNotArray,
-        TrustedGetRuntimeType;
+        getRuntimeTypeOfInterceptorNotArray;
 
 import 'dart:math' show Random, ln2;
 
diff --git a/sdk/lib/_internal/js_runtime/lib/js_helper.dart b/sdk/lib/_internal/js_runtime/lib/js_helper.dart
index 285644b..9f3b92f 100644
--- a/sdk/lib/_internal/js_runtime/lib/js_helper.dart
+++ b/sdk/lib/_internal/js_runtime/lib/js_helper.dart
@@ -3205,3 +3205,35 @@
   if (callback == null) return null;
   return Zone.current.bindUnaryCallbackGuarded(callback);
 }
+
+/// A marker interface for classes with 'trustworthy' implementations of `get
+/// runtimeType`.
+///
+/// Generally, overrides of `get runtimeType` are not used in displaying the
+/// types of irritants in TypeErrors or computing the structural `runtimeType`
+/// of records. Instead the Rti (aka 'true') type is used.
+///
+/// The 'true' type is sometimes confusing because it shows implementation
+/// details, e.g. the true type of `42` is `JSInt` and `2.1` is `JSNumNotInt`.
+///
+/// For a limited number of implementation classes we tell a 'white lie' that
+/// the value is of another type, e.g. that `42` is an `int` and `2.1` is
+/// `double`. This is achieved by overriding `get runtimeType` to return the
+/// desired type, and marking the implementation class type with `implements
+/// [TrustedGetRuntimeType]`.
+///
+/// [TrustedGetRuntimeType] is not exposed outside the `dart:` libraries so
+/// users cannot tell lies.
+///
+/// The `Type` returned by a trusted `get runtimeType` must be an instance of
+/// the system `Type`, which is guaranteed by using a type literal. Type
+/// literals can be generic and dependent on type variables, e.g. `List<E>`.
+///
+/// Care needs to taken to ensure that the runtime does not get caught telling
+/// lies. Generally, a class's `runtimeType` lies by returning an abstract
+/// supertype of the class.  Since both the the marker interface and `get
+/// runtimeType` are inherited, there should be no way in which a user can
+/// extend the class or implement interface of the class.
+// TODO(48585): Move this class back to the dart:_rti library when old DDC
+// runtime type system has been removed.
+abstract class TrustedGetRuntimeType {}
diff --git a/sdk/lib/_internal/js_runtime/lib/native_typed_data.dart b/sdk/lib/_internal/js_runtime/lib/native_typed_data.dart
index 011e68f..224a45c 100644
--- a/sdk/lib/_internal/js_runtime/lib/native_typed_data.dart
+++ b/sdk/lib/_internal/js_runtime/lib/native_typed_data.dart
@@ -19,9 +19,9 @@
         Native,
         Returns,
         diagnoseIndexError,
-        diagnoseRangeError;
+        diagnoseRangeError,
+        TrustedGetRuntimeType;
 import 'dart:_foreign_helper' show JS;
-import 'dart:_rti' show TrustedGetRuntimeType;
 
 import 'dart:math' as Math;
 
diff --git a/sdk/lib/_internal/js_shared/lib/rti.dart b/sdk/lib/_internal/js_shared/lib/rti.dart
index 3387b6a..9c3a3d2 100644
--- a/sdk/lib/_internal/js_shared/lib/rti.dart
+++ b/sdk/lib/_internal/js_shared/lib/rti.dart
@@ -22,41 +22,12 @@
     show JavaScriptFunction, JSArray, JSNull, JSUnmodifiableArray;
 import 'dart:_js_helper' as records
     show createRecordTypePredicate, getRtiForRecord;
+import 'dart:_js_helper' as helper show TrustedGetRuntimeType;
 import 'dart:_js_names'
     show getSpecializedTestTag, unmangleGlobalNameIfPreservedAnyways;
 import 'dart:_js_shared_embedded_names';
 import 'dart:_recipe_syntax';
 
-/// A marker interface for classes with 'trustworthy' implementations of `get
-/// runtimeType`.
-///
-/// Generally, overrides of `get runtimeType` are not used in displaying the
-/// types of irritants in TypeErrors or computing the structural `runtimeType`
-/// of records. Instead the Rti (aka 'true') type is used.
-///
-/// The 'true' type is sometimes confusing because it shows implementation
-/// details, e.g. the true type of `42` is `JSInt` and `2.1` is `JSNumNotInt`.
-///
-/// For a limited number of implementation classes we tell a 'white lie' that
-/// the value is of another type, e.g. that `42` is an `int` and `2.1` is
-/// `double`. This is achieved by overriding `get runtimeType` to return the
-/// desired type, and marking the implementation class type with `implements
-/// [TrustedGetRuntimeType]`.
-///
-/// [TrustedGetRuntimeType] is not exposed outside the `dart:` libraries so
-/// users cannot tell lies.
-///
-/// The `Type` returned by a trusted `get runtimeType` must be an instance of
-/// the system `Type`, which is guaranteed by using a type literal. Type
-/// literals can be generic and dependent on type variables, e.g. `List<E>`.
-///
-/// Care needs to taken to ensure that the runtime does not get caught telling
-/// lies. Generally, a class's `runtimeType` lies by returning an abstract
-/// supertype of the class.  Since both the the marker interface and `get
-/// runtimeType` are inherited, there should be no way in which a user can
-/// extend the class or implement interface of the class.
-abstract class TrustedGetRuntimeType {}
-
 /// The name of a property on the constructor function of Dart Object
 /// and interceptor types, used for caching Rti types.
 const constructorRtiCachePropertyName = r'$ccache';
@@ -986,7 +957,7 @@
   if (object is Record) return records.getRtiForRecord(object);
   final functionRti = _instanceFunctionType(object);
   if (functionRti != null) return functionRti;
-  if (object is TrustedGetRuntimeType) {
+  if (object is helper.TrustedGetRuntimeType) {
     final type = object.runtimeType;
     return _Utils.as_Type(type)._rti;
   }
diff --git a/tests/dartdevc/debugger/debugger_test_golden.txt b/tests/dartdevc/debugger/debugger_test_golden.txt
index defb03a..3b42e32 100644
--- a/tests/dartdevc/debugger/debugger_test_golden.txt
+++ b/tests/dartdevc/debugger/debugger_test_golden.txt
@@ -239,7 +239,7 @@
     {
         "style": "background-color: #d9edf7;color: black"
     },
-    "List<Object> implements List<Object>, JSIndexable<Object>"
+    "List<Object> implements List<Object>, JSIndexable<Object>, TrustedGetRuntimeType"
 ]
 -----------------------------------
 Test: List<Object> definition formatting body
@@ -1788,7 +1788,7 @@
     {
         "style": "background-color: #d9edf7;color: black"
     },
-    "List<int> implements List<int>, JSIndexable<int>"
+    "List<int> implements List<int>, JSIndexable<int>, TrustedGetRuntimeType"
 ]
 -----------------------------------
 Test: List<int> large definition formatting body
diff --git a/tests/dartdevc_2/debugger/debugger_test_golden.txt b/tests/dartdevc_2/debugger/debugger_test_golden.txt
index defb03a..3b42e32 100644
--- a/tests/dartdevc_2/debugger/debugger_test_golden.txt
+++ b/tests/dartdevc_2/debugger/debugger_test_golden.txt
@@ -239,7 +239,7 @@
     {
         "style": "background-color: #d9edf7;color: black"
     },
-    "List<Object> implements List<Object>, JSIndexable<Object>"
+    "List<Object> implements List<Object>, JSIndexable<Object>, TrustedGetRuntimeType"
 ]
 -----------------------------------
 Test: List<Object> definition formatting body
@@ -1788,7 +1788,7 @@
     {
         "style": "background-color: #d9edf7;color: black"
     },
-    "List<int> implements List<int>, JSIndexable<int>"
+    "List<int> implements List<int>, JSIndexable<int>, TrustedGetRuntimeType"
 ]
 -----------------------------------
 Test: List<int> large definition formatting body