[cfe] Implement sufficiency checks for the rest of type shapes

Part of https://github.com/dart-lang/sdk/issues/54998

Change-Id: Ib33391d56208e66dba0396da712ca05d21faece0
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/358453
Commit-Queue: Chloe Stefantsova <cstefantsova@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
This commit is contained in:
Chloe Stefantsova 2024-03-25 14:45:52 +00:00 committed by Commit Queue
parent 2d7af14120
commit 5b0bf46bd2
2 changed files with 363 additions and 4 deletions

View file

@ -1723,6 +1723,275 @@ class TypeSchemaEnvironmentTest extends TypeSchemaEnvironmentTestBase {
checkTargetType: "G<num, Object?>",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.interfaceShape);
checkTypeShapeCheckSufficiency(
expressionStaticType: "(int,)",
checkTargetType: "(dynamic,)",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.recordShape);
checkTypeShapeCheckSufficiency(
expressionStaticType: "(int,)",
checkTargetType: "(num,)",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.recordShape);
checkTypeShapeCheckSufficiency(
expressionStaticType: "(int,)",
checkTargetType: "(String,)",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "(int, {String foo})",
checkTargetType: "(dynamic, {dynamic foo})",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.recordShape);
checkTypeShapeCheckSufficiency(
expressionStaticType: "(int, {String foo})",
checkTargetType: "(dynamic, {String foo})",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.recordShape);
checkTypeShapeCheckSufficiency(
expressionStaticType: "(int, {String foo})",
checkTargetType: "(dynamic, {bool foo})",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "(int, {String foo})",
checkTargetType: "(dynamic, {String bar})",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "(int) -> void",
checkTargetType: "(Never) -> void",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.functionShape);
checkTypeShapeCheckSufficiency(
expressionStaticType: "(num) -> String",
checkTargetType: "(int) -> Object",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.functionShape);
checkTypeShapeCheckSufficiency(
expressionStaticType: "(int) -> bool",
checkTargetType: "(String) -> dynamic",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "(dynamic, {dynamic foo}) -> void",
checkTargetType: "(int, {String foo}) -> void",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.functionShape);
checkTypeShapeCheckSufficiency(
expressionStaticType: "(dynamic, {String foo}) -> Object?",
checkTargetType: "(bool, {String foo}) -> dynamic",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.functionShape);
checkTypeShapeCheckSufficiency(
expressionStaticType: "(dynamic, {bool foo}) -> Never?",
checkTargetType: "(int, {String foo}) -> Null",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "(dynamic, {String foo}) -> Null",
checkTargetType: "(int, {String bar}) -> Null",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "FutureOr<String>",
checkTargetType: "FutureOr<Object?>",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.futureOrShape);
checkTypeShapeCheckSufficiency(
expressionStaticType: "FutureOr<String>",
checkTargetType: "FutureOr<Object>",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.futureOrShape);
checkTypeShapeCheckSufficiency(
expressionStaticType: "FutureOr<int>",
checkTargetType: "FutureOr<num>",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.futureOrShape);
checkTypeShapeCheckSufficiency(
expressionStaticType: "FutureOr<bool>",
checkTargetType: "FutureOr<String>",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "int",
checkTargetType: "(String,)",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "int",
checkTargetType: "(num) -> void",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "bool",
checkTargetType: "FutureOr<void>",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "(int,)",
checkTargetType: "String",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "(int,)",
checkTargetType: "(num) -> void",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "(bool,)",
checkTargetType: "FutureOr<void>",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "(int) -> void",
checkTargetType: "String",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "(int) -> void",
checkTargetType: "(num,)",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "(bool) -> FutureOr<Object?>",
checkTargetType: "FutureOr<void>",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "A<int>",
checkTargetType: "C<int>?",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "A<int>",
checkTargetType: "C<String>?",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "A<int>",
checkTargetType: "C<dynamic>?",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.interfaceShape);
checkTypeShapeCheckSufficiency(
expressionStaticType: "A<int>?",
checkTargetType: "C<dynamic>",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "B",
checkTargetType: "C<int>?",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "B?",
checkTargetType: "C<Object?>",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "B",
checkTargetType: "C<Object?>?",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.interfaceShape);
checkTypeShapeCheckSufficiency(
expressionStaticType: "D<int>",
checkTargetType: "E<int>?",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.interfaceShape);
checkTypeShapeCheckSufficiency(
expressionStaticType: "D<int>?",
checkTargetType: "E<int>",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "D<int>",
checkTargetType: "E<num>?",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.interfaceShape);
checkTypeShapeCheckSufficiency(
expressionStaticType: "D<int>?",
checkTargetType: "E<num>",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "D<int>",
checkTargetType: "E<dynamic>?",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.interfaceShape);
checkTypeShapeCheckSufficiency(
expressionStaticType: "D<int>?",
checkTargetType: "E<dynamic>",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "D<num>",
checkTargetType: "E<int>?",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "D<num>?",
checkTargetType: "E<int>",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "F<int>",
checkTargetType: "G<int, String>?",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "F<int>?",
checkTargetType: "G<int, String>",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "F<int>",
checkTargetType: "G<num, String>?",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "F<int>?",
checkTargetType: "G<num, String>",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "F<int>",
checkTargetType: "G<dynamic, Object?>?",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.interfaceShape);
checkTypeShapeCheckSufficiency(
expressionStaticType: "F<int>?",
checkTargetType: "G<dynamic, Object?>",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "F<int>",
checkTargetType: "G<int, Object?>?",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.interfaceShape);
checkTypeShapeCheckSufficiency(
expressionStaticType: "F<int>?",
checkTargetType: "G<int, Object?>",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
checkTypeShapeCheckSufficiency(
expressionStaticType: "F<int>",
checkTargetType: "G<num, Object?>?",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.interfaceShape);
checkTypeShapeCheckSufficiency(
expressionStaticType: "F<int>?",
checkTargetType: "G<num, Object?>",
typeParameters: "",
sufficiency: TypeShapeCheckSufficiency.insufficient);
}
void checkUpperBound(

View file

@ -340,15 +340,18 @@ abstract class TypeEnvironment extends Types {
{required DartType expressionStaticType,
required DartType checkTargetType,
required SubtypeCheckMode subtypeCheckMode}) {
if (checkTargetType is! InterfaceType ||
expressionStaticType is! InterfaceType) {
// TODO(cstefantsova): Support record, function, and futureOr types.
if (!IsSubtypeOf.basedSolelyOnNullabilities(
expressionStaticType, checkTargetType)
.inMode(subtypeCheckMode)) {
return TypeShapeCheckSufficiency.insufficient;
} else {
} else if (checkTargetType is InterfaceType &&
expressionStaticType is InterfaceType) {
// Analyze if an interface shape check is sufficient.
// If `T` in `e is/as T` doesn't have type arguments, there's nothing more to
// check besides the shape.
// TODO(cstefantsova): Investigate if [expressionStaticType] can be of any
// kind for the following sufficiency check to work.
if (checkTargetType.typeArguments.isEmpty) {
return TypeShapeCheckSufficiency.interfaceShape;
}
@ -366,12 +369,17 @@ abstract class TypeEnvironment extends Types {
for (int typeParameterIndex = 0;
typeParameterIndex < checkTargetTypeOwnTypeParameters.length;
typeParameterIndex++) {
// TODO(cstefantsova): Investigate if super-bounded types can appear as
// [checkTargetType]s. In that case a subtype check should be done
// instead of the mutual subtype check.
if (!_isRawTypeArgumentEquivalent(checkTargetType, typeParameterIndex,
subtypeCheckMode: subtypeCheckMode)) {
targetTypeArgumentsAreDefaultTypes = false;
break;
}
}
// TODO(cstefantsova): Investigate if [expressionStaticType] can be of any
// kind for the following sufficiency check to work.
if (targetTypeArgumentsAreDefaultTypes) {
return TypeShapeCheckSufficiency.interfaceShape;
}
@ -526,6 +534,79 @@ abstract class TypeEnvironment extends Types {
return TypeShapeCheckSufficiency.insufficient;
}
}
} else if (checkTargetType is RecordType &&
expressionStaticType is RecordType) {
bool isTopRecordTypeForTheShape = true;
for (DartType positional in checkTargetType.positional) {
if (!isTop(positional)) {
isTopRecordTypeForTheShape = false;
break;
}
}
for (NamedType named in checkTargetType.named) {
if (!isTop(named.type)) {
isTopRecordTypeForTheShape = false;
break;
}
}
if (isTopRecordTypeForTheShape) {
// TODO(cstefantsova): Investigate if [expressionStaticType] can be of
// any kind for the following sufficiency check to work.
return TypeShapeCheckSufficiency.recordShape;
}
if (isSubtypeOf(
expressionStaticType, checkTargetType, subtypeCheckMode)) {
return TypeShapeCheckSufficiency.recordShape;
} else {
return TypeShapeCheckSufficiency.insufficient;
}
} else if (checkTargetType is FunctionType &&
expressionStaticType is FunctionType) {
if (checkTargetType.typeParameters.isEmpty &&
expressionStaticType.typeParameters.isEmpty) {
bool isTopFunctionTypeForTheShape = true;
for (DartType positional in checkTargetType.positionalParameters) {
if (!isBottom(positional)) {
isTopFunctionTypeForTheShape = false;
}
}
for (NamedType named in checkTargetType.namedParameters) {
if (!isBottom(named.type)) {
isTopFunctionTypeForTheShape = false;
}
}
if (!isTop(checkTargetType.returnType)) {
isTopFunctionTypeForTheShape = false;
}
if (isTopFunctionTypeForTheShape) {
// TODO(cstefantsova): Investigate if [expressionStaticType] can be of
// any kind for the following sufficiency check to work.
return TypeShapeCheckSufficiency.functionShape;
}
}
if (isSubtypeOf(
expressionStaticType, checkTargetType, subtypeCheckMode)) {
return TypeShapeCheckSufficiency.functionShape;
} else {
return TypeShapeCheckSufficiency.insufficient;
}
} else if (checkTargetType is FutureOrType &&
expressionStaticType is FutureOrType) {
if (isTop(checkTargetType.typeArgument)) {
// TODO(cstefantsova): Investigate if [expressionStaticType] can be of
// any kind for the following sufficiency check to work.
return TypeShapeCheckSufficiency.futureOrShape;
} else if (isSubtypeOf(expressionStaticType.typeArgument,
checkTargetType.typeArgument, subtypeCheckMode)) {
return TypeShapeCheckSufficiency.futureOrShape;
} else {
return TypeShapeCheckSufficiency.insufficient;
}
} else {
return TypeShapeCheckSufficiency.insufficient;
}
}
}
@ -766,6 +847,15 @@ class IsSubtypeOf {
}
return "IsSubtypeOf.<unknown value '${_value}'>";
}
bool inMode(SubtypeCheckMode subtypeCheckMode) {
switch (subtypeCheckMode) {
case SubtypeCheckMode.withNullabilities:
return isSubtypeWhenUsingNullabilities();
case SubtypeCheckMode.ignoringNullabilities:
return isSubtypeWhenIgnoringNullabilities();
}
}
}
enum SubtypeCheckMode {