[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:
Joshua Litt 2022-10-17 23:07:27 +00:00 committed by Commit Queue
parent 2230bc2064
commit 78f95a427b
4 changed files with 63 additions and 85 deletions

View file

@ -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",

View file

@ -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);
});
}

View file

@ -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;
}

View file

@ -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);
}