diff --git a/packages/safe-ds-lang/src/language/typing/safe-ds-type-checker.ts b/packages/safe-ds-lang/src/language/typing/safe-ds-type-checker.ts index ab910b340..3b99f0f09 100644 --- a/packages/safe-ds-lang/src/language/typing/safe-ds-type-checker.ts +++ b/packages/safe-ds-lang/src/language/typing/safe-ds-type-checker.ts @@ -50,12 +50,14 @@ export class SafeDsTypeChecker { * Checks whether {@link type} is a subtype of {@link other}. */ isSubtypeOf = (type: Type, other: Type, options: TypeCheckOptions = {}): boolean => { + // Handle base cases if (type.equals(this.coreTypes.Nothing) || other.equals(this.coreTypes.AnyOrNull)) { return true; } else if (type === UnknownType || other === UnknownType) { return false; } + // Handle type parameter types if (other instanceof TypeParameterType) { if (type.isExplicitlyNullable && !other.isExplicitlyNullable) { return false; @@ -68,10 +70,16 @@ export class SafeDsTypeChecker { const otherLowerBound = this.coreTypes.Nothing.withExplicitNullability(other.isExplicitlyNullable); return this.isSubtypeOf(type, otherLowerBound, options); + } + + // Handle union types + if (type instanceof UnionType) { + return type.types.every((it) => this.isSubtypeOf(it, other, options)); } else if (other instanceof UnionType) { return other.types.some((it) => this.isSubtypeOf(type, it, options)); } + // Handle other cases if (type instanceof CallableType) { return this.callableTypeIsSubtypeOf(type, other, options); } else if (type instanceof ClassType) { @@ -88,8 +96,6 @@ export class SafeDsTypeChecker { return this.staticTypeIsSubtypeOf(type, other, options); } else if (type instanceof TypeParameterType) { return this.typeParameterTypeIsSubtypeOf(type, other, options); - } else if (type instanceof UnionType) { - return this.unionTypeIsSubtypeOf(type, other, options); } /* c8 ignore start */ else { throw new Error(`Unexpected type: ${type.constructor.name}`); } /* c8 ignore stop */ @@ -312,10 +318,6 @@ export class SafeDsTypeChecker { return this.isSubtypeOf(upperBound, other, options); } - private unionTypeIsSubtypeOf(type: UnionType, other: Type, options: TypeCheckOptions): boolean { - return type.types.every((it) => this.isSubtypeOf(it, other, options)); - } - // ----------------------------------------------------------------------------------------------------------------- // Special cases // ----------------------------------------------------------------------------------------------------------------- diff --git a/packages/safe-ds-lang/tests/language/typing/type checker/isSubOrSupertypeOf.test.ts b/packages/safe-ds-lang/tests/language/typing/type checker/isSubOrSupertypeOf.test.ts index 247a85fc7..b182cac2c 100644 --- a/packages/safe-ds-lang/tests/language/typing/type checker/isSubOrSupertypeOf.test.ts +++ b/packages/safe-ds-lang/tests/language/typing/type checker/isSubOrSupertypeOf.test.ts @@ -59,8 +59,6 @@ const basic = async (): Promise => { class Class2() sub Class1 class Class3 - class Class4 - enum Enum1 { Variant1(p: Int) Variant2 @@ -654,7 +652,23 @@ const basic = async (): Promise => { type2: enumType1, expected: false, }, - // Union type to X + // Union type to union type + { + type1: factory.createUnionType(classType1, classType2), + type2: factory.createUnionType(classType1, classType2), + expected: true, + }, + { + type1: factory.createUnionType(classType1, classType2), + type2: factory.createUnionType(classType1, classType3), + expected: true, + }, + { + type1: factory.createUnionType(classType1, classType3), + type2: factory.createUnionType(classType1, classType2), + expected: false, + }, + // Union type to other { type1: factory.createUnionType(), type2: classType1,