[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>
This commit is contained in:
Nicholas Shahan 2023-05-12 17:23:07 +00:00 committed by Commit Queue
parent 42c79709a4
commit f2aa312e17
17 changed files with 222 additions and 103 deletions

View file

@ -785,7 +785,7 @@ runTests() async {
patternClass = elementEnvironment.lookupClass(coreLibrary, 'Pattern');
final trustedGetRuntimeTypeInterface = elementEnvironment.lookupClass(
commonElements.rtiLibrary, 'TrustedGetRuntimeType')!;
commonElements.jsHelperLibrary!, 'TrustedGetRuntimeType')!;
nonPrimitive1 =
TypeMask.nonNullSubtype(closedWorld.commonElements.mapClass, closedWorld);

View file

@ -3523,10 +3523,10 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
// 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 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
[_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 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
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),

View file

@ -157,9 +157,8 @@ class _TypeRecipeVisitor extends DartTypeVisitor<String> {
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 @@ class _TypeRecipeVisitor extends DartTypeVisitor<String> {
}
@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) {

View file

@ -13,6 +13,12 @@ argumentError(value) {
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);
}

View file

@ -96,9 +96,18 @@ final class RecordImpl implements Record {
/// 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 _shapes = JS('!', 'new Map()');
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 @@ Object registerRecord(@notNull String shapeRecipe, @notNull int positionals,
}
}
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);
}

View file

@ -2283,12 +2283,11 @@ RecordType recordType(@notNull Shape shape, @notNull List types) =>
/// 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);
}

View file

@ -30,7 +30,7 @@ abstract class Interceptor {
* 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].

View file

@ -11,7 +11,8 @@ part of dart._interceptors;
* 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();
/**

View file

@ -7,7 +7,7 @@ library dart._js_helper;
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 @@ import 'dart:_internal'
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 @@ void Function(T)? wrapZoneUnaryCallback<T>(void Function(T)? callback) {
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 {}

View file

@ -16,7 +16,8 @@ final class JSNumNotInt extends JSNumber implements double {}
///
/// 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

View file

@ -12,7 +12,7 @@ part of dart._interceptors;
*/
@JsPeerInterface(name: 'String')
final class JSString extends Interceptor
implements String, JSIndexable<String> {
implements String, JSIndexable<String>, TrustedGetRuntimeType {
const JSString();
@notNull
@ -207,8 +207,10 @@ final class JSString extends Interceptor
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 @@ final class JSString extends Interceptor
case 0x202F:
case 0x205F:
case 0x3000:
case 0xFEFF: return true;
default: return false;
case 0xFEFF:
return true;
default:
return false;
}
}

View file

@ -41,7 +41,8 @@ import 'dart:_js_helper'
throwConcurrentModificationError,
lookupAndCacheInterceptor,
StringMatch,
firstMatchAfter;
firstMatchAfter,
TrustedGetRuntimeType;
import 'dart:_foreign_helper'
show
@ -56,8 +57,7 @@ import 'dart:_rti'
show
createRuntimeType,
getRuntimeTypeOfArray,
getRuntimeTypeOfInterceptorNotArray,
TrustedGetRuntimeType;
getRuntimeTypeOfInterceptorNotArray;
import 'dart:math' show Random, ln2;

View file

@ -3205,3 +3205,35 @@ void Function(T)? wrapZoneUnaryCallback<T>(void Function(T)? callback) {
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 {}

View file

@ -19,9 +19,9 @@ import 'dart:_js_helper'
Native,
Returns,
diagnoseIndexError,
diagnoseRangeError;
diagnoseRangeError,
TrustedGetRuntimeType;
import 'dart:_foreign_helper' show JS;
import 'dart:_rti' show TrustedGetRuntimeType;
import 'dart:math' as Math;

View file

@ -22,41 +22,12 @@ import 'dart:_interceptors'
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 @@ Rti _structuralTypeOf(Object? object) {
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;
}

View file

@ -239,7 +239,7 @@ Value:
{
"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 @@ Value:
{
"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

View file

@ -239,7 +239,7 @@ Value:
{
"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 @@ Value:
{
"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