From 4d6cb4ef6fa7c8f9aedbbc525b82150b2689092b Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Sun, 18 Feb 2024 14:44:54 +0100 Subject: [PATCH] feat: handle invariant/covariant type parameters when computing lowest common supertype (#868) Closes partially #860 ### Summary of Changes Invariant/covariant type parameters are now handled when computing the lowest common supertype of a list of types. This functionality is used to compute the type of * lists (based on their elements) * maps (based on their keys and values) * elvis operator (based on their operands). It's also needed later for #861. Contravariant type parameters need a means to compute the highest common subtype, which will be tackled in a future PR. --- .../language/typing/safe-ds-type-computer.ts | 188 +++++++++++++----- .../class type and class type/main.sdstest | 2 +- .../with type parameters.sdstest | 88 ++++++++ .../main.sdstest | 29 +++ .../main.sdstest | 36 ++++ .../main.sdstest | 31 +++ .../main.sdstest | 24 +++ .../main.sdstest | 54 +++++ 8 files changed, 400 insertions(+), 52 deletions(-) create mode 100644 packages/safe-ds-lang/tests/resources/typing/lowest common supertype/class type and class type/with type parameters.sdstest create mode 100644 packages/safe-ds-lang/tests/resources/typing/lowest common supertype/class type and type parameter type/main.sdstest create mode 100644 packages/safe-ds-lang/tests/resources/typing/lowest common supertype/enum type and type parameter type/main.sdstest create mode 100644 packages/safe-ds-lang/tests/resources/typing/lowest common supertype/enum variant type and type parameter type/main.sdstest create mode 100644 packages/safe-ds-lang/tests/resources/typing/lowest common supertype/literal type and type parameter type/main.sdstest create mode 100644 packages/safe-ds-lang/tests/resources/typing/lowest common supertype/type parameter type and type parameter type/main.sdstest 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 d9dde4de4..d3ab3907d 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 @@ -115,14 +115,12 @@ import { UnionType, UnknownType, } from './model.js'; -import type { SafeDsClassHierarchy } from './safe-ds-class-hierarchy.js'; import { SafeDsCoreTypes } from './safe-ds-core-types.js'; import type { SafeDsTypeChecker } from './safe-ds-type-checker.js'; import { SafeDsClasses } from '../builtins/safe-ds-classes.js'; export class SafeDsTypeComputer { private readonly astNodeLocator: AstNodeLocator; - private readonly classHierarchy: SafeDsClassHierarchy; private readonly coreClasses: SafeDsClasses; private readonly coreTypes: SafeDsCoreTypes; private readonly nodeMapper: SafeDsNodeMapper; @@ -133,7 +131,6 @@ export class SafeDsTypeComputer { constructor(services: SafeDsServices) { this.astNodeLocator = services.workspace.AstNodeLocator; - this.classHierarchy = services.types.ClassHierarchy; this.coreClasses = services.builtins.Classes; this.coreTypes = services.types.CoreTypes; this.nodeMapper = services.helpers.NodeMapper; @@ -337,10 +334,10 @@ export class SafeDsTypeComputer { // Terminal cases if (isSdsList(node)) { - const elementType = this.lowestCommonSupertype(...node.elements.map((it) => this.computeType(it))); + const elementType = this.lowestCommonSupertype(node.elements.map((it) => this.computeType(it))); return this.coreTypes.List(elementType); } else if (isSdsMap(node)) { - let keyType = this.lowestCommonSupertype(...node.entries.map((it) => this.computeType(it.key))); + let keyType = this.lowestCommonSupertype(node.entries.map((it) => this.computeType(it.key))); // Keeping literal types for keys is too strict: We would otherwise infer the key type of `{"a": 1, "b": 2}` // as `Literal<"a", "b">`. But then we would be unable to pass an unknown `String` as the key in an indexed @@ -350,7 +347,7 @@ export class SafeDsTypeComputer { keyType = this.computeClassTypeForLiteralType(keyType); } - const valueType = this.lowestCommonSupertype(...node.entries.map((it) => this.computeType(it.value))); + const valueType = this.lowestCommonSupertype(node.entries.map((it) => this.computeType(it.value))); return this.coreTypes.Map(keyType, valueType); } else if (isSdsTemplateString(node)) { return this.coreTypes.String; @@ -522,7 +519,7 @@ export class SafeDsTypeComputer { const leftOperandType = this.computeType(node.leftOperand); if (leftOperandType.isExplicitlyNullable) { const rightOperandType = this.computeType(node.rightOperand); - return this.lowestCommonSupertype(leftOperandType.updateExplicitNullability(false), rightOperandType); + return this.lowestCommonSupertype([leftOperandType.updateExplicitNullability(false), rightOperandType]); } else { return leftOperandType; } @@ -782,7 +779,7 @@ export class SafeDsTypeComputer { * Returns the lowest class type for the given literal type. */ computeClassTypeForLiteralType(literalType: LiteralType): Type { - return this.lowestCommonSupertype(...literalType.constants.map((it) => this.computeClassTypeForConstant(it))); + return this.lowestCommonSupertype(literalType.constants.map((it) => this.computeClassTypeForConstant(it))); } /** @@ -913,7 +910,7 @@ export class SafeDsTypeComputer { // Lowest common supertype // ----------------------------------------------------------------------------------------------------------------- - private lowestCommonSupertype(...types: Type[]): Type { + private lowestCommonSupertype(types: Type[]): Type { // Simplify types const simplifiedTypes = this.simplifyTypes(types); @@ -922,25 +919,30 @@ export class SafeDsTypeComputer { return simplifiedTypes[0]!; } + // Replace type parameter types by their upper bound + const replacedTypes = simplifiedTypes.map((it) => { + if (it instanceof TypeParameterType) { + return this.computeUpperBound(it); + } else { + return it; + } + }); + // Includes type with unknown supertype - const groupedTypes = this.groupTypes(simplifiedTypes); + const groupedTypes = this.groupTypes(replacedTypes); if (groupedTypes.hasTypeWithUnknownSupertype) { return UnknownType; } - const isNullable = simplifiedTypes.some((it) => it.isExplicitlyNullable); + const isNullable = replacedTypes.some((it) => it.isExplicitlyNullable); // Class-based types - if (!isEmpty(groupedTypes.classTypes) || !isEmpty(groupedTypes.constants)) { + if (!isEmpty(groupedTypes.classTypes)) { if (!isEmpty(groupedTypes.enumTypes) || !isEmpty(groupedTypes.enumVariantTypes)) { - // Class types/literal types are never compatible to enum types/enum variant types + // Class types other than Any/Any? are never compatible to enum types/enum variant types return this.Any(isNullable); } else { - return this.lowestCommonSupertypeForClassBasedTypes( - groupedTypes.classTypes, - groupedTypes.constants, - isNullable, - ); + return this.lowestCommonSupertypeForClassBasedTypes(groupedTypes.classTypes, isNullable); } } @@ -968,21 +970,27 @@ export class SafeDsTypeComputer { private groupTypes(types: Type[]): GroupTypesResult { const result: GroupTypesResult = { classTypes: [], - constants: [], enumTypes: [], enumVariantTypes: [], hasTypeWithUnknownSupertype: false, }; for (const type of types) { - if (type instanceof ClassType) { + if (type.equals(this.coreTypes.Nothing) || type.equals(this.coreTypes.NothingOrNull)) { + // Drop Nothing/Nothing? types. They are compatible to everything with appropriate nullability. + } else if (type instanceof ClassType) { result.classTypes.push(type); } else if (type instanceof EnumType) { result.enumTypes.push(type); } else if (type instanceof EnumVariantType) { result.enumVariantTypes.push(type); } else if (type instanceof LiteralType) { - result.constants.push(...type.constants); + const classType = this.computeClassTypeForLiteralType(type); + if (classType instanceof ClassType) { + result.classTypes.push(classType); + } else { + result.hasTypeWithUnknownSupertype = true; + } } else { // Other types don't have a clear lowest common supertype result.hasTypeWithUnknownSupertype = true; @@ -994,45 +1002,98 @@ export class SafeDsTypeComputer { } /** - * Returns the lowest common supertype for the given class-based types. This function assumes that either the array - * of class types or the array of constants is not empty. + * Returns the lowest common supertype for the given class-based types. */ - private lowestCommonSupertypeForClassBasedTypes( - classTypes: ClassType[], - constants: Constant[], - isNullable: boolean, - ): Type { - // If there are only constants, return a literal type - const literalType = new LiteralType(...constants); + private lowestCommonSupertypeForClassBasedTypes(classTypes: ClassType[], isNullable: boolean): Type { if (isEmpty(classTypes)) { /* c8 ignore next 2 */ - return literalType; + return this.Nothing(isNullable); } // Find the class type that is compatible to all other types - const candidateClasses = stream( - [classTypes[0]!.declaration], - this.classHierarchy.streamProperSuperclasses(classTypes[0]!.declaration), - ); - const other = [...classTypes.slice(1), literalType]; - - for (const candidateClass of candidateClasses) { - // TODO: handle type parameters - const candidateType = new ClassType(candidateClass, NO_SUBSTITUTIONS, isNullable); - // TODO: We need to check first without type parameters - // Then check with type parameters and whether we can find a common supertype for them, respecting variance - // If we can't, try the next candidate - if (this.isCommonSupertype(candidateType, other)) { - return candidateType; + const firstClassType = classTypes[0]!.updateExplicitNullability(isNullable); + const candidates = [firstClassType, ...this.streamProperSupertypes(firstClassType)]; + let other = [...classTypes.slice(1)]; + + for (const candidate of candidates) { + if (this.isCommonSupertypeIgnoringTypeParameters(candidate, other)) { + // If the class has no type parameters, we are done + const typeParameters = getTypeParameters(candidate.declaration); + if (isEmpty(typeParameters)) { + return candidate; + } + + // Check whether all substitutions of invariant type parameters are equal + other = other.map((it) => this.computeMatchingSupertype(it, candidate.declaration)!); + + if (!this.substitutionsForInvariantTypeParametersAreEqual(typeParameters, candidate, other)) { + continue; + } + + // Unify substitutions for type parameters + const substitutions = this.newTypeParameterSubstitutionsForLowestCommonSupertype( + typeParameters, + candidate, + other, + ); + return new ClassType(candidate.declaration, substitutions, isNullable); } } /* c8 ignore next */ return this.Any(isNullable); } + private substitutionsForInvariantTypeParametersAreEqual( + allTypeParameters: SdsTypeParameter[], + candidate: ClassType, + others: ClassType[], + ): boolean { + return allTypeParameters.filter(TypeParameter.isInvariant).every((typeParameter) => { + const candidateSubstitution = candidate.substitutions.get(typeParameter); + return ( + candidateSubstitution && + others.every((other) => { + const otherSubstitution = other.substitutions.get(typeParameter); + return otherSubstitution && candidateSubstitution.equals(otherSubstitution); + }) + ); + }); + } + + private newTypeParameterSubstitutionsForLowestCommonSupertype( + typeParameters: SdsTypeParameter[], + candidate: ClassType, + others: ClassType[], + ): TypeParameterSubstitutions { + const substitutions: TypeParameterSubstitutions = new Map(); + + for (const typeParameter of typeParameters) { + const candidateSubstitution = candidate.substitutions.get(typeParameter) ?? UnknownType; + + if (TypeParameter.isCovariant(typeParameter)) { + // Compute the lowest common supertype for substitutions + const otherSubstitutions = others.map((it) => it.substitutions.get(typeParameter) ?? UnknownType); + substitutions.set( + typeParameter, + this.lowestCommonSupertype([candidateSubstitution, ...otherSubstitutions]), + ); + } /* c8 ignore start */ else if (TypeParameter.isContravariant(typeParameter)) { + // Compute the highest common subtype for substitutions + const otherSubstitutions = others.map((it) => it.substitutions.get(typeParameter) ?? UnknownType); + substitutions.set( + typeParameter, + this.highestCommonSubtype([candidateSubstitution, ...otherSubstitutions]), + ); + } /* c8 ignore stop */ else { + substitutions.set(typeParameter, candidateSubstitution); + } + } + + return substitutions; + } + /** - * Returns the lowest common supertype for the given enum-based types. This function assumes that either the array - * of enum types or the array of enum variant types is not empty. + * Returns the lowest common supertype for the given enum-based types. */ private lowestCommonSupertypeForEnumBasedTypes( enumTypes: EnumType[], @@ -1051,6 +1112,9 @@ export class SafeDsTypeComputer { if (containingEnum) { candidates.push(new EnumType(containingEnum, isNullable)); } + } else { + /* c8 ignore next 2 */ + return this.Nothing(isNullable); } const other = [...enumTypes, ...enumVariantTypes]; @@ -1065,13 +1129,24 @@ export class SafeDsTypeComputer { return this.Any(isNullable); } + private isCommonSupertypeIgnoringTypeParameters(candidate: Type, otherTypes: Type[]): boolean { + return otherTypes.every((it) => this.typeChecker.isSupertypeOf(candidate, it, { ignoreTypeParameters: true })); + } + private isCommonSupertype(candidate: Type, otherTypes: Type[]): boolean { - return otherTypes.every((it) => this.typeChecker.isSubtypeOf(it, candidate)); + return otherTypes.every((it) => this.typeChecker.isSupertypeOf(candidate, it)); } - private Any(isNullable: boolean): Type { - return isNullable ? this.coreTypes.AnyOrNull : this.coreTypes.Any; + // ----------------------------------------------------------------------------------------------------------------- + // Highest common subtype + // ----------------------------------------------------------------------------------------------------------------- + + /* c8 ignore start */ + private highestCommonSubtype(_types: Type[]): Type { + // TODO(lr): Implement + return this.coreTypes.Nothing; } + /* c8 ignore stop */ // ----------------------------------------------------------------------------------------------------------------- // Supertypes @@ -1153,6 +1228,18 @@ export class SafeDsTypeComputer { } return undefined; } + + // ----------------------------------------------------------------------------------------------------------------- + // Helpers + // ----------------------------------------------------------------------------------------------------------------- + + private Any(isNullable: boolean): Type { + return isNullable ? this.coreTypes.AnyOrNull : this.coreTypes.Any; + } + + private Nothing(isNullable: boolean): Type { + return isNullable ? this.coreTypes.NothingOrNull : this.coreTypes.Nothing; + } } /** @@ -1168,7 +1255,6 @@ interface ComputeBoundOptions { interface GroupTypesResult { classTypes: ClassType[]; - constants: Constant[]; enumTypes: EnumType[]; enumVariantTypes: EnumVariantType[]; hasTypeWithUnknownSupertype: boolean; diff --git a/packages/safe-ds-lang/tests/resources/typing/lowest common supertype/class type and class type/main.sdstest b/packages/safe-ds-lang/tests/resources/typing/lowest common supertype/class type and class type/main.sdstest index 664b22b6a..561086848 100644 --- a/packages/safe-ds-lang/tests/resources/typing/lowest common supertype/class type and class type/main.sdstest +++ b/packages/safe-ds-lang/tests/resources/typing/lowest common supertype/class type and class type/main.sdstest @@ -4,7 +4,7 @@ class C class D sub C class E -segment mySegment( +segment mySegment1( c: C, cOrNull: C?, d: D, diff --git a/packages/safe-ds-lang/tests/resources/typing/lowest common supertype/class type and class type/with type parameters.sdstest b/packages/safe-ds-lang/tests/resources/typing/lowest common supertype/class type and class type/with type parameters.sdstest new file mode 100644 index 000000000..cad533037 --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/typing/lowest common supertype/class type and class type/with type parameters.sdstest @@ -0,0 +1,88 @@ +package tests.typing.lowestCommonSupertype.classTypeAndClassType + +class Covariant +class FloatCovariant sub Covariant + +class Invariant +class FloatInvariant sub Invariant + +class Multiple + +segment covariant( + coNumber: Covariant, + coInt: Covariant, + coString: Covariant, + coFloat: FloatCovariant, +) { + // $TEST$ serialization List> + »[coNumber, coNumber]«; + // $TEST$ serialization List> + »[coNumber, coInt]«; + // $TEST$ serialization List> + »[coNumber, coString]«; + // $TEST$ serialization List> + »[coNumber, coFloat]«; + + // $TEST$ serialization List> + »[coInt, coInt]«; + // $TEST$ serialization List> + »[coInt, coString]«; + // $TEST$ serialization List> + »[coInt, coFloat]«; + + // $TEST$ serialization List> + »[coString, coString]«; + // $TEST$ serialization List> + »[coString, coFloat]«; + + // $TEST$ serialization List + »[coFloat, coFloat]«; +} + +segment contravariant() { + // Tests are in the "highest common subtype" folder +} + +segment invariant( + invNumber: Invariant, + invInt: Invariant, + invString: Invariant, + invFloat: FloatInvariant +) { + // $TEST$ serialization List> + »[invNumber, invNumber]«; + // $TEST$ serialization List + »[invNumber, invInt]«; + // $TEST$ serialization List + »[invNumber, invString]«; + // $TEST$ serialization List + »[invNumber, invFloat]«; + + // $TEST$ serialization List> + »[invInt, invInt]«; + // $TEST$ serialization List + »[invInt, invString]«; + // $TEST$ serialization List + »[invInt, invFloat]«; + + // $TEST$ serialization List> + »[invString, invString]«; + // $TEST$ serialization List + »[invString, invFloat]«; + + // $TEST$ serialization List + »[invFloat, invFloat]«; +} + +segment multiple( + multipleNumberInt: Multiple, + multipleNumberFloat: Multiple, +) { + // $TEST$ serialization List> + »[multipleNumberInt, multipleNumberInt]«; + // $TEST$ serialization List> + »[multipleNumberInt, multipleNumberFloat]«; + + // $TEST$ serialization List> + »[multipleNumberFloat, multipleNumberFloat]«; +} diff --git a/packages/safe-ds-lang/tests/resources/typing/lowest common supertype/class type and type parameter type/main.sdstest b/packages/safe-ds-lang/tests/resources/typing/lowest common supertype/class type and type parameter type/main.sdstest new file mode 100644 index 000000000..87853362e --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/typing/lowest common supertype/class type and type parameter type/main.sdstest @@ -0,0 +1,29 @@ +package tests.typing.lowestCommonSupertype.classTypeAndTypeParameterType + +class C +class D + +class Test( + c: C, + + unbounded: Unbounded, + unboundedOrNull: Unbounded?, + boundedByC: BoundedByC, + boundedByD: BoundedByD, + boundedByDOrNull: BoundedByDOrNull, + + // $TEST$ serialization List + p1: Any = »[c, unbounded]«, + // $TEST$ serialization List + p2: Any = »[c, unboundedOrNull]«, + // $TEST$ serialization List + p3: Any = »[c, boundedByC]«, + // $TEST$ serialization List + p4: Any = »[c, boundedByD]«, + // $TEST$ serialization List + p5: Any = »[c, boundedByDOrNull]«, +) where { + BoundedByC sub C, + BoundedByD sub D, + BoundedByDOrNull sub D?, +} diff --git a/packages/safe-ds-lang/tests/resources/typing/lowest common supertype/enum type and type parameter type/main.sdstest b/packages/safe-ds-lang/tests/resources/typing/lowest common supertype/enum type and type parameter type/main.sdstest new file mode 100644 index 000000000..633c35e72 --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/typing/lowest common supertype/enum type and type parameter type/main.sdstest @@ -0,0 +1,36 @@ +package tests.typing.lowestCommonSupertype.enumTypeAndTypeParameterType + +enum E { + V +} + +enum F + +class Test( + e: E, + + unbounded: Unbounded, + unboundedOrNull: Unbounded?, + boundedByEnum: BoundedByEnum, + boundedByEnumVariant: BoundedByEnumVariant, + boundedByOtherEnum: BoundedByOtherEnum, + boundedByOtherEnumOrNull: BoundedByOtherEnumOrNull, + + // $TEST$ serialization List + p1: Any = »[e, unbounded]«, + // $TEST$ serialization List + p2: Any = »[e, unboundedOrNull]«, + // $TEST$ serialization List + p3: Any = »[e, boundedByEnum]«, + // $TEST$ serialization List + p4: Any = »[e, boundedByEnumVariant]«, + // $TEST$ serialization List + p5: Any = »[e, boundedByOtherEnum]«, + // $TEST$ serialization List + p6: Any = »[e, boundedByOtherEnumOrNull]«, +) where { + BoundedByEnum sub E, + BoundedByEnumVariant sub E.V, + BoundedByOtherEnum sub F, + BoundedByOtherEnumOrNull sub F?, +} diff --git a/packages/safe-ds-lang/tests/resources/typing/lowest common supertype/enum variant type and type parameter type/main.sdstest b/packages/safe-ds-lang/tests/resources/typing/lowest common supertype/enum variant type and type parameter type/main.sdstest new file mode 100644 index 000000000..bcac6b659 --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/typing/lowest common supertype/enum variant type and type parameter type/main.sdstest @@ -0,0 +1,31 @@ +package tests.typing.lowestCommonSupertype.enumVariantTypeAndTypeParameterType + +enum E { + V1 + V2 +} + +class D( + v: E.V1, + + unbounded: Unbounded, + unboundedOrNull: Unbounded?, + boundedByEnumVariant: BoundedByEnumVariant, + boundedByOtherEnumVariant: BoundedByOtherEnumVariant, + boundedByOtherEnumVariantOrNull: BoundedByOtherEnumVariantOrNull, + + // $TEST$ serialization List + p1: Any = »[v, unbounded]«, + // $TEST$ serialization List + p2: Any = »[v, unboundedOrNull]«, + // $TEST$ serialization List + p3: Any = »[v, boundedByEnumVariant]«, + // $TEST$ serialization List + p4: Any = »[v, boundedByOtherEnumVariant]«, + // $TEST$ serialization List + p5: Any = »[v, boundedByOtherEnumVariantOrNull]«, +) where { + BoundedByEnumVariant sub E.V1, + BoundedByOtherEnumVariant sub E.V2, + BoundedByOtherEnumVariantOrNull sub E.V2? +} diff --git a/packages/safe-ds-lang/tests/resources/typing/lowest common supertype/literal type and type parameter type/main.sdstest b/packages/safe-ds-lang/tests/resources/typing/lowest common supertype/literal type and type parameter type/main.sdstest new file mode 100644 index 000000000..fd6ccaeaf --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/typing/lowest common supertype/literal type and type parameter type/main.sdstest @@ -0,0 +1,24 @@ +package tests.typing.lowestCommonSupertype.literalTypeAndTypeParameterType + +class Test( + unbounded: Unbounded, + unboundedOrNull: Unbounded?, + boundedByInt: BoundedByInt, + boundedByString: BoundedByString, + boundedByStringOrNull: BoundedByStringOrNull, + + // $TEST$ serialization List + p1: Any = »[1, unbounded]«, + // $TEST$ serialization List + p2: Any = »[1, unboundedOrNull]«, + // $TEST$ serialization List + p3: Any = »[1, boundedByInt]«, + // $TEST$ serialization List + p4: Any = »[1, boundedByString]«, + // $TEST$ serialization List + p5: Any = »[1, boundedByStringOrNull]«, +) where { + BoundedByInt sub Int, + BoundedByString sub String, + BoundedByStringOrNull sub String?, +} diff --git a/packages/safe-ds-lang/tests/resources/typing/lowest common supertype/type parameter type and type parameter type/main.sdstest b/packages/safe-ds-lang/tests/resources/typing/lowest common supertype/type parameter type and type parameter type/main.sdstest new file mode 100644 index 000000000..11fca4516 --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/typing/lowest common supertype/type parameter type and type parameter type/main.sdstest @@ -0,0 +1,54 @@ +package tests.typing.lowestCommonSupertype.typeParameterTypeAndTypeParameterType + +class C +class D sub C +class E sub C + +class Test( + nothingToAnyOrNull: NothingToAnyOrNull, + nothingToD: NothingToD, + nothingToE: NothingToE, + dToAnyOrNull: DToAnyOrNull, + dToC: DToC, + + // $TEST$ serialization List + a1: Any = »[nothingToAnyOrNull, nothingToAnyOrNull]«, + // $TEST$ serialization List + a2: Any = »[nothingToAnyOrNull, nothingToD]«, + // $TEST$ serialization List + a3: Any = »[nothingToAnyOrNull, nothingToE]«, + // $TEST$ serialization List + a4: Any = »[nothingToAnyOrNull, dToAnyOrNull]«, + // $TEST$ serialization List + a5: Any = »[nothingToAnyOrNull, dToC]«, + + // $TEST$ serialization List + b1: Any = »[nothingToD, nothingToD]«, + // $TEST$ serialization List + b2: Any = »[nothingToD, nothingToE]«, + // $TEST$ serialization List + b3: Any = »[nothingToD, dToAnyOrNull]«, + // $TEST$ serialization List + b4: Any = »[nothingToD, dToC]«, + + // $TEST$ serialization List + c1: Any = »[nothingToE, nothingToE]«, + // $TEST$ serialization List + c2: Any = »[nothingToE, dToAnyOrNull]«, + // $TEST$ serialization List + c3: Any = »[nothingToE, dToC]«, + + // $TEST$ serialization List + d1: Any = »[dToAnyOrNull, dToAnyOrNull]«, + // $TEST$ serialization List + d2: Any = »[dToAnyOrNull, dToC]«, + + // $TEST$ serialization List + e1: Any = »[dToC, dToC]«, +) where { + NothingToD sub D, + NothingToE sub E, + DToAnyOrNull super D, + DToC super D, + DToC sub C, +}