Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use bounds of type system where possible #899

Merged
merged 3 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,13 @@ export class SafeDsTypeChecker {
* Checks whether {@link type} is a subtype of {@link other}.
*/
isSubtypeOf = (type: Type, other: Type, options: TypeCheckOptions = {}): boolean => {
if (type === UnknownType || other === UnknownType) {
if (type.equals(this.coreTypes.Nothing) || other.equals(this.coreTypes.AnyOrNull)) {
return true;
} else if (type === UnknownType || other === UnknownType) {
return false;
} else if (other instanceof TypeParameterType) {
}

if (other instanceof TypeParameterType) {
const otherUpperBound = this.typeComputer().computeUpperBound(other);
return this.isSubtypeOf(type, otherUpperBound, options);
} else if (other instanceof UnionType) {
Expand Down Expand Up @@ -262,7 +266,9 @@ export class SafeDsTypeChecker {
other: Type,
options: TypeCheckOptions,
): boolean {
if (other instanceof NamedTupleType) {
if (other instanceof ClassType) {
return other.declaration === this.builtinClasses.Any;
} else if (other instanceof NamedTupleType) {
return (
type.length === other.length &&
type.entries.every((typeEntry, i) => {
Expand All @@ -281,6 +287,8 @@ export class SafeDsTypeChecker {
private staticTypeIsSubtypeOf(type: StaticType, other: Type, options: TypeCheckOptions): boolean {
if (other instanceof CallableType) {
return this.isSubtypeOf(this.associatedCallableTypeForStaticType(type), other, options);
} else if (other instanceof ClassType) {
return other.declaration === this.builtinClasses.Any;
} else {
return type.equals(other);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -742,14 +742,22 @@ export class SafeDsTypeComputer {
}
});

// Includes type with unknown supertype
// Group types by their kind
const groupedTypes = this.groupTypes(replacedTypes);
if (groupedTypes.hasTypeWithUnknownSupertype) {
return UnknownType;

// Includes unknown type
if (groupedTypes.containsUnknownType) {
return this.coreTypes.AnyOrNull;
}

// The result must be nullable if any of the types is nullable
const isNullable = replacedTypes.some((it) => it.isExplicitlyNullable);

// Includes unhandled type
if (groupedTypes.containsUnhandledType) {
return this.Any(isNullable);
}

// Class-based types
if (!isEmpty(groupedTypes.classTypes)) {
if (!isEmpty(groupedTypes.enumTypes) || !isEmpty(groupedTypes.enumVariantTypes)) {
Expand Down Expand Up @@ -786,7 +794,8 @@ export class SafeDsTypeComputer {
classTypes: [],
enumTypes: [],
enumVariantTypes: [],
hasTypeWithUnknownSupertype: false,
containsUnhandledType: false,
containsUnknownType: false,
};

for (const type of types) {
Expand All @@ -803,11 +812,14 @@ export class SafeDsTypeComputer {
if (classType instanceof ClassType) {
result.classTypes.push(classType);
} else {
result.hasTypeWithUnknownSupertype = true;
/* c8 ignore next 2 */
result.containsUnknownType = true;
}
} else if (type === UnknownType) {
result.containsUnknownType = true;
} else {
// Other types don't have a clear lowest common supertype
result.hasTypeWithUnknownSupertype = true;
// Since these types don't occur in legal programs, we don't need to handle them better
result.containsUnhandledType = true;
return result;
}
}
Expand Down Expand Up @@ -1022,7 +1034,7 @@ export class SafeDsTypeComputer {
current = this.parentClassType(current);
}

const Any = this.coreTypes.Any.withExplicitNullability(type.isExplicitlyNullable);
const Any = this.Any(type.isExplicitlyNullable);
if (Any instanceof ClassType && !visited.has(Any.declaration)) {
yield Any;
}
Expand Down Expand Up @@ -1072,7 +1084,8 @@ interface GroupTypesResult {
classTypes: ClassType[];
enumTypes: EnumType[];
enumVariantTypes: EnumVariantType[];
hasTypeWithUnknownSupertype: boolean;
containsUnhandledType: boolean;
containsUnknownType: boolean;
}

const NO_SUBSTITUTIONS: TypeParameterSubstitutions = new Map();
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ const basic = async (): Promise<IsSubOrSupertypeOfTest[]> => {
const enumVariantType2 = typeComputer.computeType(enumVariant2) as EnumVariantType;

return [
// Callable type to callable type
{
type1: callableType1,
type2: callableType1,
Expand Down Expand Up @@ -526,6 +527,22 @@ const basic = async (): Promise<IsSubOrSupertypeOfTest[]> => {
type2: enumType1,
expected: false,
},
// Named tuple type to class type
{
type1: factory.createNamedTupleType(),
type2: classType1,
expected: false,
},
{
type1: factory.createNamedTupleType(),
type2: coreTypes.Any,
expected: true,
},
{
type1: factory.createNamedTupleType(),
type2: coreTypes.AnyOrNull,
expected: true,
},
// Named tuple type to named tuple type
{
type1: factory.createNamedTupleType(),
Expand Down Expand Up @@ -604,6 +621,22 @@ const basic = async (): Promise<IsSubOrSupertypeOfTest[]> => {
type2: callableType12,
expected: true,
},
// Static type to class type
{
type1: factory.createStaticType(classType1),
type2: classType1,
expected: false,
},
{
type1: factory.createStaticType(classType1),
type2: coreTypes.Any,
expected: true,
},
{
type1: factory.createStaticType(classType1),
type2: coreTypes.AnyOrNull,
expected: true,
},
// Static type to static type
{
type1: factory.createStaticType(classType1),
Expand Down Expand Up @@ -658,6 +691,16 @@ const basic = async (): Promise<IsSubOrSupertypeOfTest[]> => {
type2: UnknownType,
expected: false,
},
{
type1: coreTypes.Nothing,
type2: UnknownType,
expected: true,
},
{
type1: UnknownType,
type2: coreTypes.AnyOrNull,
expected: true,
},
];
};

Expand Down Expand Up @@ -1011,7 +1054,7 @@ const typeParameterTypes = async (): Promise<IsSubOrSupertypeOfTest[]> => {
{
type1: unresolved,
type2: unbounded,
expected: false,
expected: true,
},
{
type1: coreTypes.AnyOrNull,
Expand Down Expand Up @@ -1107,7 +1150,7 @@ const typeParameterTypes = async (): Promise<IsSubOrSupertypeOfTest[]> => {
{
type1: coreTypes.Nothing,
type2: unresolved,
expected: false,
expected: true,
},

// Compare to some other type
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package tests.typing.lowestCommonSupertype.unhandledType

class C
@Pure fun f() -> (r1: Int, r2: Int)

segment mySegment() {
// $TEST$ serialization List<Any>
»[1, f]«;

// $TEST$ serialization List<Any?>
»[null, f]«;

// $TEST$ serialization List<Any>
»[1, C]«;

// $TEST$ serialization List<Any?>
»[null, C]«;

// $TEST$ serialization List<Any>
»[1, f()]«;

// $TEST$ serialization List<Any?>
»[null, f()]«;
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package tests.typing.lowestCommonSupertype.unknownType

segment mySegment() {
// $TEST$ serialization List<Any?>
»[1, unknown]«;

// $TEST$ serialization List<Any?>
»[null, unknown]«;
}
Loading