diff --git a/packages/safe-ds-lang/src/language/typing/model.ts b/packages/safe-ds-lang/src/language/typing/model.ts index 2b2f9984d..275b5bb05 100644 --- a/packages/safe-ds-lang/src/language/typing/model.ts +++ b/packages/safe-ds-lang/src/language/typing/model.ts @@ -20,9 +20,11 @@ export type TypeParameterSubstitutions = Map; */ export abstract class Type { /** - * Whether this type allows `null` as a value. + * Whether this type is explicitly marked as nullable (e.g. using a `?` for named types). A type parameter type can + * also become nullable if its upper bound is nullable, which is not covered here. Use {@link TypeChecker.canBeNull} + * if you need to cover this. */ - abstract isNullable: boolean; + abstract isExplicitlyNullable: boolean; /** * Returns whether the type is equal to another type. @@ -47,11 +49,11 @@ export abstract class Type { /** * Returns a copy of this type with the given nullability. */ - abstract updateNullability(isNullable: boolean): Type; + abstract updateExplicitNullability(isExplicitlyNullable: boolean): Type; } export class CallableType extends Type { - override isNullable: boolean = false; + override isExplicitlyNullable: boolean = false; constructor( readonly callable: SdsCallable, @@ -114,8 +116,8 @@ export class CallableType extends Type { ); } - override updateNullability(isNullable: boolean): Type { - if (!isNullable) { + override updateExplicitNullability(isExplicitlyNullable: boolean): Type { + if (!isExplicitlyNullable) { return this; } @@ -125,7 +127,7 @@ export class CallableType extends Type { export class LiteralType extends Type { readonly constants: Constant[]; - private _isNullable: boolean | undefined; + private _isExplicitlyNullable: boolean | undefined; constructor(...constants: Constant[]) { super(); @@ -133,12 +135,12 @@ export class LiteralType extends Type { this.constants = constants; } - override get isNullable(): boolean { - if (this._isNullable === undefined) { - this._isNullable = this.constants.some((it) => it === NullConstant); + override get isExplicitlyNullable(): boolean { + if (this._isExplicitlyNullable === undefined) { + this._isExplicitlyNullable = this.constants.some((it) => it === NullConstant); } - return this._isNullable; + return this._isExplicitlyNullable; } override equals(other: unknown): boolean { @@ -166,10 +168,10 @@ export class LiteralType extends Type { return this; } - override updateNullability(isNullable: boolean): LiteralType { - if (this.isNullable && !isNullable) { + override updateExplicitNullability(isExplicitlyNullable: boolean): LiteralType { + if (this.isExplicitlyNullable && !isExplicitlyNullable) { return new LiteralType(...this.constants.filter((it) => it !== NullConstant)); - } else if (!this.isNullable && isNullable) { + } else if (!this.isExplicitlyNullable && isExplicitlyNullable) { return new LiteralType(...this.constants, NullConstant); } else { return this; @@ -179,7 +181,7 @@ export class LiteralType extends Type { export class NamedTupleType extends Type { readonly entries: NamedTupleEntry[]; - override readonly isNullable = false; + override readonly isExplicitlyNullable = false; constructor(...entries: NamedTupleEntry[]) { super(); @@ -237,8 +239,8 @@ export class NamedTupleType extends Type { return new NamedTupleType(...this.entries.map((it) => it.unwrap())); } - override updateNullability(isNullable: boolean): Type { - if (!isNullable) { + override updateExplicitNullability(isExplicitlyNullable: boolean): Type { + if (!isExplicitlyNullable) { return this; } @@ -289,14 +291,14 @@ export abstract class NamedType extends Type { } override toString(): string { - if (this.isNullable) { + if (this.isExplicitlyNullable) { return `${this.declaration.name}?`; } else { return this.declaration.name; } } - abstract override updateNullability(isNullable: boolean): NamedType; + abstract override updateExplicitNullability(isExplicitlyNullable: boolean): NamedType; unwrap(): NamedType { return this; @@ -307,7 +309,7 @@ export class ClassType extends NamedType { constructor( declaration: SdsClass, readonly substitutions: TypeParameterSubstitutions, - override readonly isNullable: boolean, + override readonly isExplicitlyNullable: boolean, ) { super(declaration); } @@ -330,7 +332,7 @@ export class ClassType extends NamedType { return ( other.declaration === this.declaration && - other.isNullable === this.isNullable && + other.isExplicitlyNullable === this.isExplicitlyNullable && substitutionsAreEqual(other.substitutions, this.substitutions) ); } @@ -344,7 +346,7 @@ export class ClassType extends NamedType { .join(', ')}>`; } - if (this.isNullable) { + if (this.isExplicitlyNullable) { result += '?'; } @@ -360,27 +362,27 @@ export class ClassType extends NamedType { stream(this.substitutions).map(([key, value]) => [key, value.substituteTypeParameters(substitutions)]), ); - return new ClassType(this.declaration, newSubstitutions, this.isNullable); + return new ClassType(this.declaration, newSubstitutions, this.isExplicitlyNullable); } override unwrap(): ClassType { const newSubstitutions = new Map(stream(this.substitutions).map(([key, value]) => [key, value.unwrap()])); - return new ClassType(this.declaration, newSubstitutions, this.isNullable); + return new ClassType(this.declaration, newSubstitutions, this.isExplicitlyNullable); } - override updateNullability(isNullable: boolean): ClassType { - if (this.isNullable === isNullable) { + override updateExplicitNullability(isExplicitlyNullable: boolean): ClassType { + if (this.isExplicitlyNullable === isExplicitlyNullable) { return this; } - return new ClassType(this.declaration, this.substitutions, isNullable); + return new ClassType(this.declaration, this.substitutions, isExplicitlyNullable); } } export class EnumType extends NamedType { constructor( declaration: SdsEnum, - override readonly isNullable: boolean, + override readonly isExplicitlyNullable: boolean, ) { super(declaration); } @@ -392,26 +394,26 @@ export class EnumType extends NamedType { return false; } - return other.declaration === this.declaration && other.isNullable === this.isNullable; + return other.declaration === this.declaration && other.isExplicitlyNullable === this.isExplicitlyNullable; } override substituteTypeParameters(_substitutions: TypeParameterSubstitutions): Type { return this; } - override updateNullability(isNullable: boolean): EnumType { - if (this.isNullable === isNullable) { + override updateExplicitNullability(isExplicitlyNullable: boolean): EnumType { + if (this.isExplicitlyNullable === isExplicitlyNullable) { return this; } - return new EnumType(this.declaration, isNullable); + return new EnumType(this.declaration, isExplicitlyNullable); } } export class EnumVariantType extends NamedType { constructor( declaration: SdsEnumVariant, - override readonly isNullable: boolean, + override readonly isExplicitlyNullable: boolean, ) { super(declaration); } @@ -423,26 +425,26 @@ export class EnumVariantType extends NamedType { return false; } - return other.declaration === this.declaration && other.isNullable === this.isNullable; + return other.declaration === this.declaration && other.isExplicitlyNullable === this.isExplicitlyNullable; } override substituteTypeParameters(_substitutions: TypeParameterSubstitutions): Type { return this; } - override updateNullability(isNullable: boolean): EnumVariantType { - if (this.isNullable === isNullable) { + override updateExplicitNullability(isExplicitlyNullable: boolean): EnumVariantType { + if (this.isExplicitlyNullable === isExplicitlyNullable) { return this; } - return new EnumVariantType(this.declaration, isNullable); + return new EnumVariantType(this.declaration, isExplicitlyNullable); } } export class TypeParameterType extends NamedType { constructor( declaration: SdsTypeParameter, - override readonly isNullable: boolean, + override readonly isExplicitlyNullable: boolean, ) { super(declaration); } @@ -454,7 +456,7 @@ export class TypeParameterType extends NamedType { return false; } - return other.declaration === this.declaration && other.isNullable === this.isNullable; + return other.declaration === this.declaration && other.isExplicitlyNullable === this.isExplicitlyNullable; } override substituteTypeParameters(substitutions: TypeParameterSubstitutions): Type { @@ -462,19 +464,19 @@ export class TypeParameterType extends NamedType { if (!substitution) { return this; - } else if (this.isNullable) { - return substitution.updateNullability(true); + } else if (this.isExplicitlyNullable) { + return substitution.updateExplicitNullability(true); } else { return substitution; } } - override updateNullability(isNullable: boolean): TypeParameterType { - if (this.isNullable === isNullable) { + override updateExplicitNullability(isExplicitlyNullable: boolean): TypeParameterType { + if (this.isExplicitlyNullable === isExplicitlyNullable) { return this; } - return new TypeParameterType(this.declaration, isNullable); + return new TypeParameterType(this.declaration, isExplicitlyNullable); } } @@ -482,7 +484,7 @@ export class TypeParameterType extends NamedType { * A type that represents an actual class, enum, or enum variant instead of an instance of it. */ export class StaticType extends Type { - override readonly isNullable = false; + override readonly isExplicitlyNullable = false; constructor(readonly instanceType: NamedType) { super(); @@ -512,8 +514,8 @@ export class StaticType extends Type { return this; } - override updateNullability(isNullable: boolean): Type { - if (!isNullable) { + override updateExplicitNullability(isExplicitlyNullable: boolean): Type { + if (!isExplicitlyNullable) { return this; } @@ -523,7 +525,7 @@ export class StaticType extends Type { export class UnionType extends Type { readonly possibleTypes: Type[]; - private _isNullable: boolean | undefined; + private _isExplicitlyNullable: boolean | undefined; constructor(...possibleTypes: Type[]) { super(); @@ -531,12 +533,12 @@ export class UnionType extends Type { this.possibleTypes = possibleTypes; } - override get isNullable(): boolean { - if (this._isNullable === undefined) { - this._isNullable = this.possibleTypes.some((it) => it.isNullable); + override get isExplicitlyNullable(): boolean { + if (this._isExplicitlyNullable === undefined) { + this._isExplicitlyNullable = this.possibleTypes.some((it) => it.isExplicitlyNullable); } - return this._isNullable; + return this._isExplicitlyNullable; } override equals(other: unknown): boolean { @@ -583,14 +585,14 @@ export class UnionType extends Type { return new UnionType(...newPossibleTypes); } - override updateNullability(isNullable: boolean): Type { - if (this.isNullable && !isNullable) { - return new UnionType(...this.possibleTypes.map((it) => it.updateNullability(false))); - } else if (!this.isNullable && isNullable) { + override updateExplicitNullability(isExplicitlyNullable: boolean): Type { + if (this.isExplicitlyNullable && !isExplicitlyNullable) { + return new UnionType(...this.possibleTypes.map((it) => it.updateExplicitNullability(false))); + } else if (!this.isExplicitlyNullable && isExplicitlyNullable) { if (isEmpty(this.possibleTypes)) { return new LiteralType(NullConstant); } else { - return new UnionType(...this.possibleTypes.map((it) => it.updateNullability(true))); + return new UnionType(...this.possibleTypes.map((it) => it.updateExplicitNullability(true))); } } else { return this; @@ -599,7 +601,7 @@ export class UnionType extends Type { } class UnknownTypeClass extends Type { - readonly isNullable = false; + readonly isExplicitlyNullable = false; override equals(other: unknown): boolean { return other instanceof UnknownTypeClass; @@ -617,7 +619,7 @@ class UnknownTypeClass extends Type { return this; } - override updateNullability(_isNullable: boolean): Type { + override updateExplicitNullability(_isExplicitlyNullable: boolean): Type { return this; } } 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 9660e7cd1..477584233 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 @@ -164,7 +164,7 @@ export class SafeDsTypeChecker { } private classTypeIsSubtypeOf(type: ClassType, other: Type, options: TypeCheckOptions): boolean { - if (type.isNullable && !other.isNullable) { + if (type.isExplicitlyNullable && !other.isExplicitlyNullable) { return false; } else if (type.declaration === this.builtinClasses.Nothing) { return true; @@ -207,7 +207,7 @@ export class SafeDsTypeChecker { } private enumTypeIsSubtypeOf(type: EnumType, other: Type): boolean { - if (type.isNullable && !other.isNullable) { + if (type.isExplicitlyNullable && !other.isExplicitlyNullable) { return false; } @@ -221,7 +221,7 @@ export class SafeDsTypeChecker { } private enumVariantTypeIsSubtypeOf(type: EnumVariantType, other: Type): boolean { - if (type.isNullable && !other.isNullable) { + if (type.isExplicitlyNullable && !other.isExplicitlyNullable) { return false; } @@ -238,18 +238,18 @@ export class SafeDsTypeChecker { } private literalTypeIsSubtypeOf(type: LiteralType, other: Type, options: TypeCheckOptions): boolean { - if (type.isNullable && !other.isNullable) { + if (type.isExplicitlyNullable && !other.isExplicitlyNullable) { return false; } else if (type.constants.length === 0) { // Empty literal types are equivalent to `Nothing` and assignable to any type return true; } else if (type.constants.every((it) => it === NullConstant)) { // Literal types containing only `null` are equivalent to `Nothing?` and assignable to any nullable type - return other.isNullable; + return other.isExplicitlyNullable; } if (other instanceof ClassType) { - if (other.equals(this.coreTypes.Any.updateNullability(type.isNullable))) { + if (other.equals(this.coreTypes.Any.updateExplicitNullability(type.isExplicitlyNullable))) { return true; } @@ -376,15 +376,15 @@ export class SafeDsTypeChecker { }; /** - * Returns whether {@link type} can be `null`. Compared to {@link Type.isNullable}, this method also considers the + * Returns whether {@link type} can be `null`. Compared to {@link Type.isExplicitlyNullable}, this method also considers the * upper bound of type parameter types. */ canBeNull = (type: Type): boolean => { - if (type.isNullable) { + if (type.isExplicitlyNullable) { return true; } else if (type instanceof TypeParameterType) { const upperBound = this.typeComputer().computeUpperBound(type); - return upperBound.isNullable; + return upperBound.isExplicitlyNullable; } else { return false; } @@ -417,7 +417,7 @@ export class SafeDsTypeChecker { * Checks whether {@link type} is some kind of list (with any element type). */ isList(type: Type): type is ClassType | TypeParameterType { - const listOrNull = this.coreTypes.List(UnknownType).updateNullability(true); + const listOrNull = this.coreTypes.List(UnknownType).updateExplicitNullability(true); return ( !type.equals(this.coreTypes.Nothing) && @@ -432,7 +432,7 @@ export class SafeDsTypeChecker { * Checks whether {@link type} is some kind of map (with any key/value types). */ isMap(type: Type): type is ClassType | TypeParameterType { - const mapOrNull = this.coreTypes.Map(UnknownType, UnknownType).updateNullability(true); + const mapOrNull = this.coreTypes.Map(UnknownType, UnknownType).updateExplicitNullability(true); return ( !type.equals(this.coreTypes.Nothing) && diff --git a/packages/safe-ds-lang/src/language/typing/safe-ds-type-computer.ts b/packages/safe-ds-lang/src/language/typing/safe-ds-type-computer.ts index 8a740311e..d9dde4de4 100644 --- a/packages/safe-ds-lang/src/language/typing/safe-ds-type-computer.ts +++ b/packages/safe-ds-lang/src/language/typing/safe-ds-type-computer.ts @@ -460,7 +460,7 @@ export class SafeDsTypeComputer { } // Update nullability - return result.updateNullability(receiverType.isNullable && node.isNullSafe); + return result.updateExplicitNullability(receiverType.isExplicitlyNullable && node.isNullSafe); } private computeTypeOfExpressionLambda(node: SdsExpressionLambda): Type { @@ -488,13 +488,17 @@ export class SafeDsTypeComputer { // Receiver is a list const listType = this.computeMatchingSupertype(receiverType, this.coreClasses.List); if (listType) { - return listType.getTypeParameterTypeByIndex(0).updateNullability(listType.isNullable && node.isNullSafe); + return listType + .getTypeParameterTypeByIndex(0) + .updateExplicitNullability(listType.isExplicitlyNullable && node.isNullSafe); } // Receiver is a map const mapType = this.computeMatchingSupertype(receiverType, this.coreClasses.Map); if (mapType) { - return mapType.getTypeParameterTypeByIndex(1).updateNullability(mapType.isNullable && node.isNullSafe); + return mapType + .getTypeParameterTypeByIndex(1) + .updateExplicitNullability(mapType.isExplicitlyNullable && node.isNullSafe); } return UnknownType; @@ -516,9 +520,9 @@ export class SafeDsTypeComputer { private computeTypeOfElvisOperation(node: SdsInfixOperation): Type { const leftOperandType = this.computeType(node.leftOperand); - if (leftOperandType.isNullable) { + if (leftOperandType.isExplicitlyNullable) { const rightOperandType = this.computeType(node.rightOperand); - return this.lowestCommonSupertype(leftOperandType.updateNullability(false), rightOperandType); + return this.lowestCommonSupertype(leftOperandType.updateExplicitNullability(false), rightOperandType); } else { return leftOperandType; } @@ -550,7 +554,9 @@ export class SafeDsTypeComputer { } // Update nullability - return result.updateNullability((receiverType.isNullable && node.isNullSafe) || result.isNullable); + return result.updateExplicitNullability( + (receiverType.isExplicitlyNullable && node.isNullSafe) || result.isExplicitlyNullable, + ); } private computeTypeOfArithmeticPrefixOperation(node: SdsPrefixOperation): Type { @@ -568,7 +574,7 @@ export class SafeDsTypeComputer { const instanceType = this.computeType(target); if (isSdsNamedTypeDeclaration(target) && instanceType instanceof NamedType) { - return new StaticType(instanceType.updateNullability(false)); + return new StaticType(instanceType.updateExplicitNullability(false)); } else { return instanceType; } @@ -601,7 +607,7 @@ export class SafeDsTypeComputer { } private computeTypeOfNamedType(node: SdsNamedType) { - const unparameterizedType = this.computeType(node.declaration?.ref).updateNullability(node.isNullable); + const unparameterizedType = this.computeType(node.declaration?.ref).updateExplicitNullability(node.isNullable); if (!(unparameterizedType instanceof ClassType)) { return unparameterizedType; } @@ -705,7 +711,7 @@ export class SafeDsTypeComputer { continue; } - let otherType = newPossibleTypes[j]!; + const otherType = newPossibleTypes[j]!; // Don't merge `Nothing?` into callable types, named tuple types or static types, since that would // create another union type. @@ -728,10 +734,26 @@ export class SafeDsTypeComputer { break; } - // Remove subtypes of other types - otherType = otherType.updateNullability(currentType.isNullable || otherType.isNullable); - if (this.typeChecker.isSubtypeOf(currentType, otherType)) { - newPossibleTypes.splice(j, 1, otherType); // Update nullability + // Remove subtypes of other types. Type parameter types are a special case, since there might be a + // subtype relation between `currentType` and `otherType` in both directions. We always keep the type + // parameter type in this case for consistency, since it can be narrower if it has a lower bound. + if (currentType instanceof TypeParameterType) { + const candidateType = currentType.updateExplicitNullability( + currentType.isExplicitlyNullable || otherType.isExplicitlyNullable, + ); + + if (this.typeChecker.isSubtypeOf(otherType, candidateType)) { + newPossibleTypes.splice(j, 1, candidateType); + newPossibleTypes.splice(i, 1); + break; + } + } + + const candidateType = otherType.updateExplicitNullability( + currentType.isExplicitlyNullable || otherType.isExplicitlyNullable, + ); + if (this.typeChecker.isSubtypeOf(currentType, candidateType)) { + newPossibleTypes.splice(j, 1, candidateType); // Update nullability newPossibleTypes.splice(i, 1); break; } @@ -753,7 +775,7 @@ export class SafeDsTypeComputer { * Returns the non-nullable type for the given type. The result is simplified as much as possible. */ computeNonNullableType(type: Type): Type { - return this.simplifyType(type.updateNullability(false)); + return this.simplifyType(type.updateExplicitNullability(false)); } /** @@ -850,7 +872,7 @@ export class SafeDsTypeComputer { } const result = this.doComputeUpperBound(type, new Set(), options); - return result.updateNullability(result.isNullable || type.isNullable); + return result.updateExplicitNullability(result.isExplicitlyNullable || type.isExplicitlyNullable); } private doComputeUpperBound( @@ -906,7 +928,7 @@ export class SafeDsTypeComputer { return UnknownType; } - const isNullable = simplifiedTypes.some((it) => it.isNullable); + const isNullable = simplifiedTypes.some((it) => it.isExplicitlyNullable); // Class-based types if (!isEmpty(groupedTypes.classTypes) || !isEmpty(groupedTypes.constants)) { @@ -1021,9 +1043,9 @@ export class SafeDsTypeComputer { const candidates: Type[] = []; if (!isEmpty(enumTypes)) { - candidates.push(enumTypes[0]!.updateNullability(isNullable)); + candidates.push(enumTypes[0]!.updateExplicitNullability(isNullable)); } else if (!isEmpty(enumVariantTypes)) { - candidates.push(enumVariantTypes[0]!.updateNullability(isNullable)); + candidates.push(enumVariantTypes[0]!.updateExplicitNullability(isNullable)); const containingEnum = getContainerOfType(enumVariantTypes[0]!.declaration, isSdsEnum); if (containingEnum) { @@ -1111,7 +1133,7 @@ export class SafeDsTypeComputer { current = this.parentClassType(current); } - const Any = this.coreTypes.Any.updateNullability(type.isNullable); + const Any = this.coreTypes.Any.updateExplicitNullability(type.isExplicitlyNullable); if (Any instanceof ClassType && !visited.has(Any.declaration)) { yield Any; } @@ -1127,7 +1149,7 @@ export class SafeDsTypeComputer { const firstParentType = this.computeType(firstParentTypeNode, type.substitutions); if (firstParentType instanceof ClassType) { - return firstParentType.updateNullability(type.isNullable); + return firstParentType.updateExplicitNullability(type.isExplicitlyNullable); } return undefined; } diff --git a/packages/safe-ds-lang/src/language/validation/inheritance.ts b/packages/safe-ds-lang/src/language/validation/inheritance.ts index 5f3b20c09..6f3abb8a8 100644 --- a/packages/safe-ds-lang/src/language/validation/inheritance.ts +++ b/packages/safe-ds-lang/src/language/validation/inheritance.ts @@ -122,7 +122,7 @@ export const classMustOnlyInheritASingleClass = (services: SafeDsServices) => { node: firstParentType!, code: CODE_INHERITANCE_NOT_A_CLASS, }); - } else if (computedType.isNullable) { + } else if (computedType.isExplicitlyNullable) { accept('error', 'The parent type must not be nullable.', { node: firstParentType!, code: CODE_INHERITANCE_NULLABLE, diff --git a/packages/safe-ds-lang/tests/language/typing/model.test.ts b/packages/safe-ds-lang/tests/language/typing/model.test.ts index 2ea2b4aa5..f1f8a039f 100644 --- a/packages/safe-ds-lang/tests/language/typing/model.test.ts +++ b/packages/safe-ds-lang/tests/language/typing/model.test.ts @@ -435,7 +435,7 @@ describe('type model', async () => { }); }); - const updateNullabilityTests: UpdateNullabilityTest[] = [ + const updateExplicitNullabilityTests: UpdateExplicitNullabilityTest[] = [ { type: new CallableType(callable1, undefined, new NamedTupleType(), new NamedTupleType()), isNullable: true, @@ -573,9 +573,9 @@ describe('type model', async () => { expectedType: UnknownType, }, ]; - describe.each(updateNullabilityTests)('updateNullability', ({ type, isNullable, expectedType }) => { + describe.each(updateExplicitNullabilityTests)('updateExplicitNullability', ({ type, isNullable, expectedType }) => { it(`should return the expected value (${type.constructor.name} -- ${type})`, () => { - const actual = type.updateNullability(isNullable); + const actual = type.updateExplicitNullability(isNullable); expectEqualTypes(actual, expectedType); }); }); @@ -686,9 +686,9 @@ interface UnwrapTest { } /** - * Tests for {@link Type.updateNullability}. + * Tests for {@link Type.updateExplicitNullability}. */ -interface UpdateNullabilityTest { +interface UpdateExplicitNullabilityTest { /** * The type to test. */ diff --git a/packages/safe-ds-lang/tests/language/typing/type checker/canBeTypeOfConstantParameter.test.ts b/packages/safe-ds-lang/tests/language/typing/type checker/canBeTypeOfConstantParameter.test.ts index c2280c2ed..ddd4be359 100644 --- a/packages/safe-ds-lang/tests/language/typing/type checker/canBeTypeOfConstantParameter.test.ts +++ b/packages/safe-ds-lang/tests/language/typing/type checker/canBeTypeOfConstantParameter.test.ts @@ -112,8 +112,8 @@ describe('SafeDsTypeChecker', async () => { expect(typeChecker.canBeTypeOfConstantParameter(type)).toBe(expected); }); - it(type.updateNullability(true).toString, () => { - expect(typeChecker.canBeTypeOfConstantParameter(type.updateNullability(true))).toBe(expected); + it(type.updateExplicitNullability(true).toString, () => { + expect(typeChecker.canBeTypeOfConstantParameter(type.updateExplicitNullability(true))).toBe(expected); }); }); }); 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 9f468f1f8..ba1417e7c 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 @@ -225,13 +225,13 @@ const basic = async (): Promise => { expected: true, }, { - type1: classType2.updateNullability(true), + type1: classType2.updateExplicitNullability(true), type2: classType1, expected: false, }, { - type1: classType2.updateNullability(true), - type2: classType1.updateNullability(true), + type1: classType2.updateExplicitNullability(true), + type2: classType1.updateExplicitNullability(true), expected: true, }, // Class type to union type @@ -263,12 +263,12 @@ const basic = async (): Promise => { expected: true, }, { - type1: enumType1.updateNullability(true), + type1: enumType1.updateExplicitNullability(true), type2: coreTypes.Any, expected: false, }, { - type1: enumType1.updateNullability(true), + type1: enumType1.updateExplicitNullability(true), type2: coreTypes.AnyOrNull, expected: true, }, @@ -284,13 +284,13 @@ const basic = async (): Promise => { expected: false, }, { - type1: enumType1.updateNullability(true), + type1: enumType1.updateExplicitNullability(true), type2: enumType1, expected: false, }, { - type1: enumType1.updateNullability(true), - type2: enumType1.updateNullability(true), + type1: enumType1.updateExplicitNullability(true), + type2: enumType1.updateExplicitNullability(true), expected: true, }, // Enum type to union type @@ -322,12 +322,12 @@ const basic = async (): Promise => { expected: true, }, { - type1: enumVariantType1.updateNullability(true), + type1: enumVariantType1.updateExplicitNullability(true), type2: coreTypes.Any, expected: false, }, { - type1: enumVariantType1.updateNullability(true), + type1: enumVariantType1.updateExplicitNullability(true), type2: coreTypes.AnyOrNull, expected: true, }, @@ -343,13 +343,13 @@ const basic = async (): Promise => { expected: false, }, { - type1: enumVariantType1.updateNullability(true), + type1: enumVariantType1.updateExplicitNullability(true), type2: enumType1, expected: false, }, { - type1: enumVariantType1.updateNullability(true), - type2: enumType1.updateNullability(true), + type1: enumVariantType1.updateExplicitNullability(true), + type2: enumType1.updateExplicitNullability(true), expected: true, }, // Enum variant type to enum variant type @@ -364,13 +364,13 @@ const basic = async (): Promise => { expected: false, }, { - type1: enumVariantType1.updateNullability(true), + type1: enumVariantType1.updateExplicitNullability(true), type2: enumVariantType1, expected: false, }, { - type1: enumVariantType1.updateNullability(true), - type2: enumVariantType1.updateNullability(true), + type1: enumVariantType1.updateExplicitNullability(true), + type2: enumVariantType1.updateExplicitNullability(true), expected: true, }, // Enum variant type to union type @@ -433,7 +433,7 @@ const basic = async (): Promise => { }, { type1: new LiteralType(new IntConstant(1n), NullConstant), - type2: coreTypes.Int.updateNullability(true), + type2: coreTypes.Int.updateExplicitNullability(true), expected: true, }, { @@ -516,12 +516,12 @@ const basic = async (): Promise => { }, { type1: new LiteralType(NullConstant), - type2: enumType1.updateNullability(true), + type2: enumType1.updateExplicitNullability(true), expected: true, }, { type1: new LiteralType(NullConstant, NullConstant), - type2: enumType1.updateNullability(true), + type2: enumType1.updateExplicitNullability(true), expected: true, }, { @@ -646,13 +646,13 @@ const basic = async (): Promise => { expected: false, }, { - type1: new UnionType(classType1.updateNullability(true)), + type1: new UnionType(classType1.updateExplicitNullability(true)), type2: classType1, expected: false, }, { - type1: new UnionType(classType1.updateNullability(true)), - type2: classType1.updateNullability(true), + type1: new UnionType(classType1.updateExplicitNullability(true)), + type2: classType1.updateExplicitNullability(true), expected: true, }, // Unknown to X @@ -1124,7 +1124,7 @@ const typeParameterTypes = async (): Promise => { expected: true, }, { - type1: coreTypes.Number.updateNullability(true), + type1: coreTypes.Number.updateExplicitNullability(true), type2: lowerBound, expected: true, }, @@ -1186,13 +1186,13 @@ const typeParameterTypes = async (): Promise => { expected: true, }, { - type1: coreTypes.Number.updateNullability(true), + type1: coreTypes.Number.updateExplicitNullability(true), type2: upperBound, expected: false, }, { - type1: coreTypes.Number.updateNullability(true), - type2: upperBound.updateNullability(true), + type1: coreTypes.Number.updateExplicitNullability(true), + type2: upperBound.updateExplicitNullability(true), expected: true, }, { @@ -1253,13 +1253,13 @@ const typeParameterTypes = async (): Promise => { expected: true, }, { - type1: coreTypes.Number.updateNullability(true), + type1: coreTypes.Number.updateExplicitNullability(true), type2: bothBounds, expected: false, }, { - type1: coreTypes.Number.updateNullability(true), - type2: bothBounds.updateNullability(true), + type1: coreTypes.Number.updateExplicitNullability(true), + type2: bothBounds.updateExplicitNullability(true), expected: true, }, { @@ -1268,13 +1268,13 @@ const typeParameterTypes = async (): Promise => { expected: true, }, { - type1: coreTypes.Int.updateNullability(true), + type1: coreTypes.Int.updateExplicitNullability(true), type2: bothBounds, expected: false, }, { - type1: coreTypes.Int.updateNullability(true), - type2: bothBounds.updateNullability(true), + type1: coreTypes.Int.updateExplicitNullability(true), + type2: bothBounds.updateExplicitNullability(true), expected: true, }, { @@ -1363,12 +1363,12 @@ const typeParameterTypes = async (): Promise => { expected: true, }, { - type1: unbounded.updateNullability(true), + type1: unbounded.updateExplicitNullability(true), type2: coreTypes.Any, expected: false, }, { - type1: unbounded.updateNullability(true), + type1: unbounded.updateExplicitNullability(true), type2: coreTypes.AnyOrNull, expected: true, }, @@ -1383,12 +1383,12 @@ const typeParameterTypes = async (): Promise => { expected: true, }, { - type1: lowerBound.updateNullability(true), + type1: lowerBound.updateExplicitNullability(true), type2: coreTypes.Any, expected: false, }, { - type1: lowerBound.updateNullability(true), + type1: lowerBound.updateExplicitNullability(true), type2: coreTypes.AnyOrNull, expected: true, }, @@ -1398,7 +1398,7 @@ const typeParameterTypes = async (): Promise => { expected: true, }, { - type1: upperBound.updateNullability(true), + type1: upperBound.updateExplicitNullability(true), type2: coreTypes.AnyOrNull, expected: true, }, @@ -1408,17 +1408,17 @@ const typeParameterTypes = async (): Promise => { expected: true, }, { - type1: upperBound.updateNullability(true), + type1: upperBound.updateExplicitNullability(true), type2: coreTypes.Any, expected: false, }, { - type1: upperBound.updateNullability(true), + type1: upperBound.updateExplicitNullability(true), type2: coreTypes.AnyOrNull, expected: true, }, { - type1: upperBound.updateNullability(true), + type1: upperBound.updateExplicitNullability(true), type2: coreTypes.Number, expected: false, }, @@ -1428,7 +1428,7 @@ const typeParameterTypes = async (): Promise => { expected: true, }, { - type1: bothBounds.updateNullability(true), + type1: bothBounds.updateExplicitNullability(true), type2: coreTypes.AnyOrNull, expected: true, }, @@ -1438,17 +1438,17 @@ const typeParameterTypes = async (): Promise => { expected: true, }, { - type1: bothBounds.updateNullability(true), + type1: bothBounds.updateExplicitNullability(true), type2: coreTypes.Any, expected: false, }, { - type1: bothBounds.updateNullability(true), + type1: bothBounds.updateExplicitNullability(true), type2: coreTypes.AnyOrNull, expected: true, }, { - type1: bothBounds.updateNullability(true), + type1: bothBounds.updateExplicitNullability(true), type2: coreTypes.Number, expected: false, }, diff --git a/packages/safe-ds-lang/tests/language/typing/type computer/computeClassTypeForLiteralType.test.ts b/packages/safe-ds-lang/tests/language/typing/type computer/computeClassTypeForLiteralType.test.ts index c1f853e41..8e1ea3e44 100644 --- a/packages/safe-ds-lang/tests/language/typing/type computer/computeClassTypeForLiteralType.test.ts +++ b/packages/safe-ds-lang/tests/language/typing/type computer/computeClassTypeForLiteralType.test.ts @@ -44,19 +44,19 @@ const tests: ComputeClassTypeForLiteralTypeTest[] = [ // Nullable types { literalType: new LiteralType(new BooleanConstant(false), NullConstant), - expected: coreTypes.Boolean.updateNullability(true), + expected: coreTypes.Boolean.updateExplicitNullability(true), }, { literalType: new LiteralType(new FloatConstant(1.5), NullConstant), - expected: coreTypes.Float.updateNullability(true), + expected: coreTypes.Float.updateExplicitNullability(true), }, { literalType: new LiteralType(new IntConstant(1n), NullConstant), - expected: coreTypes.Int.updateNullability(true), + expected: coreTypes.Int.updateExplicitNullability(true), }, { literalType: new LiteralType(new StringConstant(''), NullConstant), - expected: coreTypes.String.updateNullability(true), + expected: coreTypes.String.updateExplicitNullability(true), }, // Other combinations { @@ -77,7 +77,7 @@ const tests: ComputeClassTypeForLiteralTypeTest[] = [ }, { literalType: new LiteralType(new FloatConstant(1.5), new IntConstant(1n), NullConstant), - expected: coreTypes.Number.updateNullability(true), + expected: coreTypes.Number.updateExplicitNullability(true), }, { literalType: new LiteralType(new IntConstant(1n), new StringConstant(''), NullConstant), diff --git a/packages/safe-ds-lang/tests/resources/typing/simplification/remove unneeded entries from union types/main.sdstest b/packages/safe-ds-lang/tests/resources/typing/simplification/remove unneeded entries from union types/main.sdstest index 032b16079..3f35841d9 100644 --- a/packages/safe-ds-lang/tests/resources/typing/simplification/remove unneeded entries from union types/main.sdstest +++ b/packages/safe-ds-lang/tests/resources/typing/simplification/remove unneeded entries from union types/main.sdstest @@ -71,3 +71,91 @@ class TestsInvolvingNothing( // $TEST$ serialization union<() -> (), Nothing?> p6: »union<() -> (), Nothing?>«, ) + +class TestsInvolvingTypeParameters( + // $TEST$ serialization Unbounded + a1: »union«, + // $TEST$ serialization Unbounded + a2: »union«, + + // $TEST$ serialization Unbounded? + a3: »union«, + // $TEST$ serialization Unbounded? + a4: »union«, + + // $TEST$ serialization Unbounded + a5: »union«, + // $TEST$ serialization Unbounded + a6: »union«, + + // $TEST$ serialization Unbounded? + a7: »union«, + // $TEST$ serialization Unbounded? + a8: »union«, + + + // $TEST$ serialization UpperBound + b1: »union«, + // $TEST$ serialization UpperBound + b2: »union«, + + // $TEST$ serialization UpperBound? + b3: »union«, + // $TEST$ serialization UpperBound? + b4: »union«, + + // $TEST$ serialization UpperBound + b5: »union«, + // $TEST$ serialization UpperBound + b6: »union«, + + // $TEST$ serialization UpperBound + b7: »union«, + // $TEST$ serialization UpperBound + b8: »union«, + + // $TEST$ serialization UpperBound? + b9: »union«, + // $TEST$ serialization UpperBound? + b10: »union«, + + // $TEST$ serialization Any? + b11: »union«, + // $TEST$ serialization Any? + b12: »union«, + + + // $TEST$ serialization union + c1: »union«, + // $TEST$ serialization union + c2: »union«, + + // $TEST$ serialization union + c3: »union«, + // $TEST$ serialization union + c4: »union«, + + // $TEST$ serialization BothBounds + c5: »union«, + // $TEST$ serialization BothBounds + c6: »union«, + + // $TEST$ serialization BothBounds + c7: »union«, + // $TEST$ serialization BothBounds + c8: »union«, + + // $TEST$ serialization BothBounds + c9: »union«, + // $TEST$ serialization BothBounds + c10: »union«, + + // $TEST$ serialization BothBounds? + c11: »union«, + // $TEST$ serialization BothBounds? + c12: »union«, +) where { + UpperBound sub Number, + BothBounds super Int, + BothBounds sub Any, +}