mirror of
https://github.com/dart-lang/sdk
synced 2024-11-02 10:28:02 +00:00
[dart2wasm] Switch to using declared nullability in subtype check.
Change-Id: Id993392aad23dd5a6c03e84a21323ff529992c6f Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/263146 Commit-Queue: Joshua Litt <joshualitt@google.com> Reviewed-by: Aske Simon Christensen <askesc@google.com>
This commit is contained in:
parent
2230bc2064
commit
78f95a427b
4 changed files with 63 additions and 85 deletions
|
@ -29,7 +29,7 @@ class FieldIndex {
|
|||
static const vtableInstantiationFunction = 0;
|
||||
static const instantiationContextInner = 0;
|
||||
static const instantiationContextTypeArgumentsBase = 1;
|
||||
static const typeIsNullable = 2;
|
||||
static const typeIsDeclaredNullable = 2;
|
||||
static const interfaceTypeTypeArguments = 4;
|
||||
static const functionTypeNamedParameters = 6;
|
||||
static const typedListBaseLength = 2;
|
||||
|
@ -57,7 +57,8 @@ class FieldIndex {
|
|||
check(translator.hashFieldBaseClass, "_index", FieldIndex.hashBaseIndex);
|
||||
check(translator.hashFieldBaseClass, "_data", FieldIndex.hashBaseData);
|
||||
check(translator.functionClass, "context", FieldIndex.closureContext);
|
||||
check(translator.typeClass, "isNullable", FieldIndex.typeIsNullable);
|
||||
check(translator.typeClass, "isDeclaredNullable",
|
||||
FieldIndex.typeIsDeclaredNullable);
|
||||
check(translator.interfaceTypeClass, "typeArguments",
|
||||
FieldIndex.interfaceTypeTypeArguments);
|
||||
check(translator.functionTypeClass, "namedParameters",
|
||||
|
|
|
@ -729,7 +729,7 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?> {
|
|||
|
||||
b.i32_const(info.classId);
|
||||
b.i32_const(initialIdentityHash);
|
||||
types.encodeNullability(b, type);
|
||||
b.i32_const(types.encodedNullability(type));
|
||||
b.i64_const(typeInfo.classId);
|
||||
constants.instantiateConstant(
|
||||
function, b, typeArgs, typeListExpectedType);
|
||||
|
@ -744,7 +744,7 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?> {
|
|||
return createConstant(constant, info.nonNullableType, (function, b) {
|
||||
b.i32_const(info.classId);
|
||||
b.i32_const(initialIdentityHash);
|
||||
types.encodeNullability(b, type);
|
||||
b.i32_const(types.encodedNullability(type));
|
||||
constants.instantiateConstant(
|
||||
function, b, typeArgument, types.nonNullableTypeType);
|
||||
b.struct_new(info.struct);
|
||||
|
@ -768,7 +768,7 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?> {
|
|||
return createConstant(constant, info.nonNullableType, (function, b) {
|
||||
b.i32_const(info.classId);
|
||||
b.i32_const(initialIdentityHash);
|
||||
types.encodeNullability(b, type);
|
||||
b.i32_const(types.encodedNullability(type));
|
||||
constants.instantiateConstant(
|
||||
function, b, returnTypeConstant, types.nonNullableTypeType);
|
||||
constants.instantiateConstant(function, b, positionalParametersConstant,
|
||||
|
@ -798,7 +798,7 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?> {
|
|||
return createConstant(constant, info.nonNullableType, (function, b) {
|
||||
b.i32_const(info.classId);
|
||||
b.i32_const(initialIdentityHash);
|
||||
types.encodeNullability(b, type);
|
||||
b.i32_const(types.encodedNullability(type));
|
||||
b.struct_new(info.struct);
|
||||
});
|
||||
} else {
|
||||
|
@ -812,13 +812,7 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?> {
|
|||
return createConstant(constant, info.nonNullableType, (function, b) {
|
||||
b.i32_const(info.classId);
|
||||
b.i32_const(initialIdentityHash);
|
||||
|
||||
// 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.i32_const(types.encodedNullability(type));
|
||||
b.i64_const(environmentIndex);
|
||||
b.struct_new(info.struct);
|
||||
});
|
||||
|
@ -830,7 +824,7 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?> {
|
|||
return createConstant(constant, info.nonNullableType, (function, b) {
|
||||
b.i32_const(info.classId);
|
||||
b.i32_const(initialIdentityHash);
|
||||
types.encodeNullability(b, type);
|
||||
b.i32_const(types.encodedNullability(type));
|
||||
b.struct_new(info.struct);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -308,7 +308,7 @@ class Types {
|
|||
CodeGenerator codeGen, ClassInfo info, InterfaceType type) {
|
||||
w.Instructions b = codeGen.b;
|
||||
ClassInfo typeInfo = translator.classInfo[type.classNode]!;
|
||||
encodeNullability(b, type);
|
||||
b.i32_const(encodedNullability(type));
|
||||
b.i64_const(typeInfo.classId);
|
||||
_makeTypeList(codeGen, type.typeArguments);
|
||||
}
|
||||
|
@ -366,29 +366,14 @@ class Types {
|
|||
|
||||
void _makeFutureOrType(CodeGenerator codeGen, FutureOrType type) {
|
||||
w.Instructions b = codeGen.b;
|
||||
w.DefinedFunction function = codeGen.function;
|
||||
|
||||
// We canonicalize `FutureOr<T?>` to `FutureOr<T?>?`. However, we have to
|
||||
// take special care to handle the case where we have
|
||||
// undetermined nullability. To handle this, we emit the type argument, and
|
||||
// read back its nullability at runtime.
|
||||
if (type.nullability == Nullability.undetermined) {
|
||||
w.ValueType typeArgumentType = makeType(codeGen, type.typeArgument);
|
||||
w.Local typeArgumentTemporary = codeGen.addLocal(typeArgumentType);
|
||||
b.local_tee(typeArgumentTemporary);
|
||||
b.struct_get(typeClassInfo.struct, FieldIndex.typeIsNullable);
|
||||
b.local_get(typeArgumentTemporary);
|
||||
translator.convertType(function, typeArgumentType, nonNullableTypeType);
|
||||
} else {
|
||||
encodeNullability(b, type);
|
||||
makeType(codeGen, type.typeArgument);
|
||||
}
|
||||
b.i32_const(encodedNullability(type));
|
||||
makeType(codeGen, type.typeArgument);
|
||||
}
|
||||
|
||||
void _makeFunctionType(
|
||||
CodeGenerator codeGen, ClassInfo info, FunctionType type) {
|
||||
w.Instructions b = codeGen.b;
|
||||
encodeNullability(b, type);
|
||||
b.i32_const(encodedNullability(type));
|
||||
makeType(codeGen, type.returnType);
|
||||
if (type.positionalParameters.every(_isTypeConstant)) {
|
||||
translator.constants.instantiateConstant(
|
||||
|
@ -469,7 +454,7 @@ class Types {
|
|||
// TODO(joshualitt): Implement generic function types and share most of
|
||||
// the logic with _makeFunctionType.
|
||||
print("Not implemented: RTI ${type}");
|
||||
encodeNullability(b, type);
|
||||
b.i32_const(encodedNullability(type));
|
||||
} else {
|
||||
_makeFunctionType(codeGen, info, type);
|
||||
}
|
||||
|
@ -515,7 +500,7 @@ class Types {
|
|||
if (isPotentiallyNullable) {
|
||||
b.br(resultLabel!);
|
||||
b.end(); // nullLabel
|
||||
encodeNullability(b, type);
|
||||
b.i32_const(encodedNullability(type));
|
||||
b.end(); // resultLabel
|
||||
}
|
||||
}
|
||||
|
@ -570,15 +555,6 @@ class Types {
|
|||
_endPotentiallyNullableBlock();
|
||||
}
|
||||
|
||||
/// Returns true if a given type is nullable, and false otherwise. This
|
||||
/// function should not be used on [DartType]s with undetermined nullability.
|
||||
bool isNullable(DartType type) {
|
||||
Nullability nullability = type.nullability;
|
||||
assert(nullability == Nullability.nullable ||
|
||||
nullability == Nullability.nonNullable);
|
||||
return nullability == Nullability.nullable ? true : false;
|
||||
}
|
||||
|
||||
void encodeNullability(w.Instructions b, DartType type) =>
|
||||
b.i32_const(isNullable(type) ? 1 : 0);
|
||||
int encodedNullability(DartType type) =>
|
||||
type.declaredNullability == Nullability.nullable ? 1 : 0;
|
||||
}
|
||||
|
|
|
@ -15,9 +15,9 @@ part of 'core_patch.dart';
|
|||
// * [_FutureOrType.asFuture].
|
||||
// TODO(joshualitt): Make `Function` a canonical type.
|
||||
abstract class _Type implements Type {
|
||||
final bool isNullable;
|
||||
final bool isDeclaredNullable;
|
||||
|
||||
const _Type(this.isNullable);
|
||||
const _Type(this.isDeclaredNullable);
|
||||
|
||||
bool _testID(int value) => ClassID.getID(this) == value;
|
||||
bool get isNever => _testID(ClassID.cidNeverType);
|
||||
|
@ -33,8 +33,8 @@ abstract class _Type implements Type {
|
|||
|
||||
T as<T>() => unsafeCast<T>(this);
|
||||
|
||||
_Type get asNonNullable => isNullable ? _asNonNullable : this;
|
||||
_Type get asNullable => isNullable ? this : _asNullable;
|
||||
_Type get asNonNullable => isDeclaredNullable ? _asNonNullable : this;
|
||||
_Type get asNullable => isDeclaredNullable ? this : _asNullable;
|
||||
|
||||
_Type get _asNonNullable;
|
||||
_Type get _asNullable;
|
||||
|
@ -115,7 +115,8 @@ class _InterfaceTypeParameterType extends _Type {
|
|||
final int environmentIndex;
|
||||
|
||||
@pragma("wasm:entry-point")
|
||||
const _InterfaceTypeParameterType(super.isNullable, this.environmentIndex);
|
||||
const _InterfaceTypeParameterType(
|
||||
super.isDeclaredNullable, this.environmentIndex);
|
||||
|
||||
@override
|
||||
_Type get _asNonNullable =>
|
||||
|
@ -134,7 +135,7 @@ class _GenericFunctionTypeParameterType extends _Type {
|
|||
final int environmentIndex;
|
||||
|
||||
const _GenericFunctionTypeParameterType(
|
||||
super.isNullable, this.environmentIndex);
|
||||
super.isDeclaredNullable, this.environmentIndex);
|
||||
|
||||
@override
|
||||
_Type get _asNonNullable =>
|
||||
|
@ -153,16 +154,13 @@ class _FutureOrType extends _Type {
|
|||
final _Type typeArgument;
|
||||
|
||||
@pragma("wasm:entry-point")
|
||||
const _FutureOrType(bool isNullable, this.typeArgument) : super(isNullable);
|
||||
const _FutureOrType(super.isDeclaredNullable, this.typeArgument);
|
||||
|
||||
_InterfaceType get asFuture =>
|
||||
_InterfaceType(ClassID.cid_Future, isNullable, [typeArgument]);
|
||||
_InterfaceType(ClassID.cid_Future, isDeclaredNullable, [typeArgument]);
|
||||
|
||||
@override
|
||||
_Type get _asNonNullable {
|
||||
if (!typeArgument.isNullable) return _FutureOrType(false, typeArgument);
|
||||
throw '`$this` cannot be non nullable.';
|
||||
}
|
||||
_Type get _asNonNullable => _FutureOrType(false, typeArgument);
|
||||
|
||||
@override
|
||||
_Type get _asNullable => _FutureOrType(true, typeArgument);
|
||||
|
@ -171,14 +169,14 @@ class _FutureOrType extends _Type {
|
|||
bool operator ==(Object o) {
|
||||
if (!(super == o)) return false;
|
||||
_FutureOrType other = unsafeCast<_FutureOrType>(o);
|
||||
if (isNullable != other.isNullable) return false;
|
||||
if (isDeclaredNullable != other.isDeclaredNullable) return false;
|
||||
return typeArgument == other.typeArgument;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
int hash = super.hashCode;
|
||||
hash = mix64(hash ^ (isNullable ? 1 : 0));
|
||||
hash = mix64(hash ^ (isDeclaredNullable ? 1 : 0));
|
||||
return mix64(hash ^ typeArgument.hashCode);
|
||||
}
|
||||
|
||||
|
@ -189,7 +187,7 @@ class _FutureOrType extends _Type {
|
|||
s.write("<");
|
||||
s.write(typeArgument);
|
||||
s.write(">");
|
||||
if (isNullable) s.write("?");
|
||||
if (isDeclaredNullable) s.write("?");
|
||||
return s.toString();
|
||||
}
|
||||
}
|
||||
|
@ -199,9 +197,8 @@ class _InterfaceType extends _Type {
|
|||
final List<_Type> typeArguments;
|
||||
|
||||
@pragma("wasm:entry-point")
|
||||
const _InterfaceType(this.classId, bool isNullable,
|
||||
[this.typeArguments = const []])
|
||||
: super(isNullable);
|
||||
const _InterfaceType(this.classId, super.isDeclaredNullable,
|
||||
[this.typeArguments = const []]);
|
||||
|
||||
@override
|
||||
_Type get _asNonNullable => _InterfaceType(classId, false, typeArguments);
|
||||
|
@ -214,7 +211,7 @@ class _InterfaceType extends _Type {
|
|||
if (!(super == o)) return false;
|
||||
_InterfaceType other = unsafeCast<_InterfaceType>(o);
|
||||
if (classId != other.classId) return false;
|
||||
if (isNullable != other.isNullable) return false;
|
||||
if (isDeclaredNullable != other.isDeclaredNullable) return false;
|
||||
assert(typeArguments.length == other.typeArguments.length);
|
||||
for (int i = 0; i < typeArguments.length; i++) {
|
||||
if (typeArguments[i] != other.typeArguments[i]) return false;
|
||||
|
@ -226,7 +223,7 @@ class _InterfaceType extends _Type {
|
|||
int get hashCode {
|
||||
int hash = super.hashCode;
|
||||
hash = mix64(hash ^ classId);
|
||||
hash = mix64(hash ^ (isNullable ? 1 : 0));
|
||||
hash = mix64(hash ^ (isDeclaredNullable ? 1 : 0));
|
||||
for (int i = 0; i < typeArguments.length; i++) {
|
||||
hash = mix64(hash ^ typeArguments[i].hashCode);
|
||||
}
|
||||
|
@ -245,7 +242,7 @@ class _InterfaceType extends _Type {
|
|||
}
|
||||
s.write(">");
|
||||
}
|
||||
if (isNullable) s.write("?");
|
||||
if (isDeclaredNullable) s.write("?");
|
||||
return s.toString();
|
||||
}
|
||||
}
|
||||
|
@ -293,9 +290,12 @@ class _FunctionType extends _Type {
|
|||
final List<_NamedParameter> namedParameters;
|
||||
|
||||
@pragma("wasm:entry-point")
|
||||
const _FunctionType(this.returnType, this.positionalParameters,
|
||||
this.requiredParameterCount, this.namedParameters, bool isNullable)
|
||||
: super(isNullable);
|
||||
const _FunctionType(
|
||||
this.returnType,
|
||||
this.positionalParameters,
|
||||
this.requiredParameterCount,
|
||||
this.namedParameters,
|
||||
super.isDeclaredNullable);
|
||||
|
||||
@override
|
||||
_Type get _asNonNullable => _FunctionType(returnType, positionalParameters,
|
||||
|
@ -308,7 +308,7 @@ class _FunctionType extends _Type {
|
|||
bool operator ==(Object o) {
|
||||
if (!(super == o)) return false;
|
||||
_FunctionType other = unsafeCast<_FunctionType>(o);
|
||||
if (isNullable != other.isNullable) return false;
|
||||
if (isDeclaredNullable != other.isDeclaredNullable) return false;
|
||||
if (returnType != other.returnType) ;
|
||||
if (positionalParameters.length != other.positionalParameters.length) {
|
||||
return false;
|
||||
|
@ -329,7 +329,7 @@ class _FunctionType extends _Type {
|
|||
@override
|
||||
int get hashCode {
|
||||
int hash = super.hashCode;
|
||||
hash = mix64(hash ^ (isNullable ? 1 : 0));
|
||||
hash = mix64(hash ^ (isDeclaredNullable ? 1 : 0));
|
||||
hash = mix64(hash ^ returnType.hashCode);
|
||||
for (int i = 0; i < positionalParameters.length; i++) {
|
||||
hash = mix64(hash ^ positionalParameters[i].hashCode);
|
||||
|
@ -362,7 +362,7 @@ class _FunctionType extends _Type {
|
|||
s.write("}");
|
||||
}
|
||||
s.write(")");
|
||||
if (isNullable) s.write("?");
|
||||
if (isDeclaredNullable) s.write("?");
|
||||
return s.toString();
|
||||
}
|
||||
}
|
||||
|
@ -370,7 +370,7 @@ class _FunctionType extends _Type {
|
|||
// TODO(joshualitt): Implement. This should probably extend _FunctionType.
|
||||
class _GenericFunctionType extends _Type {
|
||||
@pragma("wasm:entry-point")
|
||||
const _GenericFunctionType(bool isNullable) : super(isNullable);
|
||||
const _GenericFunctionType(super.isDeclaredNullable);
|
||||
|
||||
@override
|
||||
_Type get _asNonNullable => throw 'unimplemented';
|
||||
|
@ -407,7 +407,7 @@ class _Environment {
|
|||
// [type] as nullable.
|
||||
// Note: This will throw if the required nullability is impossible to
|
||||
// generate.
|
||||
if (!declaredNullable || type.isNullable) {
|
||||
if (!declaredNullable || type.isDeclaredNullable) {
|
||||
return type;
|
||||
}
|
||||
return type.asNullable;
|
||||
|
@ -417,8 +417,8 @@ class _Environment {
|
|||
// Lookup `InterfaceType` parameters in the top environment.
|
||||
// TODO(joshualitt): When we implement generic functions be sure to keep the
|
||||
// environments distinct.
|
||||
return _substituteTypeParameter(
|
||||
typeParameter.isNullable, scopes.last[typeParameter.environmentIndex]);
|
||||
return _substituteTypeParameter(typeParameter.isDeclaredNullable,
|
||||
scopes.last[typeParameter.environmentIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -442,7 +442,7 @@ class _TypeUniverse {
|
|||
return type.classId == classId;
|
||||
}
|
||||
|
||||
bool isObjectQuestionType(_Type t) => isObjectType(t) && t.isNullable;
|
||||
bool isObjectQuestionType(_Type t) => isObjectType(t) && t.isDeclaredNullable;
|
||||
|
||||
bool isObjectType(_Type t) => isSpecificInterfaceType(t, ClassID.cidObject);
|
||||
|
||||
|
@ -605,15 +605,18 @@ class _TypeUniverse {
|
|||
}
|
||||
|
||||
// Left Null:
|
||||
// TODO(joshualitt): Combine with 'Right Null', and this can just be:
|
||||
// `if (s.isNullable && !t.isNullable) return false`
|
||||
if (s.isNull) {
|
||||
return t.isNullable;
|
||||
return (t.isFutureOr &&
|
||||
isSubtype(s, sEnv, t.as<_FutureOrType>().typeArgument, tEnv)) ||
|
||||
t.isDeclaredNullable;
|
||||
}
|
||||
|
||||
// Right Object:
|
||||
if (isObjectType(t)) {
|
||||
return !s.isNullable;
|
||||
if (s.isFutureOr) {
|
||||
return isSubtype(s.as<_FutureOrType>().typeArgument, sEnv, t, tEnv);
|
||||
}
|
||||
return !s.isDeclaredNullable;
|
||||
}
|
||||
|
||||
// Left FutureOr:
|
||||
|
@ -626,8 +629,9 @@ class _TypeUniverse {
|
|||
}
|
||||
|
||||
// Left Nullable:
|
||||
if (s.isNullable) {
|
||||
return t.isNullable && isSubtype(s.asNonNullable, sEnv, t, tEnv);
|
||||
if (s.isDeclaredNullable) {
|
||||
return isSubtype(const _NullType(), sEnv, t, tEnv) &&
|
||||
isSubtype(s.asNonNullable, sEnv, t, tEnv);
|
||||
}
|
||||
|
||||
// Type Variable Reflexivity 1 is subsumed by Reflexivity and therefore
|
||||
|
@ -645,7 +649,10 @@ class _TypeUniverse {
|
|||
}
|
||||
|
||||
// Right Nullable:
|
||||
if (t.isNullable) {
|
||||
if (t.isDeclaredNullable) {
|
||||
// `s` is neither null nor declared nullable so return false if `t` is
|
||||
// truly `null`.
|
||||
if (t.isNull) return false;
|
||||
return isSubtype(s, sEnv, t.asNonNullable, tEnv);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue