Version 2.19.0-30.0.dev
Merge commit 'f6f29df4b56cdeae756eb9ddd34df91b72c74ed0' into 'dev'
diff --git a/pkg/_js_interop_checks/lib/src/transformations/js_util_wasm_optimizer.dart b/pkg/_js_interop_checks/lib/src/transformations/js_util_wasm_optimizer.dart
index 89e3b5a..e8cc373 100644
--- a/pkg/_js_interop_checks/lib/src/transformations/js_util_wasm_optimizer.dart
+++ b/pkg/_js_interop_checks/lib/src/transformations/js_util_wasm_optimizer.dart
@@ -297,7 +297,8 @@
Expression getObjectOffGlobalThis(Procedure node, List<String> selectors) {
Expression currentTarget = _globalThis;
for (String selector in selectors) {
- currentTarget = _getProperty(node, currentTarget, selector);
+ currentTarget = _getProperty(node, currentTarget, selector,
+ typeArgument: _nonNullableObjectType);
}
return currentTarget;
}
@@ -315,8 +316,9 @@
type: _nonNullableObjectType);
body.add(object);
for (VariableDeclaration variable in node.function.namedParameters) {
- body.add(ExpressionStatement(
- _setProperty(node, VariableGet(object), variable.name!, variable)));
+ body.add(ExpressionStatement(_setProperty(
+ node, VariableGet(object), variable.name!, variable,
+ typeArgument: variable.type)));
}
body.add(ReturnStatement(VariableGet(object)));
return Block(body);
@@ -349,12 +351,12 @@
///
/// The new [Expression] is equivalent to:
/// `js_util.getProperty([object], [getterName])`.
- Expression _getProperty(
- Procedure node, Expression object, String getterName) =>
+ Expression _getProperty(Procedure node, Expression object, String getterName,
+ {DartType? typeArgument}) =>
StaticInvocation(
_getPropertyTarget,
Arguments([object, StringLiteral(getterName)],
- types: [node.function.returnType]))
+ types: [typeArgument ?? node.function.returnType]))
..fileOffset = node.fileOffset;
/// Returns a new function body for the given [node] external getter.
@@ -377,11 +379,11 @@
/// The new [Expression] is equivalent to:
/// `js_util.setProperty([object], [setterName], [value])`.
Expression _setProperty(Procedure node, Expression object, String setterName,
- VariableDeclaration value) =>
+ VariableDeclaration value, {DartType? typeArgument}) =>
StaticInvocation(
_setPropertyTarget,
Arguments([object, StringLiteral(setterName), VariableGet(value)],
- types: [node.function.returnType]))
+ types: [typeArgument ?? node.function.returnType]))
..fileOffset = node.fileOffset;
/// Returns a new function body for the given [node] external setter.
diff --git a/pkg/dart2wasm/lib/constants.dart b/pkg/dart2wasm/lib/constants.dart
index e62bf7f..2d2a59a 100644
--- a/pkg/dart2wasm/lib/constants.dart
+++ b/pkg/dart2wasm/lib/constants.dart
@@ -803,8 +803,14 @@
return createConstant(constant, info.nonNullableType, (function, b) {
b.i32_const(info.classId);
b.i32_const(initialIdentityHash);
- types.encodeNullability(b, type);
- b.i32_const(environmentIndex);
+
+ // A type parameter's type nullability is undetermined when it's
+ // syntactically not declared nullable and the bound of the type
+ // parameter is nullable. Because we are encoding the declared
+ // nullability, we only declare a type parameter to be nullable if it is
+ // explicitly declared to be nullabe.
+ b.i32_const(type.declaredNullability == Nullability.nullable ? 1 : 0);
+ b.i64_const(environmentIndex);
translator.struct_new(b, info);
});
} else {
diff --git a/pkg/dart2wasm/lib/intrinsics.dart b/pkg/dart2wasm/lib/intrinsics.dart
index 7971b28..9e5effc 100644
--- a/pkg/dart2wasm/lib/intrinsics.dart
+++ b/pkg/dart2wasm/lib/intrinsics.dart
@@ -593,7 +593,8 @@
if (className != null) {
List<String> libAndClass = className.split("#");
Class cls = translator.libraries
- .firstWhere((l) => l.name == libAndClass[0])
+ .firstWhere(
+ (l) => l.name == libAndClass[0] && l.importUri.scheme == 'dart')
.classes
.firstWhere((c) => c.name == libAndClass[1]);
int classId = translator.classInfo[cls]!.classId;
@@ -682,6 +683,8 @@
return translator.types.makeTypeRulesSupers(b);
case "_getTypeRulesSubstitutions":
return translator.types.makeTypeRulesSubstitutions(b);
+ case "_getTypeNames":
+ return translator.types.makeTypeNames(b);
case "_getInterfaceTypeRuntimeType":
Expression object = node.arguments.positional[0];
Expression typeArguments = node.arguments.positional[1];
@@ -793,6 +796,26 @@
return w.NumType.f64;
case "getID":
return getID(node.arguments.positional.single);
+ case "makeListFixedLength":
+ ClassInfo receiverInfo =
+ translator.classInfo[translator.listBaseClass]!;
+ codeGen.wrap(
+ node.arguments.positional.single, receiverInfo.nonNullableType);
+ w.Local receiverLocal =
+ codeGen.function.addLocal(receiverInfo.nullableType);
+ b.local_tee(receiverLocal);
+ // We ignore the type argument and just update the classID of the
+ // receiver.
+ // TODO(joshualitt): If the amount of free space is significant, it
+ // might be worth doing a copy here.
+ ClassInfo topInfo = translator.topInfo;
+ ClassInfo fixedLengthListInfo =
+ translator.classInfo[translator.fixedLengthListClass]!;
+ b.i32_const(fixedLengthListInfo.classId);
+ b.struct_set(topInfo.struct, FieldIndex.classId);
+ b.local_get(receiverLocal);
+ b.ref_as_non_null();
+ return fixedLengthListInfo.nonNullableType;
}
}
@@ -1250,6 +1273,11 @@
ClassInfo info = translator.classInfo[cls]!;
b.local_get(paramLocals[0]);
translator.ref_cast(b, info);
+ // TODO(joshualitt): Because we currently merge getters to support
+ // dynamic calls, the return types of `.length` and `.offsetInBytes` can
+ // change. Should we decide to stop merging getters, we should remove
+ // the conversions below.
+ w.ValueType outputType = function.type.outputs.single;
switch (name) {
case "length":
assert(cls == translator.typedListBaseClass ||
@@ -1260,6 +1288,7 @@
b.struct_get(info.struct, FieldIndex.byteDataViewLength);
}
b.i64_extend_i32_u();
+ translator.convertType(function, intType, outputType);
return true;
case "offsetInBytes":
assert(cls == translator.typedListViewClass ||
@@ -1270,6 +1299,7 @@
b.struct_get(info.struct, FieldIndex.byteDataViewOffsetInBytes);
}
b.i64_extend_i32_u();
+ translator.convertType(function, intType, outputType);
return true;
case "_typedData":
assert(cls == translator.typedListViewClass ||
diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart
index 8e6c7d9..8cd8483 100644
--- a/pkg/dart2wasm/lib/translator.dart
+++ b/pkg/dart2wasm/lib/translator.dart
@@ -127,6 +127,7 @@
late final Procedure hashImmutableIndexNullable;
late final Procedure isSubtype;
late final Procedure objectRuntimeType;
+ late final Procedure typeAsNullable;
late final Map<Class, w.StorageType> builtinTypes;
late final Map<w.ValueType, Class> boxedClasses;
@@ -272,6 +273,9 @@
objectRuntimeType = lookupCore("Object")
.procedures
.firstWhere((p) => p.name.text == "_runtimeType");
+ typeAsNullable = lookupCore("_Type")
+ .procedures
+ .firstWhere((p) => p.name.text == "asNullable");
builtinTypes = {
coreTypes.boolClass: w.NumType.i32,
coreTypes.intClass: w.NumType.i64,
diff --git a/pkg/dart2wasm/lib/types.dart b/pkg/dart2wasm/lib/types.dart
index 50e4283..1011b26 100644
--- a/pkg/dart2wasm/lib/types.dart
+++ b/pkg/dart2wasm/lib/types.dart
@@ -63,6 +63,9 @@
late final List<List<List<DartType>>> typeRulesSubstitutions =
_buildTypeRulesSubstitutions();
+ /// A list which maps class ID to the classes [String] name.
+ late final List<String> typeNames = _buildTypeNames();
+
Types(this.translator);
w.ValueType classAndFieldToType(Class cls, int fieldIndex) =>
@@ -100,8 +103,14 @@
if (superclassInfo.cls == null ||
superclassInfo.cls == coreTypes.objectClass) continue;
Class superclass = superclassInfo.cls!;
- Iterable<Class> subclasses =
- _getConcreteSubtypes(superclass).where((cls) => cls != superclass);
+
+ // TODO(joshualitt): This includes abstract types that can't be
+ // instantiated, but might be needed for subtype checks. The majority of
+ // abstract classes are probably unnecessary though. We should filter
+ // these cases to reduce the size of the type rules.
+ Iterable<Class> subclasses = translator.subtypes
+ .getSubtypesOf(superclass)
+ .where((cls) => cls != superclass);
Iterable<InterfaceType> subtypes = subclasses.map(
(Class cls) => cls.getThisType(coreTypes, Nullability.nonNullable));
for (InterfaceType subtype in subtypes) {
@@ -144,6 +153,17 @@
return typeRulesSubstitutions;
}
+ List<String> _buildTypeNames() {
+ // This logic assumes `translator.classes` returns the classes indexed by
+ // class ID. If we ever change that logic, we will need to change this code.
+ List<String> typeNames = [];
+ for (ClassInfo classInfo in translator.classes) {
+ String className = classInfo.cls?.name ?? '';
+ typeNames.add(className);
+ }
+ return typeNames;
+ }
+
/// Builds a map of subclasses to the transitive set of superclasses they
/// implement.
/// TODO(joshualitt): This implementation is just temporary. Eventually we
@@ -205,6 +225,25 @@
return expectedType;
}
+ /// Returns a list of string type names for pretty printing types.
+ w.ValueType makeTypeNames(w.Instructions b) {
+ w.ValueType expectedType =
+ translator.classInfo[translator.immutableListClass]!.nonNullableType;
+ DartType stringType = InterfaceType(
+ translator.stringBaseClass,
+ Nullability.nonNullable,
+ [translator.coreTypes.stringNonNullableRawType]);
+ List<StringConstant> listStringConstant = [];
+ for (String name in typeNames) {
+ listStringConstant.add(StringConstant(name));
+ }
+ DartType listStringType = InterfaceType(
+ translator.immutableListClass, Nullability.nonNullable, [stringType]);
+ translator.constants.instantiateConstant(null, b,
+ ListConstant(listStringType, listStringConstant), expectedType);
+ return expectedType;
+ }
+
bool isGenericFunction(FunctionType type) => type.typeParameters.isNotEmpty;
bool isGenericFunctionTypeParameter(TypeParameterType type) =>
@@ -358,7 +397,11 @@
type is FutureOrType ||
type is FunctionType);
if (type is TypeParameterType) {
- return codeGen.instantiateTypeParameter(type.parameter);
+ codeGen.instantiateTypeParameter(type.parameter);
+ if (type.declaredNullability == Nullability.nullable) {
+ codeGen.call(translator.typeAsNullable.reference);
+ }
+ return nonNullableTypeType;
}
ClassInfo info = translator.classInfo[classForType(type)]!;
translator.functions.allocateClass(info.classId);
@@ -390,16 +433,20 @@
void emitTypeTest(CodeGenerator codeGen, DartType type, DartType operandType,
TreeNode node) {
w.Instructions b = codeGen.b;
- if (type is! InterfaceType) {
- // TODO(joshualitt): We can enable this after fixing `.runtimeType`.
- // makeType(codeGen, type);
- // codeGen.call(translator.isSubtype.reference);
- print("Not implemented: Type test with non-interface type $type"
+ if (type is FunctionType) {
+ // TODO(joshualitt): We can enable type tests for [FunctionType] after
+ // enabling `.runtimeType` for [FunctionType].
+ print("Not implemented: Type test with function type $type"
" at ${node.location}");
b.drop();
b.i32_const(1);
return;
}
+ if (type is! InterfaceType) {
+ makeType(codeGen, type);
+ codeGen.call(translator.isSubtype.reference);
+ return;
+ }
bool isPotentiallyNullable = operandType.isPotentiallyNullable;
w.Label? resultLabel;
if (isPotentiallyNullable) {
@@ -412,6 +459,15 @@
b.local_get(operand);
b.br_on_null(nullLabel);
}
+ void _endPotentiallyNullableBlock() {
+ if (isPotentiallyNullable) {
+ b.br(resultLabel!);
+ b.end(); // nullLabel
+ encodeNullability(b, type);
+ b.end(); // resultLabel
+ }
+ }
+
if (type.typeArguments.any((t) => t is! DynamicType)) {
// If the tested-against type as an instance of the static operand type
// has the same type arguments as the static operand type, it is not
@@ -421,8 +477,10 @@
.getTypeAsInstanceOf(type, cls, codeGen.member.enclosingLibrary)
?.withDeclaredNullability(operandType.declaredNullability);
if (base != operandType) {
- print("Not implemented: Type test with type arguments"
- " at ${node.location}");
+ makeType(codeGen, type);
+ codeGen.call(translator.isSubtype.reference);
+ _endPotentiallyNullableBlock();
+ return;
}
}
List<Class> concrete = _getConcreteSubtypes(type.classNode).toList();
@@ -454,12 +512,7 @@
b.i32_const(0);
b.end(); // done
}
- if (isPotentiallyNullable) {
- b.br(resultLabel!);
- b.end(); // nullLabel
- encodeNullability(b, type);
- b.end(); // resultLabel
- }
+ _endPotentiallyNullableBlock();
}
/// Returns true if a given type is nullable, and false otherwise. This
diff --git a/sdk/lib/_internal/wasm/lib/class_id.dart b/sdk/lib/_internal/wasm/lib/class_id.dart
index 08f2006..312ec3e 100644
--- a/sdk/lib/_internal/wasm/lib/class_id.dart
+++ b/sdk/lib/_internal/wasm/lib/class_id.dart
@@ -16,10 +16,12 @@
external static int get cidUint8ArrayView;
@pragma("wasm:class-id", "dart.core#Object")
external static int get cidObject;
- @pragma("wasm:class-id", "dart.async#Future")
- external static int get cidFuture;
+ @pragma("wasm:class-id", "dart.async#_Future")
+ external static int get cid_Future;
@pragma("wasm:class-id", "dart.core#Function")
external static int get cidFunction;
+ @pragma("wasm:class-id", "dart.core#_Function")
+ external static int get cid_Function;
// Class IDs for RTI Types.
@pragma("wasm:class-id", "dart.core#_NeverType")
diff --git a/sdk/lib/_internal/wasm/lib/object_patch.dart b/sdk/lib/_internal/wasm/lib/object_patch.dart
index e61e064..8ba627e 100644
--- a/sdk/lib/_internal/wasm/lib/object_patch.dart
+++ b/sdk/lib/_internal/wasm/lib/object_patch.dart
@@ -45,6 +45,8 @@
/// override [runtimeType].
@patch
external Type get runtimeType;
+
+ @pragma("wasm:entry-point")
_Type get _runtimeType => _getInterfaceTypeRuntimeType(this, _typeArguments);
@patch
diff --git a/sdk/lib/_internal/wasm/lib/type.dart b/sdk/lib/_internal/wasm/lib/type.dart
index 4a1ea76..8df02e3 100644
--- a/sdk/lib/_internal/wasm/lib/type.dart
+++ b/sdk/lib/_internal/wasm/lib/type.dart
@@ -48,6 +48,7 @@
@pragma("wasm:entry-point")
class _NeverType extends _Type {
+ @pragma("wasm:entry-point")
const _NeverType() : super(false);
@override
@@ -63,6 +64,7 @@
@pragma("wasm:entry-point")
class _DynamicType extends _Type {
+ @pragma("wasm:entry-point")
const _DynamicType() : super(true);
@override
@@ -77,6 +79,7 @@
@pragma("wasm:entry-point")
class _VoidType extends _Type {
+ @pragma("wasm:entry-point")
const _VoidType() : super(true);
@override
@@ -91,6 +94,7 @@
@pragma("wasm:entry-point")
class _NullType extends _Type {
+ @pragma("wasm:entry-point")
const _NullType() : super(true);
@override
@@ -110,6 +114,7 @@
class _InterfaceTypeParameterType extends _Type {
final int environmentIndex;
+ @pragma("wasm:entry-point")
const _InterfaceTypeParameterType(super.isNullable, this.environmentIndex);
@override
@@ -121,7 +126,7 @@
throw 'Type parameter should have been substituted already.';
@override
- String toString() => '$environmentIndex';
+ String toString() => 'T$environmentIndex';
}
@pragma("wasm:entry-point")
@@ -140,7 +145,7 @@
throw 'Type parameter should have been substituted already.';
@override
- String toString() => '$environmentIndex';
+ String toString() => 'G$environmentIndex';
}
@pragma("wasm:entry-point")
@@ -151,7 +156,7 @@
const _FutureOrType(bool isNullable, this.typeArgument) : super(isNullable);
_InterfaceType get asFuture =>
- _InterfaceType(ClassID.cidFuture, isNullable, [typeArgument]);
+ _InterfaceType(ClassID.cid_Future, isNullable, [typeArgument]);
@override
_Type get _asNonNullable {
@@ -231,8 +236,7 @@
@override
String toString() {
StringBuffer s = StringBuffer();
- s.write("Interface");
- s.write(classId);
+ s.write(_getTypeNames()[classId]);
if (typeArguments.isNotEmpty) {
s.write("<");
for (int i = 0; i < typeArguments.length; i++) {
@@ -364,8 +368,8 @@
}
// TODO(joshualitt): Implement. This should probably extend _FunctionType.
-@pragma("wasm:entry-point")
class _GenericFunctionType extends _Type {
+ @pragma("wasm:entry-point")
const _GenericFunctionType(bool isNullable) : super(isNullable);
@override
@@ -380,6 +384,7 @@
external List<List<int>> _getTypeRulesSupers();
external List<List<List<_Type>>> _getTypeRulesSubstitutions();
+external List<String> _getTypeNames();
class _Environment {
List<List<_Type>> scopes = [];
@@ -450,7 +455,8 @@
}
bool isFunctionType(_Type t) =>
- isSpecificInterfaceType(t, ClassID.cidFunction);
+ isSpecificInterfaceType(t, ClassID.cidFunction) ||
+ isSpecificInterfaceType(t, ClassID.cid_Function);
bool areTypeArgumentsSubtypes(List<_Type> sArgs, _Environment? sEnv,
List<_Type> tArgs, _Environment? tEnv) {
@@ -484,13 +490,22 @@
List<_Type> substitutions = typeRulesSubstitutions[sId][sSuperIndexOfT];
+ // If we have empty type arguments then create a list of dynamic type
+ // arguments.
+ List<_Type> sTypeArguments = s.typeArguments;
+ if (substitutions.isNotEmpty && sTypeArguments.isEmpty) {
+ sTypeArguments = List<_Type>.generate(
+ substitutions.length, (int index) => const _DynamicType(),
+ growable: false);
+ }
+
// If [sEnv] is null, then create a new environment. Otherwise, we are doing
// a recursive type check, so extend the existing environment with [s]'s
// type arguments.
if (sEnv == null) {
- sEnv = _Environment.from(s.typeArguments);
+ sEnv = _Environment.from(sTypeArguments);
} else {
- sEnv.push(s.typeArguments);
+ sEnv.push(sTypeArguments);
}
bool result =
areTypeArgumentsSubtypes(substitutions, sEnv, t.typeArguments, tEnv);
@@ -562,6 +577,7 @@
}
while (sIndex < sNamedLength) {
if (sNamed[sIndex].isRequired) return false;
+ sIndex++;
}
return true;
}
@@ -606,7 +622,7 @@
if (!isSubtype(sFutureOr.typeArgument, sEnv, t, tEnv)) {
return false;
}
- return _isSubtype(sFutureOr.asFuture, t);
+ return isSubtype(sFutureOr.asFuture, sEnv, t, tEnv);
}
// Left Nullable:
@@ -643,6 +659,12 @@
return true;
}
+ // TODO(joshualitt): This is not correct, but it is necessary until we fully
+ // implement RTI for FunctionTypes.
+ if (isFunctionType(s) && (t.isFunction || t.isGenericFunction)) {
+ return true;
+ }
+
// Positional Function Types + Named Function Types:
if (s.isGenericFunction && t.isGenericFunction) {
// TODO(joshualitt): Implement case.
diff --git a/sdk/lib/async/future_impl.dart b/sdk/lib/async/future_impl.dart
index 44b3906..f8fb750 100644
--- a/sdk/lib/async/future_impl.dart
+++ b/sdk/lib/async/future_impl.dart
@@ -196,6 +196,7 @@
bool shouldChain(Future<dynamic> value) => value is Future<T> || value is! T;
}
+@pragma("wasm:entry-point")
class _Future<T> implements Future<T> {
/// Initial state, waiting for a result. In this state, the
/// [_resultOrListeners] field holds a single-linked list of
diff --git a/tools/VERSION b/tools/VERSION
index d4e983a..e2c5c13 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 19
PATCH 0
-PRERELEASE 29
+PRERELEASE 30
PRERELEASE_PATCH 0
\ No newline at end of file