mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 16:41:07 +00:00
[dart2js,js_runtime] Record type recipes and runtime subtype test
Change-Id: I54adb8b6184a19667a296a670fecb37d7ce9dce7 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/262126 Reviewed-by: Mayank Patke <fishythefish@google.com> Commit-Queue: Stephen Adams <sra@google.com>
This commit is contained in:
parent
16d06dac06
commit
edf0da2df4
|
@ -2304,6 +2304,24 @@ abstract class DartTypes {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Records
|
||||||
|
//
|
||||||
|
// TODO(50081): Reference rules to updated specification
|
||||||
|
// https://github.com/dart-lang/language/blob/master/resources/type-system/subtyping.md#rules
|
||||||
|
//
|
||||||
|
// TODO(50081): record is subtype of interface `Record`.
|
||||||
|
if (s is RecordType) {
|
||||||
|
if (t is! RecordType) return false;
|
||||||
|
if (s.shape != t.shape) return false;
|
||||||
|
List<DartType> sFields = s.fields;
|
||||||
|
List<DartType> tFields = t.fields;
|
||||||
|
assert(sFields.length == tFields.length); // Guaranteed by shape.
|
||||||
|
for (int i = 0; i < sFields.length; i++) {
|
||||||
|
if (!_isSubtype(sFields[i], tFields[i], env)) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -741,6 +741,12 @@ class ScopeModelBuilder extends ir.Visitor<EvaluationComplexity>
|
||||||
return visitNodes(node.typeArguments);
|
return visitNodes(node.typeArguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
EvaluationComplexity visitRecordType(ir.RecordType node) {
|
||||||
|
EvaluationComplexity complexity = visitNodes(node.positional);
|
||||||
|
return complexity.combine(visitNodes(node.named));
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EvaluationComplexity visitFutureOrType(ir.FutureOrType node) {
|
EvaluationComplexity visitFutureOrType(ir.FutureOrType node) {
|
||||||
return visitNode(node.typeArgument);
|
return visitNode(node.typeArgument);
|
||||||
|
|
|
@ -294,8 +294,19 @@ class _RecipeGenerator implements DartTypeVisitor<void, void> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void visitRecordType(RecordType type, _) {
|
void visitRecordType(RecordType type, _) {
|
||||||
// TODO(49718): Implement Rti recipes for records.
|
_emitCode(Recipe.startRecord);
|
||||||
throw UnimplementedError();
|
// Partial shape tag. The full shape is this plus the number of fields.
|
||||||
|
_emitStringUnescaped(type.shape.fieldNames.join(Recipe.separatorString));
|
||||||
|
_emitCode(Recipe.startFunctionArguments);
|
||||||
|
bool first = true;
|
||||||
|
for (DartType field in type.fields) {
|
||||||
|
if (!first) {
|
||||||
|
_emitCode(Recipe.separator);
|
||||||
|
}
|
||||||
|
visit(field, _);
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
_emitCode(Recipe.endFunctionArguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -100,6 +100,15 @@ class DartTypeNodeWriter
|
||||||
visitTypes(node.typeArguments, functionTypeVariables);
|
visitTypes(node.typeArguments, functionTypeVariables);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void visitRecordType(
|
||||||
|
ir.RecordType node, List<ir.TypeParameter> functionTypeVariables) {
|
||||||
|
_sink.writeEnum(DartTypeNodeKind.recordType);
|
||||||
|
_sink.writeEnum(node.declaredNullability);
|
||||||
|
visitTypes(node.positional, functionTypeVariables);
|
||||||
|
_visitNamedTypes(node.named, functionTypeVariables);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void visitFutureOrType(
|
void visitFutureOrType(
|
||||||
ir.FutureOrType node, List<ir.TypeParameter> functionTypeVariables) {
|
ir.FutureOrType node, List<ir.TypeParameter> functionTypeVariables) {
|
||||||
|
@ -125,13 +134,18 @@ class DartTypeNodeWriter
|
||||||
_sink.writeEnum(node.nullability);
|
_sink.writeEnum(node.nullability);
|
||||||
_sink.writeInt(node.requiredParameterCount);
|
_sink.writeInt(node.requiredParameterCount);
|
||||||
visitTypes(node.positionalParameters, functionTypeVariables);
|
visitTypes(node.positionalParameters, functionTypeVariables);
|
||||||
_sink.writeInt(node.namedParameters.length);
|
_visitNamedTypes(node.namedParameters, functionTypeVariables);
|
||||||
for (ir.NamedType parameter in node.namedParameters) {
|
_sink.end(functionTypeNodeTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _visitNamedTypes(
|
||||||
|
List<ir.NamedType> named, List<ir.TypeParameter> functionTypeVariables) {
|
||||||
|
_sink.writeInt(named.length);
|
||||||
|
for (ir.NamedType parameter in named) {
|
||||||
_sink.writeString(parameter.name);
|
_sink.writeString(parameter.name);
|
||||||
_sink.writeBool(parameter.isRequired);
|
_sink.writeBool(parameter.isRequired);
|
||||||
_sink._writeDartTypeNode(parameter.type, functionTypeVariables);
|
_sink._writeDartTypeNode(parameter.type, functionTypeVariables);
|
||||||
}
|
}
|
||||||
_sink.end(functionTypeNodeTag);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -882,14 +882,7 @@ class DataSourceReader {
|
||||||
int requiredParameterCount = readInt();
|
int requiredParameterCount = readInt();
|
||||||
List<ir.DartType> positionalParameters =
|
List<ir.DartType> positionalParameters =
|
||||||
_readDartTypeNodes(functionTypeVariables);
|
_readDartTypeNodes(functionTypeVariables);
|
||||||
int namedParameterCount = readInt();
|
final namedParameters = _readNamedTypeNodes(functionTypeVariables);
|
||||||
final namedParameters =
|
|
||||||
List<ir.NamedType>.generate(namedParameterCount, (index) {
|
|
||||||
String name = readString();
|
|
||||||
bool isRequired = readBool();
|
|
||||||
ir.DartType type = _readDartTypeNode(functionTypeVariables)!;
|
|
||||||
return ir.NamedType(name, type, isRequired: isRequired);
|
|
||||||
}, growable: false);
|
|
||||||
end(functionTypeNodeTag);
|
end(functionTypeNodeTag);
|
||||||
return ir.FunctionType(positionalParameters, returnType, nullability,
|
return ir.FunctionType(positionalParameters, returnType, nullability,
|
||||||
namedParameters: namedParameters,
|
namedParameters: namedParameters,
|
||||||
|
@ -914,6 +907,12 @@ class DataSourceReader {
|
||||||
List<ir.DartType> typeArguments =
|
List<ir.DartType> typeArguments =
|
||||||
_readDartTypeNodes(functionTypeVariables);
|
_readDartTypeNodes(functionTypeVariables);
|
||||||
return ExactInterfaceType(cls, nullability, typeArguments);
|
return ExactInterfaceType(cls, nullability, typeArguments);
|
||||||
|
case DartTypeNodeKind.recordType:
|
||||||
|
ir.Nullability nullability = readEnum(ir.Nullability.values);
|
||||||
|
List<ir.DartType> positional =
|
||||||
|
_readDartTypeNodes(functionTypeVariables);
|
||||||
|
List<ir.NamedType> named = _readNamedTypeNodes(functionTypeVariables);
|
||||||
|
return ir.RecordType(positional, named, nullability);
|
||||||
case DartTypeNodeKind.typedef:
|
case DartTypeNodeKind.typedef:
|
||||||
ir.Typedef typedef = readTypedefNode();
|
ir.Typedef typedef = readTypedefNode();
|
||||||
ir.Nullability nullability = readEnum(ir.Nullability.values);
|
ir.Nullability nullability = readEnum(ir.Nullability.values);
|
||||||
|
@ -931,6 +930,18 @@ class DataSourceReader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<ir.NamedType> _readNamedTypeNodes(
|
||||||
|
List<ir.TypeParameter> functionTypeVariables) {
|
||||||
|
int count = readInt();
|
||||||
|
if (count == 0) return const [];
|
||||||
|
return List<ir.NamedType>.generate(count, (index) {
|
||||||
|
String name = readString();
|
||||||
|
bool isRequired = readBool();
|
||||||
|
ir.DartType type = _readDartTypeNode(functionTypeVariables)!;
|
||||||
|
return ir.NamedType(name, type, isRequired: isRequired);
|
||||||
|
}, growable: false);
|
||||||
|
}
|
||||||
|
|
||||||
/// Reads a list of kernel type nodes from this data source.
|
/// Reads a list of kernel type nodes from this data source.
|
||||||
///
|
///
|
||||||
/// This is a convenience method to be used together with
|
/// This is a convenience method to be used together with
|
||||||
|
|
|
@ -85,6 +85,7 @@ enum DartTypeNodeKind {
|
||||||
functionType,
|
functionType,
|
||||||
functionTypeVariable,
|
functionTypeVariable,
|
||||||
interfaceType,
|
interfaceType,
|
||||||
|
recordType,
|
||||||
typedef,
|
typedef,
|
||||||
dynamicType,
|
dynamicType,
|
||||||
invalidType,
|
invalidType,
|
||||||
|
|
|
@ -58,6 +58,9 @@ abstract class Recipe {
|
||||||
static const String genericFunctionTypeParameterIndexString =
|
static const String genericFunctionTypeParameterIndexString =
|
||||||
_circumflexString;
|
_circumflexString;
|
||||||
|
|
||||||
|
static const int startRecord = _plus;
|
||||||
|
static const String startRecordString = _plusString;
|
||||||
|
|
||||||
static const int extensionOp = _ampersand;
|
static const int extensionOp = _ampersand;
|
||||||
static const String extensionOpString = _ampersandString;
|
static const String extensionOpString = _ampersandString;
|
||||||
static const int pushNeverExtension = 0;
|
static const int pushNeverExtension = 0;
|
||||||
|
@ -198,6 +201,7 @@ abstract class Recipe {
|
||||||
requiredNameSeparatorString);
|
requiredNameSeparatorString);
|
||||||
test("genericFunctionTypeParameterIndex", genericFunctionTypeParameterIndex,
|
test("genericFunctionTypeParameterIndex", genericFunctionTypeParameterIndex,
|
||||||
genericFunctionTypeParameterIndexString);
|
genericFunctionTypeParameterIndexString);
|
||||||
|
test("startRecord", startRecord, startRecordString);
|
||||||
test("extensionOp", extensionOp, extensionOpString);
|
test("extensionOp", extensionOp, extensionOpString);
|
||||||
testExtension(
|
testExtension(
|
||||||
"pushNeverExtension", pushNeverExtension, pushNeverExtensionString);
|
"pushNeverExtension", pushNeverExtension, pushNeverExtensionString);
|
||||||
|
|
|
@ -184,9 +184,10 @@ class Rti {
|
||||||
static const int kindInterface = 9;
|
static const int kindInterface = 9;
|
||||||
// A vector of type parameters from enclosing functions and closures.
|
// A vector of type parameters from enclosing functions and closures.
|
||||||
static const int kindBinding = 10;
|
static const int kindBinding = 10;
|
||||||
static const int kindFunction = 11;
|
static const int kindRecord = 11;
|
||||||
static const int kindGenericFunction = 12;
|
static const int kindFunction = 12;
|
||||||
static const int kindGenericFunctionParameter = 13;
|
static const int kindGenericFunction = 13;
|
||||||
|
static const int kindGenericFunctionParameter = 14;
|
||||||
|
|
||||||
static bool _isUnionOfFunctionType(Rti rti) {
|
static bool _isUnionOfFunctionType(Rti rti) {
|
||||||
int kind = Rti._getKind(rti);
|
int kind = Rti._getKind(rti);
|
||||||
|
@ -202,6 +203,8 @@ class Rti {
|
||||||
/// - Underlying type for unary terms.
|
/// - Underlying type for unary terms.
|
||||||
/// - Class part of a type environment inside a generic class, or `null` for
|
/// - Class part of a type environment inside a generic class, or `null` for
|
||||||
/// type tuple.
|
/// type tuple.
|
||||||
|
/// - A tag that, together with the number of fields, distinguishes the shape
|
||||||
|
/// of a record type.
|
||||||
/// - Return type of a function type.
|
/// - Return type of a function type.
|
||||||
/// - Underlying function type for a generic function.
|
/// - Underlying function type for a generic function.
|
||||||
/// - de Bruijn index for a generic function parameter.
|
/// - de Bruijn index for a generic function parameter.
|
||||||
|
@ -217,6 +220,7 @@ class Rti {
|
||||||
/// - The type arguments of an interface type.
|
/// - The type arguments of an interface type.
|
||||||
/// - The type arguments from enclosing functions and closures for a
|
/// - The type arguments from enclosing functions and closures for a
|
||||||
/// kindBinding.
|
/// kindBinding.
|
||||||
|
/// - The field types of a record type.
|
||||||
/// - The [_FunctionParameters] of a function type.
|
/// - The [_FunctionParameters] of a function type.
|
||||||
/// - The type parameter bounds of a generic function.
|
/// - The type parameter bounds of a generic function.
|
||||||
Object? _rest;
|
Object? _rest;
|
||||||
|
@ -248,6 +252,16 @@ class Rti {
|
||||||
return JS('JSUnmodifiableArray', '#', _getRest(rti));
|
return JS('JSUnmodifiableArray', '#', _getRest(rti));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static String _getRecordPartialShapeTag(Rti rti) {
|
||||||
|
assert(_getKind(rti) == kindRecord);
|
||||||
|
return _Utils.asString(_getPrimary(rti));
|
||||||
|
}
|
||||||
|
|
||||||
|
static JSArray _getRecordFields(Rti rti) {
|
||||||
|
assert(_getKind(rti) == kindRecord);
|
||||||
|
return JS('JSUnmodifiableArray', '#', _getRest(rti));
|
||||||
|
}
|
||||||
|
|
||||||
static Rti _getStarArgument(Rti rti) {
|
static Rti _getStarArgument(Rti rti) {
|
||||||
assert(_getKind(rti) == kindStar);
|
assert(_getKind(rti) == kindStar);
|
||||||
return _Utils.asRti(_getPrimary(rti));
|
return _Utils.asRti(_getPrimary(rti));
|
||||||
|
@ -1270,6 +1284,37 @@ String _rtiArrayToString(Object? array, List<String>? genericContext) {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _recordRtiToString(Rti recordType, List<String>? genericContext) {
|
||||||
|
// For correctness of subtyping, the partial shape tag could be any encoding
|
||||||
|
// that maps different sets of names to different tags.
|
||||||
|
//
|
||||||
|
// Here we assume that the tag is a comma-separated list of names for the last
|
||||||
|
// N named fields.
|
||||||
|
String partialShape = Rti._getRecordPartialShapeTag(recordType);
|
||||||
|
Object? fields = Rti._getRecordFields(recordType);
|
||||||
|
if ('' == partialShape) {
|
||||||
|
// No named fields.
|
||||||
|
return '(' + _rtiArrayToString(fields, genericContext) + ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
int fieldCount = _Utils.arrayLength(fields);
|
||||||
|
Object names = _Utils.stringSplit(partialShape, ',');
|
||||||
|
int namesIndex = _Utils.arrayLength(names) - fieldCount; // Can be negative.
|
||||||
|
|
||||||
|
String s = '(', comma = '';
|
||||||
|
for (int i = 0; i < fieldCount; i++) {
|
||||||
|
s += comma;
|
||||||
|
comma = ', ';
|
||||||
|
if (namesIndex == 0) s += '{';
|
||||||
|
s += _rtiToString(_Utils.asRti(_Utils.arrayAt(fields, i)), genericContext);
|
||||||
|
if (namesIndex >= 0) {
|
||||||
|
s += ' ' + _Utils.asString(_Utils.arrayAt(names, namesIndex));
|
||||||
|
}
|
||||||
|
namesIndex++;
|
||||||
|
}
|
||||||
|
return s + '})';
|
||||||
|
}
|
||||||
|
|
||||||
String _functionRtiToString(Rti functionType, List<String>? genericContext,
|
String _functionRtiToString(Rti functionType, List<String>? genericContext,
|
||||||
{Object? bounds = null}) {
|
{Object? bounds = null}) {
|
||||||
String typeParametersText = '';
|
String typeParametersText = '';
|
||||||
|
@ -1416,6 +1461,10 @@ String _rtiToString(Rti rti, List<String>? genericContext) {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (kind == Rti.kindRecord) {
|
||||||
|
return _recordRtiToString(rti, genericContext);
|
||||||
|
}
|
||||||
|
|
||||||
if (kind == Rti.kindFunction) {
|
if (kind == Rti.kindFunction) {
|
||||||
return _functionRtiToString(rti, genericContext);
|
return _functionRtiToString(rti, genericContext);
|
||||||
}
|
}
|
||||||
|
@ -2049,6 +2098,36 @@ class _Universe {
|
||||||
return _installTypeTests(universe, rti);
|
return _installTypeTests(universe, rti);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static String _canonicalRecipeOfRecord(
|
||||||
|
String partialShapeTag, Object? fields) {
|
||||||
|
return _recipeJoin5(
|
||||||
|
Recipe.startRecordString,
|
||||||
|
partialShapeTag,
|
||||||
|
Recipe.startFunctionArgumentsString,
|
||||||
|
_canonicalRecipeJoin(fields),
|
||||||
|
Recipe.endFunctionArgumentsString);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Rti _lookupRecordRti(
|
||||||
|
Object? universe, String partialShapeTag, Object? fields) {
|
||||||
|
String key = _canonicalRecipeOfRecord(partialShapeTag, fields);
|
||||||
|
var cache = evalCache(universe);
|
||||||
|
var probe = _Utils.mapGet(cache, key);
|
||||||
|
if (probe != null) return _Utils.asRti(probe);
|
||||||
|
return _installRti(universe, key,
|
||||||
|
_createRecordRti(universe, partialShapeTag, fields, key));
|
||||||
|
}
|
||||||
|
|
||||||
|
static Rti _createRecordRti(
|
||||||
|
Object? universe, String partialShapeTag, Object? fields, String key) {
|
||||||
|
Rti rti = Rti.allocate();
|
||||||
|
Rti._setKind(rti, Rti.kindRecord);
|
||||||
|
Rti._setPrimary(rti, partialShapeTag);
|
||||||
|
Rti._setRest(rti, fields);
|
||||||
|
Rti._setCanonicalRecipe(rti, key);
|
||||||
|
return _installTypeTests(universe, rti);
|
||||||
|
}
|
||||||
|
|
||||||
static String _canonicalRecipeOfFunction(
|
static String _canonicalRecipeOfFunction(
|
||||||
Rti returnType, _FunctionParameters parameters) =>
|
Rti returnType, _FunctionParameters parameters) =>
|
||||||
_recipeJoin(Rti._getCanonicalRecipe(returnType),
|
_recipeJoin(Rti._getCanonicalRecipe(returnType),
|
||||||
|
@ -2413,11 +2492,12 @@ class _Parser {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Recipe.startFunctionArguments:
|
case Recipe.startFunctionArguments:
|
||||||
|
push(stack, gotoFunction);
|
||||||
pushStackFrame(parser, stack);
|
pushStackFrame(parser, stack);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Recipe.endFunctionArguments:
|
case Recipe.endFunctionArguments:
|
||||||
handleFunctionArguments(parser, stack);
|
handleArguments(parser, stack);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Recipe.startOptionalGroup:
|
case Recipe.startOptionalGroup:
|
||||||
|
@ -2436,6 +2516,10 @@ class _Parser {
|
||||||
handleNamedGroup(parser, stack);
|
handleNamedGroup(parser, stack);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case Recipe.startRecord:
|
||||||
|
i = handleStartRecord(parser, i, source, stack);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
JS('', 'throw "Bad character " + #', ch);
|
JS('', 'throw "Bad character " + #', ch);
|
||||||
}
|
}
|
||||||
|
@ -2511,24 +2595,40 @@ class _Parser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const int optionalPositionalSentinel = -1;
|
static const int optionalPositionalMarker = -1;
|
||||||
static const int namedSentinel = -2;
|
static const int namedMarker = -2;
|
||||||
|
static const int gotoFunction = -3;
|
||||||
|
static const int gotoRecord = -4;
|
||||||
|
|
||||||
static void handleFunctionArguments(Object? parser, Object? stack) {
|
static void handleArguments(Object? parser, Object? stack) {
|
||||||
var universe = _Parser.universe(parser);
|
var universe = _Parser.universe(parser);
|
||||||
_FunctionParameters parameters = _FunctionParameters.allocate();
|
Object? optionalPositional;
|
||||||
Object? optionalPositional = _Universe.sharedEmptyArray(universe);
|
Object? named;
|
||||||
Object? named = _Universe.sharedEmptyArray(universe);
|
|
||||||
|
// Parse the stack into a function type or a record type. A 'goto' marker is
|
||||||
|
// on the stack to distinguish between records and functions (similar to the
|
||||||
|
// GOTO table of an LR parser), and a marker tag is used for optional and
|
||||||
|
// named argument groups.
|
||||||
|
//
|
||||||
|
// Function types:
|
||||||
|
//
|
||||||
|
// R -3 <pos> T1 ... Tn -> R(T1,...,Tn)
|
||||||
|
// R -3 <pos> T1 ... Tn optional -1 -> R(T1,...,Tn, [optional...])
|
||||||
|
// R -3 <pos> T1 ... Tn named -2 -> R(T1,...,Tn, {named...}])
|
||||||
|
//
|
||||||
|
// Record types:
|
||||||
|
//
|
||||||
|
// shapeToken -4 <pos> T1 ... Tn -> (T1,...,Tn) with shapeToken
|
||||||
|
|
||||||
var head = pop(stack);
|
var head = pop(stack);
|
||||||
if (_Utils.isNum(head)) {
|
if (_Utils.isNum(head)) {
|
||||||
int sentinel = _Utils.asInt(head);
|
int sentinel = _Utils.asInt(head);
|
||||||
switch (sentinel) {
|
switch (sentinel) {
|
||||||
case optionalPositionalSentinel:
|
case optionalPositionalMarker:
|
||||||
optionalPositional = pop(stack);
|
optionalPositional = pop(stack);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case namedSentinel:
|
case namedMarker:
|
||||||
named = pop(stack);
|
named = pop(stack);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -2540,24 +2640,62 @@ class _Parser {
|
||||||
push(stack, head);
|
push(stack, head);
|
||||||
}
|
}
|
||||||
|
|
||||||
_FunctionParameters._setRequiredPositional(
|
Object? requiredPositional = collectArray(parser, stack);
|
||||||
parameters, collectArray(parser, stack));
|
|
||||||
_FunctionParameters._setOptionalPositional(parameters, optionalPositional);
|
head = pop(stack);
|
||||||
_FunctionParameters._setNamed(parameters, named);
|
switch (head) {
|
||||||
Rti returnType = toType(universe, environment(parser), pop(stack));
|
case gotoFunction:
|
||||||
push(stack, _Universe._lookupFunctionRti(universe, returnType, parameters));
|
head = pop(stack);
|
||||||
|
optionalPositional ??= _Universe.sharedEmptyArray(universe);
|
||||||
|
named ??= _Universe.sharedEmptyArray(universe);
|
||||||
|
Rti returnType = toType(universe, environment(parser), head);
|
||||||
|
_FunctionParameters parameters = _FunctionParameters.allocate();
|
||||||
|
_FunctionParameters._setRequiredPositional(
|
||||||
|
parameters, requiredPositional);
|
||||||
|
_FunctionParameters._setOptionalPositional(
|
||||||
|
parameters, optionalPositional);
|
||||||
|
_FunctionParameters._setNamed(parameters, named);
|
||||||
|
push(stack,
|
||||||
|
_Universe._lookupFunctionRti(universe, returnType, parameters));
|
||||||
|
return;
|
||||||
|
|
||||||
|
case gotoRecord:
|
||||||
|
assert(optionalPositional == null);
|
||||||
|
assert(named == null);
|
||||||
|
head = pop(stack);
|
||||||
|
assert(_Utils.isString(head));
|
||||||
|
push(
|
||||||
|
stack,
|
||||||
|
_Universe._lookupRecordRti(
|
||||||
|
universe, _Utils.asString(head), requiredPositional));
|
||||||
|
return;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw AssertionError('Unexpected state under `()`: $head');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void handleOptionalGroup(Object? parser, Object? stack) {
|
static void handleOptionalGroup(Object? parser, Object? stack) {
|
||||||
var parameters = collectArray(parser, stack);
|
var parameters = collectArray(parser, stack);
|
||||||
push(stack, parameters);
|
push(stack, parameters);
|
||||||
push(stack, optionalPositionalSentinel);
|
push(stack, optionalPositionalMarker);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void handleNamedGroup(Object? parser, Object? stack) {
|
static void handleNamedGroup(Object? parser, Object? stack) {
|
||||||
var parameters = collectNamed(parser, stack);
|
var parameters = collectNamed(parser, stack);
|
||||||
push(stack, parameters);
|
push(stack, parameters);
|
||||||
push(stack, namedSentinel);
|
push(stack, namedMarker);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int handleStartRecord(
|
||||||
|
Object? parser, int start, String source, Object? stack) {
|
||||||
|
int end = _Utils.stringIndexOf(
|
||||||
|
source, Recipe.startFunctionArgumentsString, start);
|
||||||
|
assert(end >= 0);
|
||||||
|
push(stack, _Utils.substring(source, start, end));
|
||||||
|
push(stack, gotoRecord);
|
||||||
|
pushStackFrame(parser, stack);
|
||||||
|
return end + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void handleExtendedOperations(Object? parser, Object? stack) {
|
static void handleExtendedOperations(Object? parser, Object? stack) {
|
||||||
|
@ -2861,6 +2999,17 @@ bool _isSubtype(Object? universe, Rti s, Object? sEnv, Rti t, Object? tEnv) {
|
||||||
return _isInterfaceSubtype(universe, s, sEnv, t, tEnv);
|
return _isInterfaceSubtype(universe, s, sEnv, t, tEnv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Records
|
||||||
|
//
|
||||||
|
// TODO(50081): Reference rules to updated specification
|
||||||
|
// https://github.com/dart-lang/language/blob/master/resources/type-system/subtyping.md#rules
|
||||||
|
//
|
||||||
|
// TODO(50081): record is subtype of interface `Record`.
|
||||||
|
if (sKind == Rti.kindRecord) {
|
||||||
|
if (tKind != Rti.kindRecord) return false;
|
||||||
|
return _isRecordSubtype(universe, s, sEnv, t, tEnv);
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3058,6 +3207,29 @@ bool _areArgumentsSubtypes(Object? universe, Object? sArgs, Object? sVariances,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _isRecordSubtype(
|
||||||
|
Object? universe, Rti s, Object? sEnv, Rti t, Object? tEnv) {
|
||||||
|
// `s` is a subtype of `t` if `s` and `t` have the same shape and the fields
|
||||||
|
// of `s` are pairwise subtypes of the fields of `t`.
|
||||||
|
final sFields = Rti._getRecordFields(s);
|
||||||
|
final tFields = Rti._getRecordFields(t);
|
||||||
|
int sCount = _Utils.arrayLength(sFields);
|
||||||
|
int tCount = _Utils.arrayLength(tFields);
|
||||||
|
if (sCount != tCount) return false;
|
||||||
|
String sTag = Rti._getRecordPartialShapeTag(s);
|
||||||
|
String tTag = Rti._getRecordPartialShapeTag(t);
|
||||||
|
if (sTag != tTag) return false;
|
||||||
|
|
||||||
|
for (int i = 0; i < sCount; i++) {
|
||||||
|
Rti sField = _Utils.asRti(_Utils.arrayAt(sFields, i));
|
||||||
|
Rti tField = _Utils.asRti(_Utils.arrayAt(tFields, i));
|
||||||
|
if (!_isSubtype(universe, sField, sEnv, tField, tEnv)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool isNullable(Rti t) {
|
bool isNullable(Rti t) {
|
||||||
int kind = Rti._getKind(t);
|
int kind = Rti._getKind(t);
|
||||||
return isNullType(t) ||
|
return isNullType(t) ||
|
||||||
|
@ -3153,9 +3325,15 @@ class _Utils {
|
||||||
static JSArray arrayConcat(Object? a1, Object? a2) =>
|
static JSArray arrayConcat(Object? a1, Object? a2) =>
|
||||||
JS('JSArray', '#.concat(#)', a1, a2);
|
JS('JSArray', '#.concat(#)', a1, a2);
|
||||||
|
|
||||||
|
static JSArray stringSplit(String s, String pattern) =>
|
||||||
|
JS('JSArray', '#.split(#)', s, pattern);
|
||||||
|
|
||||||
static String substring(String s, int start, int end) =>
|
static String substring(String s, int start, int end) =>
|
||||||
JS('String', '#.substring(#, #)', s, start, end);
|
JS('String', '#.substring(#, #)', s, start, end);
|
||||||
|
|
||||||
|
static int stringIndexOf(String s, String pattern, int start) =>
|
||||||
|
JS('int', '#.indexOf(#, #)', s, pattern, start);
|
||||||
|
|
||||||
static bool stringLessThan(String s1, String s2) =>
|
static bool stringLessThan(String s1, String s2) =>
|
||||||
JS('bool', '# < #', s1, s2);
|
JS('bool', '# < #', s1, s2);
|
||||||
|
|
||||||
|
|
|
@ -58,6 +58,9 @@ abstract class Recipe {
|
||||||
static const String genericFunctionTypeParameterIndexString =
|
static const String genericFunctionTypeParameterIndexString =
|
||||||
_circumflexString;
|
_circumflexString;
|
||||||
|
|
||||||
|
static const int startRecord = _plus;
|
||||||
|
static const String startRecordString = _plusString;
|
||||||
|
|
||||||
static const int extensionOp = _ampersand;
|
static const int extensionOp = _ampersand;
|
||||||
static const String extensionOpString = _ampersandString;
|
static const String extensionOpString = _ampersandString;
|
||||||
static const int pushNeverExtension = 0;
|
static const int pushNeverExtension = 0;
|
||||||
|
@ -198,6 +201,7 @@ abstract class Recipe {
|
||||||
requiredNameSeparatorString);
|
requiredNameSeparatorString);
|
||||||
test("genericFunctionTypeParameterIndex", genericFunctionTypeParameterIndex,
|
test("genericFunctionTypeParameterIndex", genericFunctionTypeParameterIndex,
|
||||||
genericFunctionTypeParameterIndexString);
|
genericFunctionTypeParameterIndexString);
|
||||||
|
test("startRecord", startRecord, startRecordString);
|
||||||
test("extensionOp", extensionOp, extensionOpString);
|
test("extensionOp", extensionOp, extensionOpString);
|
||||||
testExtension(
|
testExtension(
|
||||||
"pushNeverExtension", pushNeverExtension, pushNeverExtensionString);
|
"pushNeverExtension", pushNeverExtension, pushNeverExtensionString);
|
||||||
|
|
Loading…
Reference in a new issue