[js_runtime] Use marker interface to signal trusted .runtimeType overrides Use the `TrustedGetRuntimeType` interface to mark implementation classes that have a `get runtimeType` that should be used for reporting the types of instances in errors and `Record.runtimeType`. Streamline the various more general `get runtimeType` implementations by specializing them to the context. By explictly implementing `Closure.runtimeType` we avoid the need to test for closures in the 'ordinary' object path. This should help the Flutter pattern of testing `a.runtimeType == b.runtimeType`. Change-Id: Ib49a6d89ff23e752beed8753aafceff40457433d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/291741 Reviewed-by: Mayank Patke <fishythefish@google.com> Commit-Queue: Stephen Adams <sra@google.com>
diff --git a/pkg/compiler/test/inference/type_combination_test.dart b/pkg/compiler/test/inference/type_combination_test.dart index ee8246b..8d772b0 100644 --- a/pkg/compiler/test/inference/type_combination_test.dart +++ b/pkg/compiler/test/inference/type_combination_test.dart
@@ -32,6 +32,7 @@ late TypeMask potentialString; late TypeMask jsInterceptor; late TypeMask jsInterceptorOrComparable; +late TypeMask jsTrustedGetRuntimeType; late TypeMask jsIndexable; late TypeMask jsReadableArray; @@ -49,6 +50,7 @@ late TypeMask jsIndexableOrNull; late TypeMask jsInterceptorOrNull; late TypeMask jsInterceptorOrComparableOrNull; +late TypeMask jsTrustedGetRuntimeTypeOrNull; class Pair { final first; @@ -168,10 +170,10 @@ rule(jsBoolean, jsBoolean, jsBoolean); rule(jsBoolean, jsNumber, jsInterceptor); - rule(jsBoolean, jsInteger, jsInterceptor); - rule(jsBoolean, jsNumNotInt, jsInterceptor); + rule(jsBoolean, jsInteger, jsTrustedGetRuntimeType); + rule(jsBoolean, jsNumNotInt, jsTrustedGetRuntimeType); rule(jsBoolean, jsIndexable, objectType); - rule(jsBoolean, jsString, jsInterceptor); + rule(jsBoolean, jsString, jsTrustedGetRuntimeType); rule(jsBoolean, jsReadableArray, jsInterceptor); rule(jsBoolean, jsMutableArray, jsInterceptor); rule(jsBoolean, jsExtendableArray, jsInterceptor); @@ -182,9 +184,9 @@ rule(jsBoolean, potentialString, dynamicType); rule(jsBoolean, jsBooleanOrNull, jsBooleanOrNull); rule(jsBoolean, jsNumberOrNull, jsInterceptorOrNull); - rule(jsBoolean, jsIntegerOrNull, jsInterceptorOrNull); - rule(jsBoolean, jsNumNotIntOrNull, jsInterceptorOrNull); - rule(jsBoolean, jsStringOrNull, jsInterceptorOrNull); + rule(jsBoolean, jsIntegerOrNull, jsTrustedGetRuntimeTypeOrNull); + rule(jsBoolean, jsNumNotIntOrNull, jsTrustedGetRuntimeTypeOrNull); + rule(jsBoolean, jsStringOrNull, jsTrustedGetRuntimeTypeOrNull); rule(jsBoolean, nullType, jsBooleanOrNull); rule(jsBoolean, jsFixedArray, jsInterceptor); @@ -221,7 +223,7 @@ rule(jsInteger, nonPrimitive2, objectType); rule(jsInteger, potentialArray, dynamicType); rule(jsInteger, potentialString, dynamicType); - rule(jsInteger, jsBooleanOrNull, jsInterceptorOrNull); + rule(jsInteger, jsBooleanOrNull, jsTrustedGetRuntimeTypeOrNull); rule(jsInteger, jsNumberOrNull, jsNumberOrNull); rule(jsInteger, jsIntegerOrNull, jsIntegerOrNull); rule(jsInteger, jsNumNotIntOrNull, jsNumberOrNull); @@ -240,7 +242,7 @@ rule(jsNumNotInt, nonPrimitive2, objectType); rule(jsNumNotInt, potentialArray, dynamicType); rule(jsNumNotInt, potentialString, dynamicType); - rule(jsNumNotInt, jsBooleanOrNull, jsInterceptorOrNull); + rule(jsNumNotInt, jsBooleanOrNull, jsTrustedGetRuntimeTypeOrNull); rule(jsNumNotInt, jsNumberOrNull, jsNumberOrNull); rule(jsNumNotInt, jsIntegerOrNull, jsNumberOrNull); rule(jsNumNotInt, jsNumNotIntOrNull, jsNumNotIntOrNull); @@ -275,7 +277,7 @@ rule(jsString, nonPrimitive2, objectType); rule(jsString, potentialArray, dynamicType); rule(jsString, potentialString, potentialString); - rule(jsString, jsBooleanOrNull, jsInterceptorOrNull); + rule(jsString, jsBooleanOrNull, jsTrustedGetRuntimeTypeOrNull); rule(jsString, jsNumberOrNull, jsInterceptorOrComparableOrNull); rule(jsString, jsIntegerOrNull, jsInterceptorOrComparableOrNull); rule(jsString, jsNumNotIntOrNull, jsInterceptorOrComparableOrNull); @@ -383,9 +385,9 @@ rule(jsBooleanOrNull, jsBooleanOrNull, jsBooleanOrNull); rule(jsBooleanOrNull, jsNumberOrNull, jsInterceptorOrNull); - rule(jsBooleanOrNull, jsIntegerOrNull, jsInterceptorOrNull); - rule(jsBooleanOrNull, jsNumNotIntOrNull, jsInterceptorOrNull); - rule(jsBooleanOrNull, jsStringOrNull, jsInterceptorOrNull); + rule(jsBooleanOrNull, jsIntegerOrNull, jsTrustedGetRuntimeTypeOrNull); + rule(jsBooleanOrNull, jsNumNotIntOrNull, jsTrustedGetRuntimeTypeOrNull); + rule(jsBooleanOrNull, jsStringOrNull, jsTrustedGetRuntimeTypeOrNull); rule(jsBooleanOrNull, nullType, jsBooleanOrNull); rule(jsBooleanOrNull, jsFixedArray, jsInterceptorOrNull); @@ -782,6 +784,9 @@ LibraryEntity coreLibrary = commonElements.coreLibrary; patternClass = elementEnvironment.lookupClass(coreLibrary, 'Pattern'); + final trustedGetRuntimeTypeInterface = elementEnvironment.lookupClass( + commonElements.rtiLibrary, 'TrustedGetRuntimeType')!; + nonPrimitive1 = TypeMask.nonNullSubtype(closedWorld.commonElements.mapClass, closedWorld); nonPrimitive2 = TypeMask.nonNullSubtype( @@ -791,6 +796,8 @@ potentialString = TypeMask.subtype(patternClass, closedWorld); jsInterceptor = TypeMask.nonNullSubclass( closedWorld.commonElements.jsInterceptorClass, closedWorld); + jsTrustedGetRuntimeType = + TypeMask.nonNullSubtype(trustedGetRuntimeTypeInterface, closedWorld); jsArrayOrNull = TypeMask.subclass(closedWorld.commonElements.jsArrayClass, closedWorld); jsReadableArray = TypeMask.nonNullSubclass( @@ -817,6 +824,8 @@ closedWorld.commonElements.jsIndexableClass, closedWorld); jsInterceptorOrNull = TypeMask.subclass( closedWorld.commonElements.jsInterceptorClass, closedWorld); + jsTrustedGetRuntimeTypeOrNull = + TypeMask.subtype(trustedGetRuntimeTypeInterface, closedWorld); jsStringOrNull = TypeMask.exact(closedWorld.commonElements.jsStringClass, closedWorld); jsString = TypeMask.nonNullExact(
diff --git a/pkg/compiler/test/rti/emission/map_literal.dart b/pkg/compiler/test/rti/emission/map_literal.dart index 7803471..f3ec553 100644 --- a/pkg/compiler/test/rti/emission/map_literal.dart +++ b/pkg/compiler/test/rti/emission/map_literal.dart
@@ -12,7 +12,7 @@ /*spec.class: global#double:checkedInstance,instance,typeArgument*/ -/*class: global#JSNumNotInt:checks=[],instance*/ +/*class: global#JSNumNotInt:checks=[$isTrustedGetRuntimeType],instance*/ main() { <int, double>{}[0] = 0.5;
diff --git a/sdk/lib/_internal/js_runtime/lib/core_patch.dart b/sdk/lib/_internal/js_runtime/lib/core_patch.dart index 116cad6..ae38294 100644 --- a/sdk/lib/_internal/js_runtime/lib/core_patch.dart +++ b/sdk/lib/_internal/js_runtime/lib/core_patch.dart
@@ -24,11 +24,12 @@ getTraceFromException, RuntimeError, wrapException, - wrapZoneUnaryCallback; + wrapZoneUnaryCallback, + TrustedGetRuntimeType; import 'dart:_foreign_helper' show JS; import 'dart:_native_typed_data' show NativeUint8List; -import 'dart:_rti' show getRuntimeType; +import 'dart:_rti' show getRuntimeTypeOfDartObject; import 'dart:convert' show Encoding, utf8; import 'dart:typed_data' show Endian, Uint8List, Uint16List; @@ -69,7 +70,7 @@ } @patch - Type get runtimeType => getRuntimeType(this); + Type get runtimeType => getRuntimeTypeOfDartObject(this); } @patch
diff --git a/sdk/lib/_internal/js_runtime/lib/interceptors.dart b/sdk/lib/_internal/js_runtime/lib/interceptors.dart index bb91148..7f91159 100644 --- a/sdk/lib/_internal/js_runtime/lib/interceptors.dart +++ b/sdk/lib/_internal/js_runtime/lib/interceptors.dart
@@ -8,7 +8,7 @@ show DISPATCH_PROPERTY_NAME, TYPE_TO_INTERCEPTOR_MAP; import 'dart:collection' hide LinkedList, LinkedListEntry; -import 'dart:_foreign_helper' show JS_GET_FLAG; +import 'dart:_foreign_helper' show JS_GET_FLAG, TYPE_REF; import 'dart:_internal' hide Symbol; import "dart:_internal" as _symbol_dev show Symbol; import 'dart:_js_helper' @@ -52,7 +52,12 @@ JS_INTERCEPTOR_CONSTANT, JS_STRING_CONCAT; -import 'dart:_rti' show getRuntimeType; +import 'dart:_rti' + show + createRuntimeType, + getRuntimeTypeOfArray, + getRuntimeTypeOfInterceptorNotArray, + TrustedGetRuntimeType; import 'dart:math' show Random, ln2; @@ -338,11 +343,12 @@ throw new NoSuchMethodError.withInvocation(this, invocation); } - Type get runtimeType => getRuntimeType(this); + Type get runtimeType => + getRuntimeTypeOfInterceptorNotArray(getInterceptor(this), this); } /// The interceptor class for [bool]. -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]. @@ -358,7 +364,8 @@ // positions, including the low bit, so they are different mod 2^k. int get hashCode => this ? (2 * 3 * 23 * 3761) : (269 * 811); - Type get runtimeType => bool; + // Same as `=> bool`, but without a constant-pool object. + Type get runtimeType => createRuntimeType(TYPE_REF<bool>()); } /// The interceptor class for [Null]. @@ -366,7 +373,7 @@ /// This class defines implementations for *all* methods on [Object] since /// the methods on Object assume the receiver is non-null. This means that /// JSNull will always be in the interceptor set for methods defined on Object. -final class JSNull extends Interceptor implements Null { +final class JSNull extends Interceptor implements Null, TrustedGetRuntimeType { const JSNull(); external bool operator ==(Object other); @@ -376,10 +383,8 @@ int get hashCode => 0; - // The spec guarantees that `null` is the singleton instance of the `Null` - // class. In the mirrors library we also have to patch the `type` getter to - // special case `null`. - Type get runtimeType => Null; + // Same as `=> Null`, but without a constant-pool object. + Type get runtimeType => createRuntimeType(TYPE_REF<Null>()); dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); }
diff --git a/sdk/lib/_internal/js_runtime/lib/js_array.dart b/sdk/lib/_internal/js_runtime/lib/js_array.dart index f18f169..3a3fb27 100644 --- a/sdk/lib/_internal/js_runtime/lib/js_array.dart +++ b/sdk/lib/_internal/js_runtime/lib/js_array.dart
@@ -762,6 +762,10 @@ if (this.isEmpty) throw IterableElementError.noElement(); this[this.length - 1] = element; } + + // Specialized version of `get runtimeType` is needed here so that + // `Interceptor.runtimeType` can avoid testing for `JSArray`. + Type get runtimeType => getRuntimeTypeOfArray(this); } /// Dummy subclasses that allow the backend to track more precise
diff --git a/sdk/lib/_internal/js_runtime/lib/js_helper.dart b/sdk/lib/_internal/js_runtime/lib/js_helper.dart index a3f7ae4..e351e13 100644 --- a/sdk/lib/_internal/js_runtime/lib/js_helper.dart +++ b/sdk/lib/_internal/js_runtime/lib/js_helper.dart
@@ -66,6 +66,7 @@ evalInInstance, evaluateRtiForRecord, getRuntimeType, + getRuntimeTypeOfClosure, getRuntimeTypeOfRecord, getTypeFromTypesTable, instanceTypeName, @@ -2419,6 +2420,8 @@ if (name == null) name = 'unknown'; return "Closure '${unminifyOrTag(name)}'"; } + + Type get runtimeType => newRti.getRuntimeTypeOfClosure(this); } /// This is called by the fragment emitter.
diff --git a/sdk/lib/_internal/js_runtime/lib/js_number.dart b/sdk/lib/_internal/js_runtime/lib/js_number.dart index 288c0bb..27dc231 100644 --- a/sdk/lib/_internal/js_runtime/lib/js_number.dart +++ b/sdk/lib/_internal/js_runtime/lib/js_number.dart
@@ -477,7 +477,8 @@ return JS('bool', '# >= #', this, other); } - Type get runtimeType => num; + // Same as `=> num;`, but without a constant-pool object. + Type get runtimeType => createRuntimeType(TYPE_REF<num>()); } /// The interceptor class for [int]s. @@ -486,7 +487,7 @@ /// JavaScript all numbers are doubles, so while we want to treat `2.0` as an /// integer for some operations, its interceptor should answer `true` to `is /// double`. -final class JSInt extends JSNumber implements int { +final class JSInt extends JSNumber implements int, TrustedGetRuntimeType { const JSInt(); @override @@ -683,15 +684,19 @@ return _binaryGcd(x, y, false); } - Type get runtimeType => int; + // Same as `=> int;`, but without a constant-pool object. + Type get runtimeType => createRuntimeType(TYPE_REF<int>()); int operator ~() => JS('JSUInt32', r'(~#) >>> 0', this); } /// Interceptor for JavaScript values that are not a subclass of [JSInt]. -final class JSNumNotInt extends JSNumber implements double { +final class JSNumNotInt extends JSNumber + implements double, TrustedGetRuntimeType { const JSNumNotInt(); - Type get runtimeType => double; + + // Same as `=> double;`, but without a constant-pool object. + Type get runtimeType => createRuntimeType(TYPE_REF<double>()); } final class JSPositiveInt extends JSInt {}
diff --git a/sdk/lib/_internal/js_runtime/lib/js_string.dart b/sdk/lib/_internal/js_runtime/lib/js_string.dart index 14ce9c3..0c09013 100644 --- a/sdk/lib/_internal/js_runtime/lib/js_string.dart +++ b/sdk/lib/_internal/js_runtime/lib/js_string.dart
@@ -8,7 +8,8 @@ /// class as an interceptor, and changes references to [:this:] to /// actually use the receiver of the method, which is generated as an extra /// argument added to each member. -final class JSString extends Interceptor implements String, JSIndexable { +final class JSString extends Interceptor + implements String, JSIndexable, TrustedGetRuntimeType { const JSString(); @pragma('dart2js:noInline') @@ -192,8 +193,10 @@ case 0x0D: case 0x20: case 0x85: - case 0xA0: return true; - default: return false; + case 0xA0: + return true; + default: + return false; } } switch (codeUnit) { @@ -214,8 +217,10 @@ case 0x202F: case 0x205F: case 0x3000: - case 0xFEFF: return true; - default: return false; + case 0xFEFF: + return true; + default: + return false; } } @@ -455,7 +460,8 @@ return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); } - Type get runtimeType => String; + // Same as `=> String;`, but without a constant-pool object. + Type get runtimeType => createRuntimeType(TYPE_REF<String>()); int get length => JS('int', r'#.length', this);
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 e54e7b7..011e68f 100644 --- a/sdk/lib/_internal/js_runtime/lib/native_typed_data.dart +++ b/sdk/lib/_internal/js_runtime/lib/native_typed_data.dart
@@ -21,12 +21,15 @@ diagnoseIndexError, diagnoseRangeError; import 'dart:_foreign_helper' show JS; +import 'dart:_rti' show TrustedGetRuntimeType; + import 'dart:math' as Math; import 'dart:typed_data'; @Native('ArrayBuffer') -final class NativeByteBuffer extends JavaScriptObject implements ByteBuffer { +final class NativeByteBuffer extends JavaScriptObject + implements ByteBuffer, TrustedGetRuntimeType { @JSName('byteLength') int get lengthInBytes native; @@ -104,7 +107,7 @@ /// space- and time-efficient than the default [List] implementation. final class NativeFloat32x4List extends Object with ListMixin<Float32x4>, FixedLengthListMixin<Float32x4> - implements Float32x4List { + implements Float32x4List, TrustedGetRuntimeType { final Float32List _storage; /// Creates a [Float32x4List] of the specified length (in elements), @@ -176,7 +179,7 @@ /// space- and time-efficient than the default [List] implementation. final class NativeInt32x4List extends Object with ListMixin<Int32x4>, FixedLengthListMixin<Int32x4> - implements Int32x4List { + implements Int32x4List, TrustedGetRuntimeType { final Int32List _storage; /// Creates a [Int32x4List] of the specified length (in elements), @@ -248,7 +251,7 @@ /// space- and time-efficient than the default [List] implementation. final class NativeFloat64x2List extends Object with ListMixin<Float64x2>, FixedLengthListMixin<Float64x2> - implements Float64x2List { + implements Float64x2List, TrustedGetRuntimeType { final Float64List _storage; /// Creates a [Float64x2List] of the specified length (in elements), @@ -382,7 +385,8 @@ } @Native('DataView') -final class NativeByteData extends NativeTypedData implements ByteData { +final class NativeByteData extends NativeTypedData + implements ByteData, TrustedGetRuntimeType { /// Creates a [ByteData] of the specified length (in elements), all of /// whose elements are initially zero. factory NativeByteData(int length) => _create1(_checkLength(length)); @@ -739,7 +743,7 @@ @Native('Float32Array') final class NativeFloat32List extends NativeTypedArrayOfDouble - implements Float32List { + implements Float32List, TrustedGetRuntimeType { factory NativeFloat32List(int length) => _createLength(_checkLength(length)); factory NativeFloat32List.fromList(List<double> elements) => @@ -775,7 +779,7 @@ @Native('Float64Array') final class NativeFloat64List extends NativeTypedArrayOfDouble - implements Float64List { + implements Float64List, TrustedGetRuntimeType { factory NativeFloat64List(int length) => _createLength(_checkLength(length)); factory NativeFloat64List.fromList(List<double> elements) => @@ -810,7 +814,8 @@ } @Native('Int16Array') -final class NativeInt16List extends NativeTypedArrayOfInt implements Int16List { +final class NativeInt16List extends NativeTypedArrayOfInt + implements Int16List, TrustedGetRuntimeType { factory NativeInt16List(int length) => _createLength(_checkLength(length)); factory NativeInt16List.fromList(List<int> elements) => @@ -850,7 +855,8 @@ } @Native('Int32Array') -final class NativeInt32List extends NativeTypedArrayOfInt implements Int32List { +final class NativeInt32List extends NativeTypedArrayOfInt + implements Int32List, TrustedGetRuntimeType { factory NativeInt32List(int length) => _createLength(_checkLength(length)); factory NativeInt32List.fromList(List<int> elements) => @@ -890,7 +896,8 @@ } @Native('Int8Array') -final class NativeInt8List extends NativeTypedArrayOfInt implements Int8List { +final class NativeInt8List extends NativeTypedArrayOfInt + implements Int8List, TrustedGetRuntimeType { factory NativeInt8List(int length) => _createLength(_checkLength(length)); factory NativeInt8List.fromList(List<int> elements) => @@ -934,7 +941,7 @@ @Native('Uint16Array') final class NativeUint16List extends NativeTypedArrayOfInt - implements Uint16List { + implements Uint16List, TrustedGetRuntimeType { factory NativeUint16List(int length) => _createLength(_checkLength(length)); factory NativeUint16List.fromList(List<int> list) => @@ -975,7 +982,7 @@ @Native('Uint32Array') final class NativeUint32List extends NativeTypedArrayOfInt - implements Uint32List { + implements Uint32List, TrustedGetRuntimeType { factory NativeUint32List(int length) => _createLength(_checkLength(length)); factory NativeUint32List.fromList(List<int> elements) => @@ -1016,7 +1023,7 @@ @Native('Uint8ClampedArray,CanvasPixelArray') final class NativeUint8ClampedList extends NativeTypedArrayOfInt - implements Uint8ClampedList { + implements Uint8ClampedList, TrustedGetRuntimeType { factory NativeUint8ClampedList(int length) => _createLength(_checkLength(length)); @@ -1071,7 +1078,8 @@ // the potential for Uint8ClampedArray to 'accidentally' pick up the // dispatch record for Uint8List. @Native('Uint8Array,!nonleaf') -final class NativeUint8List extends NativeTypedArrayOfInt implements Uint8List { +final class NativeUint8List extends NativeTypedArrayOfInt + implements Uint8List, TrustedGetRuntimeType { factory NativeUint8List(int length) => _createLength(_checkLength(length)); factory NativeUint8List.fromList(List<int> elements) =>
diff --git a/sdk/lib/_internal/js_shared/lib/rti.dart b/sdk/lib/_internal/js_shared/lib/rti.dart index 88cce4b..7a1b76a 100644 --- a/sdk/lib/_internal/js_shared/lib/rti.dart +++ b/sdk/lib/_internal/js_shared/lib/rti.dart
@@ -27,6 +27,36 @@ 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'; @@ -834,13 +864,64 @@ return _Utils.asRti(type); } -/// Called from [Object.runtimeType] and [Interceptor.runtimeType]. -Type getRuntimeType(Object? object) { - Rti rti = _instanceFunctionType(object) ?? instanceType(object); +/// Called from [Object.runtimeType]. +/// +/// [Object.runtimeType] is shadowed by overrides so that [object] is always an +/// ordinary object and never an Array, Closure or Record. +@pragma('dart2js:never-inline') +Type getRuntimeTypeOfDartObject(Object? object) { + Rti rti = _instanceType(object); return createRuntimeType(rti); } -/// Called from [_Record.runtimeType] +/// Called from [JSArray.runtimeType]. +Type getRuntimeTypeOfArray(Object? array) { + Rti rti = _getRuntimeTypeOfArrayAsRti(array); + return createRuntimeType(rti); +} + +Rti _getRuntimeTypeOfArrayAsRti(Object? array) { + Rti rti = _arrayInstanceType(array); + + // TODO(http://dartbug.com/51894): + // + // There are two reasonable types: `JSArray<E>` and `List<E>`. + // + // Either could be achieved by making JSArray implement TrustedGetRuntimeType + // and changing the definition of JSArray.runtimeType: + // + // Type get runtimeType => JSArray<E>; + // Type get runtimeType => List<E>; + // + // - `JSArray<E>`, the internal type, is stored on the array. There is an + // SSA-level optimization that recognizes that type expression `JSArray<E>` + // is just reconstructing the value, so we get the same operations as this + // method. + // + // - `List<E>` would construct a derived type. This would be a little slower, + // but not terrible, since in the steady state, the `List<E>` constructed + // via a recipe is cached in a map on the stored Rti. + // + // The reason we don't just define a plain and understandable method is that + // the presence of type variable `E` defeats the type-erasure optimization + // when `.runtimeType` is used. + return rti; +} + +/// Called from [Closure.runtimeType]. +Type getRuntimeTypeOfClosure(Object? closure) { + // If there is no function type, use the interface type. + Rti rti = closureFunctionType(closure) ?? instanceType(closure); + return createRuntimeType(rti); +} + +/// Called from [Interceptor.runtimeType]. +Type getRuntimeTypeOfInterceptorNotArray(Object? interceptor, Object? object) { + Rti rti = _instanceTypeFromConstructor(interceptor); + return createRuntimeType(rti); +} + +/// Called from [_Record.runtimeType]. Type getRuntimeTypeOfRecord(Object record) { Rti recordRti = records.getRtiForRecord(record); return createRuntimeType(recordRti); @@ -850,7 +931,14 @@ /// or interface type of [object]. Rti _structuralTypeOf(Object? object) { if (object is Record) return records.getRtiForRecord(object); - return _instanceFunctionType(object) ?? instanceType(object); + final functionRti = _instanceFunctionType(object); + if (functionRti != null) return functionRti; + if (object is TrustedGetRuntimeType) { + final type = object.runtimeType; + return _Utils.as_Type(type)._rti; + } + if (_Utils.isArray(object)) return _getRuntimeTypeOfArrayAsRti(object); + return instanceType(object); } /// Called from generated code. @@ -3409,6 +3497,7 @@ static String asString(Object? o) => JS('String', '#', o); static Rti asRti(Object? s) => JS('Rti', '#', s); static Rti? asRtiOrNull(Object? s) => JS('Rti|Null', '#', s); + static _Type as_Type(Object? o) => JS('_Type', '#', o); static bool isString(Object? o) => JS('bool', 'typeof # == "string"', o); static bool isNum(Object? o) => JS('bool', 'typeof # == "number"', o);