diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 308b95c218611..0656b9c22d4de 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1,5 +1,9 @@ /* @internal */ namespace ts { + // This value sets the maximum number of combinations of discriminants and types allowed + // in typeRelatedToDiscriminatedType. + const MAX_DISCRIMINANT_COMBINATIONS = 25; + const ambientModuleSymbolRegex = /^".+"$/; let nextSymbolId = 1; @@ -12409,7 +12413,7 @@ namespace ts { if (result && isPerformingExcessPropertyChecks) { // Validate against excess props using the original `source` const discriminantType = target.flags & TypeFlags.Union ? findMatchingDiscriminantType(source, target as UnionType) : undefined; - if (!propertiesRelatedTo(source, discriminantType || target, reportErrors)) { + if (!propertiesRelatedTo(source, discriminantType || target, reportErrors, /*excludedProperties*/ undefined)) { return Ternary.False; } } @@ -12419,7 +12423,7 @@ namespace ts { result = typeRelatedToEachType(getRegularTypeOfObjectLiteral(source), target as IntersectionType, reportErrors); if (result && isPerformingExcessPropertyChecks) { // Validate against excess props using the original `source` - if (!propertiesRelatedTo(source, target, reportErrors)) { + if (!propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined)) { return Ternary.False; } } @@ -13097,7 +13101,7 @@ namespace ts { if (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Object) { // Report structural errors only if we haven't reported any errors yet const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo && !sourceIsPrimitive; - result = propertiesRelatedTo(source, target, reportStructuralErrors); + result = propertiesRelatedTo(source, target, reportStructuralErrors, /*excludedProperties*/ undefined); if (result) { result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportStructuralErrors); if (result) { @@ -13117,6 +13121,19 @@ namespace ts { return result; } } + // If S is an object type and T is a discriminated union, S may be related to T if + // there exists a constituent of T for every combination of the discriminants of S + // with respect to T. We do not report errors here, as we will use the existing + // error result from checking each constituent of the union. + if (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Union) { + const objectOnlyTarget = extractTypesOfKind(target, TypeFlags.Object); + if (objectOnlyTarget.flags & TypeFlags.Union) { + const result = typeRelatedToDiscriminatedType(source, objectOnlyTarget as UnionType); + if (result) { + return result; + } + } + } } return Ternary.False; @@ -13180,9 +13197,255 @@ namespace ts { return Ternary.False; } - function propertiesRelatedTo(source: Type, target: Type, reportErrors: boolean): Ternary { + function typeRelatedToDiscriminatedType(source: Type, target: UnionType) { + // 1. Generate the combinations of discriminant properties & types 'source' can satisfy. + // a. If the number of combinations is above a set limit, the comparison is too complex. + // 2. Filter 'target' to the subset of types whose discriminants exist in the matrix. + // a. If 'target' does not satisfy all discriminants in the matrix, 'source' is not related. + // 3. For each type in the filtered 'target', determine if all non-discriminant properties of + // 'target' are related to a property in 'source'. + // + // Example 1: IteratorResult + // ```ts + // type S = { done: boolean, value: number } + // type T = { done: true, value: number } // T0 + // | { done: false, value: number } // T1 + // ``` + // 1) Discriminants of S wrt T (D): ["done"] + // Discriminant types of D (Dt): [[true, false]] + // # combinations of Dt (n): 2 + // 1.a) n <= MAX_DISCRIMINANT_COMBINATIONS: true + // Combinations of Dt (C): [[true], [false]] + // 2) Types of T (Tt): [T0, T1] + // C matches in Tt (Cm): [T0, T1] + // 2.a) Cm is fully matched: true + // 3) T0: S["value"] is related to T0["value"] + // T1: S["value"] is related to T1["value"] + // Result: Related + // + // Example 2: Dropping constituents of T + // ```ts + // type S = { a: 0 | 2, b: 4 } + // type T = { a: 0, b: 1 | 4 } // T0 + // | { a: 1, b: 2 } // T1 + // | { a: 2, b: 3 | 4 } // T2 + // ``` + // 1) Discriminants of S wrt T (D): ["a", "b"] + // Discriminant types of D (Dt): [[0, 2], [4]] + // # combinations of Dt (n): 2 + // 1.a) n <= MAX_DISCRIMINANT_COMBINATIONS: true + // Combinations of Dt (C): [[0, 4], [2, 4]] + // 2) Types of T (Tt): [T0, T1, T2] + // C matches in Tt (Cm): [T0, T2] + // 2.a) Cm is fully matched: true + // 3) T0: No non-discriminants + // T2: No non-discriminants + // Result: Related + // + // Example 3: Unmatched discriminants + // ```ts + // type S = { a: 0 | 2, b: 4 } + // type T = { a: 0, b: 1 | 4 } // T0 + // | { a: 1, b: 2 | 4 } // T1 + // | { a: 2, b: 3 } // T2 + // ``` + // 1) Discriminants of S wrt T (D): ["a", "b"] + // Discriminant types of D (Dt): [[0, 2], [4]] + // # combinations of Dt (n): 2 + // 1.a) n <= MAX_DISCRIMINANT_COMBINATIONS: true + // Combinations of Dt (C): [[0, 4], [2, 4]] + // 2) Types of T (Tt): [T0, T1, T2] + // C matches in Tt (Cm): [T0, undefined] + // 2.a) Cm is fully matched: false + // Result: Not related + // + // Example 3: Unmatched non-discriminants + // ```ts + // type S = { a: 0 | 2, b: 4 } + // type T = { a: 0, b: 1 | 4 } // T0 + // | { a: 1, b: 2 } // T1 + // | { a: 2, b: 3 | 4, c: string } // T2 + // ``` + // 1) Discriminants of S wrt T (D): ["a", "b"] + // Discriminant types of D (Dt): [[0, 2], [4]] + // # combinations of Dt (n): 2 + // 1.a) n <= MAX_DISCRIMINANT_COMBINATIONS: true + // Combinations of Dt (C): [[0, 4], [2, 4]] + // 2) Types of T (Tt): [T0, T1, T2] + // C matches in Tt (Cm): [T0, T2] + // 2.a) Cm is fully matched: true + // 3) T0: No non-discriminants + // T2: "c" is not present in S + // Result: Not related + + const sourceProperties = getPropertiesOfObjectType(source); + const sourcePropertiesFiltered = findDiscriminantProperties(sourceProperties, target); + if (!sourcePropertiesFiltered) return Ternary.False; + + // Though we could compute the number of combinations as we generate + // the matrix, this would incur additional memory overhead due to + // array allocations. To reduce this overhead, we first compute + // the number of combinations to ensure we will not surpass our + // fixed limit before incurring the cost of any allocations: + let numCombinations = 1; + for (const sourceProperty of sourcePropertiesFiltered) { + numCombinations *= countTypes(getTypeOfSymbol(sourceProperty)); + if (numCombinations > MAX_DISCRIMINANT_COMBINATIONS) { + // We've reached the complexity limit. + return Ternary.False; + } + } + + // Compute the set of types for each discriminant property. + const sourceDiscriminantTypes: Type[][] = new Array(sourcePropertiesFiltered.length); + const excludedProperties = createUnderscoreEscapedMap(); + for (let i = 0; i < sourcePropertiesFiltered.length; i++) { + const sourceProperty = sourcePropertiesFiltered[i]; + const sourcePropertyType = getTypeOfSymbol(sourceProperty); + sourceDiscriminantTypes[i] = sourcePropertyType.flags & TypeFlags.Union + ? (sourcePropertyType as UnionType).types + : [sourcePropertyType]; + excludedProperties.set(sourceProperty.escapedName, true); + } + + // Match each combination of the cartesian product of discriminant properties to one or more + // constituents of 'target'. If any combination does not have a match then 'source' is not relatable. + const discriminantCombinations = cartesianProduct(sourceDiscriminantTypes); + const matchingTypes: Type[] = []; + for (const combination of discriminantCombinations) { + let hasMatch = false; + outer: for (const type of target.types) { + for (let i = 0; i < sourcePropertiesFiltered.length; i++) { + const sourceProperty = sourcePropertiesFiltered[i]; + const targetProperty = getPropertyOfObjectType(type, sourceProperty.escapedName); + if (!targetProperty) continue outer; + if (sourceProperty === targetProperty) continue; + // We compare the source property to the target in the context of a single discriminant type. + const related = propertyRelatedTo(source, target, sourceProperty, targetProperty, _ => combination[i], /*reportErrors*/ false); + // If the target property could not be found, or if the properties were not related, + // then this constituent is not a match. + if (!related) { + continue outer; + } + } + pushIfUnique(matchingTypes, type, equateValues); + hasMatch = true; + } + if (!hasMatch) { + // We failed to match any type for this combination. + return Ternary.False; + } + } + + // Compare the remaining non-discriminant properties of each match. + let result = Ternary.True; + for (const type of matchingTypes) { + result &= propertiesRelatedTo(source, type, /*reportErrors*/ false, excludedProperties); + if (result) { + result &= signaturesRelatedTo(source, type, SignatureKind.Call, /*reportStructuralErrors*/ false); + if (result) { + result &= signaturesRelatedTo(source, type, SignatureKind.Construct, /*reportStructuralErrors*/ false); + if (result) { + result &= indexTypesRelatedTo(source, type, IndexKind.String, /*sourceIsPrimitive*/ false, /*reportStructuralErrors*/ false); + if (result) { + result &= indexTypesRelatedTo(source, type, IndexKind.Number, /*sourceIsPrimitive*/ false, /*reportStructuralErrors*/ false); + } + } + } + } + if (!result) { + return result; + } + } + return result; + } + + function excludeProperties(properties: Symbol[], excludedProperties: UnderscoreEscapedMap | undefined) { + if (!excludedProperties || properties.length === 0) return properties; + let result: Symbol[] | undefined; + for (let i = 0; i < properties.length; i++) { + if (!excludedProperties.has(properties[i].escapedName)) { + if (result) { + result.push(properties[i]); + } + } + else if (!result) { + result = properties.slice(0, i); + } + } + return result || properties; + } + + function propertyRelatedTo(source: Type, target: Type, sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean): Ternary { + const sourcePropFlags = getDeclarationModifierFlagsFromSymbol(sourceProp); + const targetPropFlags = getDeclarationModifierFlagsFromSymbol(targetProp); + if (sourcePropFlags & ModifierFlags.Private || targetPropFlags & ModifierFlags.Private) { + const hasDifferingDeclarations = sourceProp.valueDeclaration !== targetProp.valueDeclaration; + if (getCheckFlags(sourceProp) & CheckFlags.ContainsPrivate && hasDifferingDeclarations) { + if (reportErrors) { + reportError(Diagnostics.Property_0_has_conflicting_declarations_and_is_inaccessible_in_type_1, symbolToString(sourceProp), typeToString(source)); + } + return Ternary.False; + } + if (hasDifferingDeclarations) { + if (reportErrors) { + if (sourcePropFlags & ModifierFlags.Private && targetPropFlags & ModifierFlags.Private) { + reportError(Diagnostics.Types_have_separate_declarations_of_a_private_property_0, symbolToString(targetProp)); + } + else { + reportError(Diagnostics.Property_0_is_private_in_type_1_but_not_in_type_2, symbolToString(targetProp), + typeToString(sourcePropFlags & ModifierFlags.Private ? source : target), + typeToString(sourcePropFlags & ModifierFlags.Private ? target : source)); + } + } + return Ternary.False; + } + } + else if (targetPropFlags & ModifierFlags.Protected) { + if (!isValidOverrideOf(sourceProp, targetProp)) { + if (reportErrors) { + reportError(Diagnostics.Property_0_is_protected_but_type_1_is_not_a_class_derived_from_2, symbolToString(targetProp), + typeToString(getDeclaringClass(sourceProp) || source), typeToString(getDeclaringClass(targetProp) || target)); + } + return Ternary.False; + } + } + else if (sourcePropFlags & ModifierFlags.Protected) { + if (reportErrors) { + reportError(Diagnostics.Property_0_is_protected_in_type_1_but_public_in_type_2, + symbolToString(targetProp), typeToString(source), typeToString(target)); + } + return Ternary.False; + } + // If the target comes from a partial union prop, allow `undefined` in the target type + const related = isRelatedTo(getTypeOfSourceProperty(sourceProp), addOptionality(getTypeOfSymbol(targetProp), !!(getCheckFlags(targetProp) & CheckFlags.Partial)), reportErrors); + if (!related) { + if (reportErrors) { + reportError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(targetProp)); + } + return Ternary.False; + } + // When checking for comparability, be more lenient with optional properties. + if (relation !== comparableRelation && sourceProp.flags & SymbolFlags.Optional && !(targetProp.flags & SymbolFlags.Optional)) { + // TypeScript 1.0 spec (April 2014): 3.8.3 + // S is a subtype of a type T, and T is a supertype of S if ... + // S' and T are object types and, for each member M in T.. + // M is a property and S' contains a property N where + // if M is a required property, N is also a required property + // (M - property in T) + // (N - property in S) + if (reportErrors) { + reportError(Diagnostics.Property_0_is_optional_in_type_1_but_required_in_type_2, + symbolToString(targetProp), typeToString(source), typeToString(target)); + } + return Ternary.False; + } + return related; + } + + function propertiesRelatedTo(source: Type, target: Type, reportErrors: boolean, excludedProperties: UnderscoreEscapedMap | undefined): Ternary { if (relation === identityRelation) { - return propertiesIdenticalTo(source, target); + return propertiesIdenticalTo(source, target, excludedProperties); } const requireOptionalProperties = relation === subtypeRelation && !isObjectLiteralType(source) && !isEmptyArrayLiteralType(source) && !isTupleType(source); const unmatchedProperty = getUnmatchedProperty(source, target, requireOptionalProperties, /*matchDiscriminantProperties*/ false); @@ -13212,7 +13475,7 @@ namespace ts { return Ternary.False; } if (isObjectLiteralType(target)) { - for (const sourceProp of getPropertiesOfType(source)) { + for (const sourceProp of excludeProperties(getPropertiesOfType(source), excludedProperties)) { if (!getPropertyOfObjectType(target, sourceProp.escapedName)) { const sourceType = getTypeOfSymbol(sourceProp); if (!(sourceType === undefinedType || sourceType === undefinedWideningType)) { @@ -13255,89 +13518,30 @@ namespace ts { // We only call this for union target types when we're attempting to do excess property checking - in those cases, we want to get _all possible props_ // from the target union, across all members const properties = target.flags & TypeFlags.Union ? getPossiblePropertiesOfUnionType(target as UnionType) : getPropertiesOfType(target); - for (const targetProp of properties) { + for (const targetProp of excludeProperties(properties, excludedProperties)) { if (!(targetProp.flags & SymbolFlags.Prototype)) { const sourceProp = getPropertyOfType(source, targetProp.escapedName); if (sourceProp && sourceProp !== targetProp) { if (isIgnoredJsxProperty(source, sourceProp, getTypeOfSymbol(targetProp))) { continue; } - const sourcePropFlags = getDeclarationModifierFlagsFromSymbol(sourceProp); - const targetPropFlags = getDeclarationModifierFlagsFromSymbol(targetProp); - if (sourcePropFlags & ModifierFlags.Private || targetPropFlags & ModifierFlags.Private) { - const hasDifferingDeclarations = sourceProp.valueDeclaration !== targetProp.valueDeclaration; - if (getCheckFlags(sourceProp) & CheckFlags.ContainsPrivate && hasDifferingDeclarations) { - if (reportErrors) { - reportError(Diagnostics.Property_0_has_conflicting_declarations_and_is_inaccessible_in_type_1, symbolToString(sourceProp), typeToString(source)); - } - return Ternary.False; - } - if (hasDifferingDeclarations) { - if (reportErrors) { - if (sourcePropFlags & ModifierFlags.Private && targetPropFlags & ModifierFlags.Private) { - reportError(Diagnostics.Types_have_separate_declarations_of_a_private_property_0, symbolToString(targetProp)); - } - else { - reportError(Diagnostics.Property_0_is_private_in_type_1_but_not_in_type_2, symbolToString(targetProp), - typeToString(sourcePropFlags & ModifierFlags.Private ? source : target), - typeToString(sourcePropFlags & ModifierFlags.Private ? target : source)); - } - } - return Ternary.False; - } - } - else if (targetPropFlags & ModifierFlags.Protected) { - if (!isValidOverrideOf(sourceProp, targetProp)) { - if (reportErrors) { - reportError(Diagnostics.Property_0_is_protected_but_type_1_is_not_a_class_derived_from_2, symbolToString(targetProp), - typeToString(getDeclaringClass(sourceProp) || source), typeToString(getDeclaringClass(targetProp) || target)); - } - return Ternary.False; - } - } - else if (sourcePropFlags & ModifierFlags.Protected) { - if (reportErrors) { - reportError(Diagnostics.Property_0_is_protected_in_type_1_but_public_in_type_2, - symbolToString(targetProp), typeToString(source), typeToString(target)); - } - return Ternary.False; - } - // If the target comes from a partial union prop, allow `undefined` in the target type - const related = isRelatedTo(getTypeOfSymbol(sourceProp), addOptionality(getTypeOfSymbol(targetProp), !!(getCheckFlags(targetProp) & CheckFlags.Partial)), reportErrors); + const related = propertyRelatedTo(source, target, sourceProp, targetProp, getTypeOfSymbol, reportErrors); if (!related) { - if (reportErrors) { - reportError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(targetProp)); - } return Ternary.False; } result &= related; - // When checking for comparability, be more lenient with optional properties. - if (relation !== comparableRelation && sourceProp.flags & SymbolFlags.Optional && !(targetProp.flags & SymbolFlags.Optional)) { - // TypeScript 1.0 spec (April 2014): 3.8.3 - // S is a subtype of a type T, and T is a supertype of S if ... - // S' and T are object types and, for each member M in T.. - // M is a property and S' contains a property N where - // if M is a required property, N is also a required property - // (M - property in T) - // (N - property in S) - if (reportErrors) { - reportError(Diagnostics.Property_0_is_optional_in_type_1_but_required_in_type_2, - symbolToString(targetProp), typeToString(source), typeToString(target)); - } - return Ternary.False; - } } } } return result; } - function propertiesIdenticalTo(source: Type, target: Type): Ternary { + function propertiesIdenticalTo(source: Type, target: Type, excludedProperties: UnderscoreEscapedMap | undefined): Ternary { if (!(source.flags & TypeFlags.Object && target.flags & TypeFlags.Object)) { return Ternary.False; } - const sourceProperties = getPropertiesOfObjectType(source); - const targetProperties = getPropertiesOfObjectType(target); + const sourceProperties = excludeProperties(getPropertiesOfObjectType(source), excludedProperties); + const targetProperties = excludeProperties(getPropertiesOfObjectType(target), excludedProperties); if (sourceProperties.length !== targetProperties.length) { return Ternary.False; } @@ -15844,6 +16048,10 @@ namespace ts { return f(type) ? type : neverType; } + function countTypes(type: Type) { + return type.flags & TypeFlags.Union ? (type as UnionType).types.length : 1; + } + // Apply a mapping function to a type and return the resulting type. If the source type // is a union type, the mapping function is applied to each constituent type and a union // of the resulting types is returned. diff --git a/src/compiler/core.ts b/src/compiler/core.ts index b99304ddfedca..7ba8b0fab5773 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2317,4 +2317,29 @@ namespace ts { } return result; } + + export function cartesianProduct(arrays: readonly T[][]) { + const result: T[][] = []; + cartesianProductWorker(arrays, result, /*outer*/ undefined, 0); + return result; + } + + function cartesianProductWorker(arrays: readonly (readonly T[])[], result: (readonly T[])[], outer: readonly T[] | undefined, index: number) { + for (const element of arrays[index]) { + let inner: T[]; + if (outer) { + inner = outer.slice(); + inner.push(element); + } + else { + inner = [element]; + } + if (index === arrays.length - 1) { + result.push(inner); + } + else { + cartesianProductWorker(arrays, result, inner, index + 1); + } + } + } } diff --git a/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.errors.txt b/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.errors.txt new file mode 100644 index 0000000000000..e59fb4382f057 --- /dev/null +++ b/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.errors.txt @@ -0,0 +1,136 @@ +tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts(44,5): error TS2322: Type 'S' is not assignable to type 'T'. + Type 'S' is not assignable to type '{ a: 2; b: 3; }'. + Types of property 'a' are incompatible. + Type '0 | 2' is not assignable to type '2'. + Type '0' is not assignable to type '2'. +tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts(58,5): error TS2322: Type 'S' is not assignable to type 'T'. + Property 'c' is missing in type 'S' but required in type '{ a: 2; b: 4 | 3; c: string; }'. +tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts(82,5): error TS2322: Type 'S' is not assignable to type 'T'. + Type 'S' is not assignable to type '{ a: 0 | 2 | 1; b: 0 | 2 | 1; c: 2; }'. + Types of property 'c' are incompatible. + Type '0 | 2 | 1' is not assignable to type '2'. + Type '0' is not assignable to type '2'. + + +==== tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts (3 errors) ==== + // see 'typeRelatedToDiscriminatedType' in checker.ts: + + // IteratorResult + namespace Example1 { + type S = { done: boolean, value: number }; + type T = + | { done: true, value: number } // T0 + | { done: false, value: number }; // T1 + + declare let s: S; + declare let t: T; + + // S is assignable to T0 when S["done"] is true + // S is assignable to T1 when S["done"] is false + t = s; + } + + // Dropping constituents of T + namespace Example2 { + type S = { a: 0 | 2, b: 4 }; + type T = { a: 0, b: 1 | 4 } // T0 + | { a: 1, b: 2 } // T1 + | { a: 2, b: 3 | 4 }; // T2 + declare let s: S; + declare let t: T; + + // S is assignable to T0 when S["a"] is 0 + // S is assignable to T2 when S["a"] is 2 + t = s; + } + + // Unmatched discriminants + namespace Example3 { + type S = { a: 0 | 2, b: 4 }; + type T = { a: 0, b: 1 | 4 } // T0 + | { a: 1, b: 2 | 4 } // T1 + | { a: 2, b: 3 }; // T2 + declare let s: S; + declare let t: T; + + // S is assignable to T0 when S["a"] is 0 + // S is *not* assignable to T1 when S["b"] is 4 + // S is *not* assignable to T2 when S["a"] is 2 + t = s; + ~ +!!! error TS2322: Type 'S' is not assignable to type 'T'. +!!! error TS2322: Type 'S' is not assignable to type '{ a: 2; b: 3; }'. +!!! error TS2322: Types of property 'a' are incompatible. +!!! error TS2322: Type '0 | 2' is not assignable to type '2'. +!!! error TS2322: Type '0' is not assignable to type '2'. + } + + // Unmatched non-discriminants + namespace Example4 { + type S = { a: 0 | 2, b: 4 }; + type T = { a: 0, b: 1 | 4 } // T0 + | { a: 1, b: 2 } // T1 + | { a: 2, b: 3 | 4, c: string }; // T2 + declare let s: S; + declare let t: T; + + // S is assignable to T0 when S["a"] is 0 + // S is *not* assignable to T2 when S["a"] is 2 as S is missing "c" + t = s; + ~ +!!! error TS2322: Type 'S' is not assignable to type 'T'. +!!! error TS2322: Property 'c' is missing in type 'S' but required in type '{ a: 2; b: 4 | 3; c: string; }'. +!!! related TS2728 tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts:52:36: 'c' is declared here. + } + + // Maximum discriminant combinations + namespace Example5 { + // NOTE: MAX_DISCRIMINANT_COMBINATIONS is currently 25 + // 3 discriminant properties with 3 types a piece + // is 27 possible combinations. + type N = 0 | 1 | 2; + type S = { a: N, b: N, c: N }; + type T = { a: 0, b: N, c: N } + | { a: 1, b: N, c: N } + | { a: 2, b: N, c: N } + | { a: N, b: 0, c: N } + | { a: N, b: 1, c: N } + | { a: N, b: 2, c: N } + | { a: N, b: N, c: 0 } + | { a: N, b: N, c: 1 } + | { a: N, b: N, c: 2 }; + declare let s: S; + declare let t: T; + + // S *should* be assignable but the number of + // combinations is too complex. + t = s; + ~ +!!! error TS2322: Type 'S' is not assignable to type 'T'. +!!! error TS2322: Type 'S' is not assignable to type '{ a: 0 | 2 | 1; b: 0 | 2 | 1; c: 2; }'. +!!! error TS2322: Types of property 'c' are incompatible. +!!! error TS2322: Type '0 | 2 | 1' is not assignable to type '2'. +!!! error TS2322: Type '0' is not assignable to type '2'. + } + + // https://github.com/Microsoft/TypeScript/issues/14865 + namespace Example6 { + type Style1 = { + type: "A"; + data: string; + } | { + type: "B"; + data: string; + }; + + type Style2 = { + type: "A" | "B"; + data: string; + } + + const a: Style2 = { type: "A", data: "whatevs" }; + let b: Style1; + a.type; // "A" | "B" + b.type; // "A" | "B" + b = a; // should be assignable + } \ No newline at end of file diff --git a/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.js b/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.js new file mode 100644 index 0000000000000..1686502301fa9 --- /dev/null +++ b/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.js @@ -0,0 +1,154 @@ +//// [assignmentCompatWithDiscriminatedUnion.ts] +// see 'typeRelatedToDiscriminatedType' in checker.ts: + +// IteratorResult +namespace Example1 { + type S = { done: boolean, value: number }; + type T = + | { done: true, value: number } // T0 + | { done: false, value: number }; // T1 + + declare let s: S; + declare let t: T; + + // S is assignable to T0 when S["done"] is true + // S is assignable to T1 when S["done"] is false + t = s; +} + +// Dropping constituents of T +namespace Example2 { + type S = { a: 0 | 2, b: 4 }; + type T = { a: 0, b: 1 | 4 } // T0 + | { a: 1, b: 2 } // T1 + | { a: 2, b: 3 | 4 }; // T2 + declare let s: S; + declare let t: T; + + // S is assignable to T0 when S["a"] is 0 + // S is assignable to T2 when S["a"] is 2 + t = s; +} + +// Unmatched discriminants +namespace Example3 { + type S = { a: 0 | 2, b: 4 }; + type T = { a: 0, b: 1 | 4 } // T0 + | { a: 1, b: 2 | 4 } // T1 + | { a: 2, b: 3 }; // T2 + declare let s: S; + declare let t: T; + + // S is assignable to T0 when S["a"] is 0 + // S is *not* assignable to T1 when S["b"] is 4 + // S is *not* assignable to T2 when S["a"] is 2 + t = s; +} + +// Unmatched non-discriminants +namespace Example4 { + type S = { a: 0 | 2, b: 4 }; + type T = { a: 0, b: 1 | 4 } // T0 + | { a: 1, b: 2 } // T1 + | { a: 2, b: 3 | 4, c: string }; // T2 + declare let s: S; + declare let t: T; + + // S is assignable to T0 when S["a"] is 0 + // S is *not* assignable to T2 when S["a"] is 2 as S is missing "c" + t = s; +} + +// Maximum discriminant combinations +namespace Example5 { + // NOTE: MAX_DISCRIMINANT_COMBINATIONS is currently 25 + // 3 discriminant properties with 3 types a piece + // is 27 possible combinations. + type N = 0 | 1 | 2; + type S = { a: N, b: N, c: N }; + type T = { a: 0, b: N, c: N } + | { a: 1, b: N, c: N } + | { a: 2, b: N, c: N } + | { a: N, b: 0, c: N } + | { a: N, b: 1, c: N } + | { a: N, b: 2, c: N } + | { a: N, b: N, c: 0 } + | { a: N, b: N, c: 1 } + | { a: N, b: N, c: 2 }; + declare let s: S; + declare let t: T; + + // S *should* be assignable but the number of + // combinations is too complex. + t = s; +} + +// https://github.com/Microsoft/TypeScript/issues/14865 +namespace Example6 { + type Style1 = { + type: "A"; + data: string; + } | { + type: "B"; + data: string; + }; + + type Style2 = { + type: "A" | "B"; + data: string; + } + + const a: Style2 = { type: "A", data: "whatevs" }; + let b: Style1; + a.type; // "A" | "B" + b.type; // "A" | "B" + b = a; // should be assignable +} + +//// [assignmentCompatWithDiscriminatedUnion.js] +// see 'typeRelatedToDiscriminatedType' in checker.ts: +// IteratorResult +var Example1; +(function (Example1) { + // S is assignable to T0 when S["done"] is true + // S is assignable to T1 when S["done"] is false + t = s; +})(Example1 || (Example1 = {})); +// Dropping constituents of T +var Example2; +(function (Example2) { + // S is assignable to T0 when S["a"] is 0 + // S is assignable to T2 when S["a"] is 2 + t = s; +})(Example2 || (Example2 = {})); +// Unmatched discriminants +var Example3; +(function (Example3) { + // S is assignable to T0 when S["a"] is 0 + // S is *not* assignable to T1 when S["b"] is 4 + // S is *not* assignable to T2 when S["a"] is 2 + t = s; +})(Example3 || (Example3 = {})); +// Unmatched non-discriminants +var Example4; +(function (Example4) { + // S is assignable to T0 when S["a"] is 0 + // S is *not* assignable to T2 when S["a"] is 2 as S is missing "c" + t = s; +})(Example4 || (Example4 = {})); +// Maximum discriminant combinations +var Example5; +(function (Example5) { + // S *should* be assignable but the number of + // combinations is too complex. + t = s; +})(Example5 || (Example5 = {})); +// https://github.com/Microsoft/TypeScript/issues/14865 +var Example6; +(function (Example6) { + var a = { type: "A", data: "whatevs" }; + var b; + a.type; // "A" | "B" + b.type; // "A" | "B" + b = a; // should be assignable +})(Example6 || (Example6 = {})); diff --git a/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.symbols b/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.symbols new file mode 100644 index 0000000000000..29e2016de900f --- /dev/null +++ b/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.symbols @@ -0,0 +1,305 @@ +=== tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts === +// see 'typeRelatedToDiscriminatedType' in checker.ts: + +// IteratorResult +namespace Example1 { +>Example1 : Symbol(Example1, Decl(assignmentCompatWithDiscriminatedUnion.ts, 0, 0)) + + type S = { done: boolean, value: number }; +>S : Symbol(S, Decl(assignmentCompatWithDiscriminatedUnion.ts, 3, 20)) +>done : Symbol(done, Decl(assignmentCompatWithDiscriminatedUnion.ts, 4, 14)) +>value : Symbol(value, Decl(assignmentCompatWithDiscriminatedUnion.ts, 4, 29)) + + type T = +>T : Symbol(T, Decl(assignmentCompatWithDiscriminatedUnion.ts, 4, 46)) + + | { done: true, value: number } // T0 +>done : Symbol(done, Decl(assignmentCompatWithDiscriminatedUnion.ts, 6, 11)) +>value : Symbol(value, Decl(assignmentCompatWithDiscriminatedUnion.ts, 6, 23)) + + | { done: false, value: number }; // T1 +>done : Symbol(done, Decl(assignmentCompatWithDiscriminatedUnion.ts, 7, 11)) +>value : Symbol(value, Decl(assignmentCompatWithDiscriminatedUnion.ts, 7, 24)) + + declare let s: S; +>s : Symbol(s, Decl(assignmentCompatWithDiscriminatedUnion.ts, 9, 15)) +>S : Symbol(S, Decl(assignmentCompatWithDiscriminatedUnion.ts, 3, 20)) + + declare let t: T; +>t : Symbol(t, Decl(assignmentCompatWithDiscriminatedUnion.ts, 10, 15)) +>T : Symbol(T, Decl(assignmentCompatWithDiscriminatedUnion.ts, 4, 46)) + + // S is assignable to T0 when S["done"] is true + // S is assignable to T1 when S["done"] is false + t = s; +>t : Symbol(t, Decl(assignmentCompatWithDiscriminatedUnion.ts, 10, 15)) +>s : Symbol(s, Decl(assignmentCompatWithDiscriminatedUnion.ts, 9, 15)) +} + +// Dropping constituents of T +namespace Example2 { +>Example2 : Symbol(Example2, Decl(assignmentCompatWithDiscriminatedUnion.ts, 15, 1)) + + type S = { a: 0 | 2, b: 4 }; +>S : Symbol(S, Decl(assignmentCompatWithDiscriminatedUnion.ts, 18, 20)) +>a : Symbol(a, Decl(assignmentCompatWithDiscriminatedUnion.ts, 19, 14)) +>b : Symbol(b, Decl(assignmentCompatWithDiscriminatedUnion.ts, 19, 24)) + + type T = { a: 0, b: 1 | 4 } // T0 +>T : Symbol(T, Decl(assignmentCompatWithDiscriminatedUnion.ts, 19, 32)) +>a : Symbol(a, Decl(assignmentCompatWithDiscriminatedUnion.ts, 20, 14)) +>b : Symbol(b, Decl(assignmentCompatWithDiscriminatedUnion.ts, 20, 20)) + + | { a: 1, b: 2 } // T1 +>a : Symbol(a, Decl(assignmentCompatWithDiscriminatedUnion.ts, 21, 14)) +>b : Symbol(b, Decl(assignmentCompatWithDiscriminatedUnion.ts, 21, 20)) + + | { a: 2, b: 3 | 4 }; // T2 +>a : Symbol(a, Decl(assignmentCompatWithDiscriminatedUnion.ts, 22, 14)) +>b : Symbol(b, Decl(assignmentCompatWithDiscriminatedUnion.ts, 22, 20)) + + declare let s: S; +>s : Symbol(s, Decl(assignmentCompatWithDiscriminatedUnion.ts, 23, 15)) +>S : Symbol(S, Decl(assignmentCompatWithDiscriminatedUnion.ts, 18, 20)) + + declare let t: T; +>t : Symbol(t, Decl(assignmentCompatWithDiscriminatedUnion.ts, 24, 15)) +>T : Symbol(T, Decl(assignmentCompatWithDiscriminatedUnion.ts, 19, 32)) + + // S is assignable to T0 when S["a"] is 0 + // S is assignable to T2 when S["a"] is 2 + t = s; +>t : Symbol(t, Decl(assignmentCompatWithDiscriminatedUnion.ts, 24, 15)) +>s : Symbol(s, Decl(assignmentCompatWithDiscriminatedUnion.ts, 23, 15)) +} + +// Unmatched discriminants +namespace Example3 { +>Example3 : Symbol(Example3, Decl(assignmentCompatWithDiscriminatedUnion.ts, 29, 1)) + + type S = { a: 0 | 2, b: 4 }; +>S : Symbol(S, Decl(assignmentCompatWithDiscriminatedUnion.ts, 32, 20)) +>a : Symbol(a, Decl(assignmentCompatWithDiscriminatedUnion.ts, 33, 14)) +>b : Symbol(b, Decl(assignmentCompatWithDiscriminatedUnion.ts, 33, 24)) + + type T = { a: 0, b: 1 | 4 } // T0 +>T : Symbol(T, Decl(assignmentCompatWithDiscriminatedUnion.ts, 33, 32)) +>a : Symbol(a, Decl(assignmentCompatWithDiscriminatedUnion.ts, 34, 14)) +>b : Symbol(b, Decl(assignmentCompatWithDiscriminatedUnion.ts, 34, 20)) + + | { a: 1, b: 2 | 4 } // T1 +>a : Symbol(a, Decl(assignmentCompatWithDiscriminatedUnion.ts, 35, 14)) +>b : Symbol(b, Decl(assignmentCompatWithDiscriminatedUnion.ts, 35, 20)) + + | { a: 2, b: 3 }; // T2 +>a : Symbol(a, Decl(assignmentCompatWithDiscriminatedUnion.ts, 36, 14)) +>b : Symbol(b, Decl(assignmentCompatWithDiscriminatedUnion.ts, 36, 20)) + + declare let s: S; +>s : Symbol(s, Decl(assignmentCompatWithDiscriminatedUnion.ts, 37, 15)) +>S : Symbol(S, Decl(assignmentCompatWithDiscriminatedUnion.ts, 32, 20)) + + declare let t: T; +>t : Symbol(t, Decl(assignmentCompatWithDiscriminatedUnion.ts, 38, 15)) +>T : Symbol(T, Decl(assignmentCompatWithDiscriminatedUnion.ts, 33, 32)) + + // S is assignable to T0 when S["a"] is 0 + // S is *not* assignable to T1 when S["b"] is 4 + // S is *not* assignable to T2 when S["a"] is 2 + t = s; +>t : Symbol(t, Decl(assignmentCompatWithDiscriminatedUnion.ts, 38, 15)) +>s : Symbol(s, Decl(assignmentCompatWithDiscriminatedUnion.ts, 37, 15)) +} + +// Unmatched non-discriminants +namespace Example4 { +>Example4 : Symbol(Example4, Decl(assignmentCompatWithDiscriminatedUnion.ts, 44, 1)) + + type S = { a: 0 | 2, b: 4 }; +>S : Symbol(S, Decl(assignmentCompatWithDiscriminatedUnion.ts, 47, 20)) +>a : Symbol(a, Decl(assignmentCompatWithDiscriminatedUnion.ts, 48, 14)) +>b : Symbol(b, Decl(assignmentCompatWithDiscriminatedUnion.ts, 48, 24)) + + type T = { a: 0, b: 1 | 4 } // T0 +>T : Symbol(T, Decl(assignmentCompatWithDiscriminatedUnion.ts, 48, 32)) +>a : Symbol(a, Decl(assignmentCompatWithDiscriminatedUnion.ts, 49, 14)) +>b : Symbol(b, Decl(assignmentCompatWithDiscriminatedUnion.ts, 49, 20)) + + | { a: 1, b: 2 } // T1 +>a : Symbol(a, Decl(assignmentCompatWithDiscriminatedUnion.ts, 50, 14)) +>b : Symbol(b, Decl(assignmentCompatWithDiscriminatedUnion.ts, 50, 20)) + + | { a: 2, b: 3 | 4, c: string }; // T2 +>a : Symbol(a, Decl(assignmentCompatWithDiscriminatedUnion.ts, 51, 14)) +>b : Symbol(b, Decl(assignmentCompatWithDiscriminatedUnion.ts, 51, 20)) +>c : Symbol(c, Decl(assignmentCompatWithDiscriminatedUnion.ts, 51, 34)) + + declare let s: S; +>s : Symbol(s, Decl(assignmentCompatWithDiscriminatedUnion.ts, 52, 15)) +>S : Symbol(S, Decl(assignmentCompatWithDiscriminatedUnion.ts, 47, 20)) + + declare let t: T; +>t : Symbol(t, Decl(assignmentCompatWithDiscriminatedUnion.ts, 53, 15)) +>T : Symbol(T, Decl(assignmentCompatWithDiscriminatedUnion.ts, 48, 32)) + + // S is assignable to T0 when S["a"] is 0 + // S is *not* assignable to T2 when S["a"] is 2 as S is missing "c" + t = s; +>t : Symbol(t, Decl(assignmentCompatWithDiscriminatedUnion.ts, 53, 15)) +>s : Symbol(s, Decl(assignmentCompatWithDiscriminatedUnion.ts, 52, 15)) +} + +// Maximum discriminant combinations +namespace Example5 { +>Example5 : Symbol(Example5, Decl(assignmentCompatWithDiscriminatedUnion.ts, 58, 1)) + + // NOTE: MAX_DISCRIMINANT_COMBINATIONS is currently 25 + // 3 discriminant properties with 3 types a piece + // is 27 possible combinations. + type N = 0 | 1 | 2; +>N : Symbol(N, Decl(assignmentCompatWithDiscriminatedUnion.ts, 61, 20)) + + type S = { a: N, b: N, c: N }; +>S : Symbol(S, Decl(assignmentCompatWithDiscriminatedUnion.ts, 65, 23)) +>a : Symbol(a, Decl(assignmentCompatWithDiscriminatedUnion.ts, 66, 14)) +>N : Symbol(N, Decl(assignmentCompatWithDiscriminatedUnion.ts, 61, 20)) +>b : Symbol(b, Decl(assignmentCompatWithDiscriminatedUnion.ts, 66, 20)) +>N : Symbol(N, Decl(assignmentCompatWithDiscriminatedUnion.ts, 61, 20)) +>c : Symbol(c, Decl(assignmentCompatWithDiscriminatedUnion.ts, 66, 26)) +>N : Symbol(N, Decl(assignmentCompatWithDiscriminatedUnion.ts, 61, 20)) + + type T = { a: 0, b: N, c: N } +>T : Symbol(T, Decl(assignmentCompatWithDiscriminatedUnion.ts, 66, 34)) +>a : Symbol(a, Decl(assignmentCompatWithDiscriminatedUnion.ts, 67, 14)) +>b : Symbol(b, Decl(assignmentCompatWithDiscriminatedUnion.ts, 67, 20)) +>N : Symbol(N, Decl(assignmentCompatWithDiscriminatedUnion.ts, 61, 20)) +>c : Symbol(c, Decl(assignmentCompatWithDiscriminatedUnion.ts, 67, 26)) +>N : Symbol(N, Decl(assignmentCompatWithDiscriminatedUnion.ts, 61, 20)) + + | { a: 1, b: N, c: N } +>a : Symbol(a, Decl(assignmentCompatWithDiscriminatedUnion.ts, 68, 14)) +>b : Symbol(b, Decl(assignmentCompatWithDiscriminatedUnion.ts, 68, 20)) +>N : Symbol(N, Decl(assignmentCompatWithDiscriminatedUnion.ts, 61, 20)) +>c : Symbol(c, Decl(assignmentCompatWithDiscriminatedUnion.ts, 68, 26)) +>N : Symbol(N, Decl(assignmentCompatWithDiscriminatedUnion.ts, 61, 20)) + + | { a: 2, b: N, c: N } +>a : Symbol(a, Decl(assignmentCompatWithDiscriminatedUnion.ts, 69, 14)) +>b : Symbol(b, Decl(assignmentCompatWithDiscriminatedUnion.ts, 69, 20)) +>N : Symbol(N, Decl(assignmentCompatWithDiscriminatedUnion.ts, 61, 20)) +>c : Symbol(c, Decl(assignmentCompatWithDiscriminatedUnion.ts, 69, 26)) +>N : Symbol(N, Decl(assignmentCompatWithDiscriminatedUnion.ts, 61, 20)) + + | { a: N, b: 0, c: N } +>a : Symbol(a, Decl(assignmentCompatWithDiscriminatedUnion.ts, 70, 14)) +>N : Symbol(N, Decl(assignmentCompatWithDiscriminatedUnion.ts, 61, 20)) +>b : Symbol(b, Decl(assignmentCompatWithDiscriminatedUnion.ts, 70, 20)) +>c : Symbol(c, Decl(assignmentCompatWithDiscriminatedUnion.ts, 70, 26)) +>N : Symbol(N, Decl(assignmentCompatWithDiscriminatedUnion.ts, 61, 20)) + + | { a: N, b: 1, c: N } +>a : Symbol(a, Decl(assignmentCompatWithDiscriminatedUnion.ts, 71, 14)) +>N : Symbol(N, Decl(assignmentCompatWithDiscriminatedUnion.ts, 61, 20)) +>b : Symbol(b, Decl(assignmentCompatWithDiscriminatedUnion.ts, 71, 20)) +>c : Symbol(c, Decl(assignmentCompatWithDiscriminatedUnion.ts, 71, 26)) +>N : Symbol(N, Decl(assignmentCompatWithDiscriminatedUnion.ts, 61, 20)) + + | { a: N, b: 2, c: N } +>a : Symbol(a, Decl(assignmentCompatWithDiscriminatedUnion.ts, 72, 14)) +>N : Symbol(N, Decl(assignmentCompatWithDiscriminatedUnion.ts, 61, 20)) +>b : Symbol(b, Decl(assignmentCompatWithDiscriminatedUnion.ts, 72, 20)) +>c : Symbol(c, Decl(assignmentCompatWithDiscriminatedUnion.ts, 72, 26)) +>N : Symbol(N, Decl(assignmentCompatWithDiscriminatedUnion.ts, 61, 20)) + + | { a: N, b: N, c: 0 } +>a : Symbol(a, Decl(assignmentCompatWithDiscriminatedUnion.ts, 73, 14)) +>N : Symbol(N, Decl(assignmentCompatWithDiscriminatedUnion.ts, 61, 20)) +>b : Symbol(b, Decl(assignmentCompatWithDiscriminatedUnion.ts, 73, 20)) +>N : Symbol(N, Decl(assignmentCompatWithDiscriminatedUnion.ts, 61, 20)) +>c : Symbol(c, Decl(assignmentCompatWithDiscriminatedUnion.ts, 73, 26)) + + | { a: N, b: N, c: 1 } +>a : Symbol(a, Decl(assignmentCompatWithDiscriminatedUnion.ts, 74, 14)) +>N : Symbol(N, Decl(assignmentCompatWithDiscriminatedUnion.ts, 61, 20)) +>b : Symbol(b, Decl(assignmentCompatWithDiscriminatedUnion.ts, 74, 20)) +>N : Symbol(N, Decl(assignmentCompatWithDiscriminatedUnion.ts, 61, 20)) +>c : Symbol(c, Decl(assignmentCompatWithDiscriminatedUnion.ts, 74, 26)) + + | { a: N, b: N, c: 2 }; +>a : Symbol(a, Decl(assignmentCompatWithDiscriminatedUnion.ts, 75, 14)) +>N : Symbol(N, Decl(assignmentCompatWithDiscriminatedUnion.ts, 61, 20)) +>b : Symbol(b, Decl(assignmentCompatWithDiscriminatedUnion.ts, 75, 20)) +>N : Symbol(N, Decl(assignmentCompatWithDiscriminatedUnion.ts, 61, 20)) +>c : Symbol(c, Decl(assignmentCompatWithDiscriminatedUnion.ts, 75, 26)) + + declare let s: S; +>s : Symbol(s, Decl(assignmentCompatWithDiscriminatedUnion.ts, 76, 15)) +>S : Symbol(S, Decl(assignmentCompatWithDiscriminatedUnion.ts, 65, 23)) + + declare let t: T; +>t : Symbol(t, Decl(assignmentCompatWithDiscriminatedUnion.ts, 77, 15)) +>T : Symbol(T, Decl(assignmentCompatWithDiscriminatedUnion.ts, 66, 34)) + + // S *should* be assignable but the number of + // combinations is too complex. + t = s; +>t : Symbol(t, Decl(assignmentCompatWithDiscriminatedUnion.ts, 77, 15)) +>s : Symbol(s, Decl(assignmentCompatWithDiscriminatedUnion.ts, 76, 15)) +} + +// https://github.com/Microsoft/TypeScript/issues/14865 +namespace Example6 { +>Example6 : Symbol(Example6, Decl(assignmentCompatWithDiscriminatedUnion.ts, 82, 1)) + + type Style1 = { +>Style1 : Symbol(Style1, Decl(assignmentCompatWithDiscriminatedUnion.ts, 85, 20)) + + type: "A"; +>type : Symbol(type, Decl(assignmentCompatWithDiscriminatedUnion.ts, 86, 19)) + + data: string; +>data : Symbol(data, Decl(assignmentCompatWithDiscriminatedUnion.ts, 87, 18)) + + } | { + type: "B"; +>type : Symbol(type, Decl(assignmentCompatWithDiscriminatedUnion.ts, 89, 9)) + + data: string; +>data : Symbol(data, Decl(assignmentCompatWithDiscriminatedUnion.ts, 90, 18)) + + }; + + type Style2 = { +>Style2 : Symbol(Style2, Decl(assignmentCompatWithDiscriminatedUnion.ts, 92, 6)) + + type: "A" | "B"; +>type : Symbol(type, Decl(assignmentCompatWithDiscriminatedUnion.ts, 94, 19)) + + data: string; +>data : Symbol(data, Decl(assignmentCompatWithDiscriminatedUnion.ts, 95, 24)) + } + + const a: Style2 = { type: "A", data: "whatevs" }; +>a : Symbol(a, Decl(assignmentCompatWithDiscriminatedUnion.ts, 99, 9)) +>Style2 : Symbol(Style2, Decl(assignmentCompatWithDiscriminatedUnion.ts, 92, 6)) +>type : Symbol(type, Decl(assignmentCompatWithDiscriminatedUnion.ts, 99, 23)) +>data : Symbol(data, Decl(assignmentCompatWithDiscriminatedUnion.ts, 99, 34)) + + let b: Style1; +>b : Symbol(b, Decl(assignmentCompatWithDiscriminatedUnion.ts, 100, 7)) +>Style1 : Symbol(Style1, Decl(assignmentCompatWithDiscriminatedUnion.ts, 85, 20)) + + a.type; // "A" | "B" +>a.type : Symbol(type, Decl(assignmentCompatWithDiscriminatedUnion.ts, 94, 19)) +>a : Symbol(a, Decl(assignmentCompatWithDiscriminatedUnion.ts, 99, 9)) +>type : Symbol(type, Decl(assignmentCompatWithDiscriminatedUnion.ts, 94, 19)) + + b.type; // "A" | "B" +>b.type : Symbol(type, Decl(assignmentCompatWithDiscriminatedUnion.ts, 86, 19), Decl(assignmentCompatWithDiscriminatedUnion.ts, 89, 9)) +>b : Symbol(b, Decl(assignmentCompatWithDiscriminatedUnion.ts, 100, 7)) +>type : Symbol(type, Decl(assignmentCompatWithDiscriminatedUnion.ts, 86, 19), Decl(assignmentCompatWithDiscriminatedUnion.ts, 89, 9)) + + b = a; // should be assignable +>b : Symbol(b, Decl(assignmentCompatWithDiscriminatedUnion.ts, 100, 7)) +>a : Symbol(a, Decl(assignmentCompatWithDiscriminatedUnion.ts, 99, 9)) +} diff --git a/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.types b/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.types new file mode 100644 index 0000000000000..b3a1881bfbee0 --- /dev/null +++ b/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.types @@ -0,0 +1,283 @@ +=== tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts === +// see 'typeRelatedToDiscriminatedType' in checker.ts: + +// IteratorResult +namespace Example1 { +>Example1 : typeof Example1 + + type S = { done: boolean, value: number }; +>S : S +>done : boolean +>value : number + + type T = +>T : T + + | { done: true, value: number } // T0 +>done : true +>true : true +>value : number + + | { done: false, value: number }; // T1 +>done : false +>false : false +>value : number + + declare let s: S; +>s : S + + declare let t: T; +>t : T + + // S is assignable to T0 when S["done"] is true + // S is assignable to T1 when S["done"] is false + t = s; +>t = s : S +>t : T +>s : S +} + +// Dropping constituents of T +namespace Example2 { +>Example2 : typeof Example2 + + type S = { a: 0 | 2, b: 4 }; +>S : S +>a : 0 | 2 +>b : 4 + + type T = { a: 0, b: 1 | 4 } // T0 +>T : T +>a : 0 +>b : 4 | 1 + + | { a: 1, b: 2 } // T1 +>a : 1 +>b : 2 + + | { a: 2, b: 3 | 4 }; // T2 +>a : 2 +>b : 4 | 3 + + declare let s: S; +>s : S + + declare let t: T; +>t : T + + // S is assignable to T0 when S["a"] is 0 + // S is assignable to T2 when S["a"] is 2 + t = s; +>t = s : S +>t : T +>s : S +} + +// Unmatched discriminants +namespace Example3 { +>Example3 : typeof Example3 + + type S = { a: 0 | 2, b: 4 }; +>S : S +>a : 0 | 2 +>b : 4 + + type T = { a: 0, b: 1 | 4 } // T0 +>T : T +>a : 0 +>b : 4 | 1 + + | { a: 1, b: 2 | 4 } // T1 +>a : 1 +>b : 2 | 4 + + | { a: 2, b: 3 }; // T2 +>a : 2 +>b : 3 + + declare let s: S; +>s : S + + declare let t: T; +>t : T + + // S is assignable to T0 when S["a"] is 0 + // S is *not* assignable to T1 when S["b"] is 4 + // S is *not* assignable to T2 when S["a"] is 2 + t = s; +>t = s : S +>t : T +>s : S +} + +// Unmatched non-discriminants +namespace Example4 { +>Example4 : typeof Example4 + + type S = { a: 0 | 2, b: 4 }; +>S : S +>a : 0 | 2 +>b : 4 + + type T = { a: 0, b: 1 | 4 } // T0 +>T : T +>a : 0 +>b : 4 | 1 + + | { a: 1, b: 2 } // T1 +>a : 1 +>b : 2 + + | { a: 2, b: 3 | 4, c: string }; // T2 +>a : 2 +>b : 4 | 3 +>c : string + + declare let s: S; +>s : S + + declare let t: T; +>t : T + + // S is assignable to T0 when S["a"] is 0 + // S is *not* assignable to T2 when S["a"] is 2 as S is missing "c" + t = s; +>t = s : S +>t : T +>s : S +} + +// Maximum discriminant combinations +namespace Example5 { +>Example5 : typeof Example5 + + // NOTE: MAX_DISCRIMINANT_COMBINATIONS is currently 25 + // 3 discriminant properties with 3 types a piece + // is 27 possible combinations. + type N = 0 | 1 | 2; +>N : 0 | 2 | 1 + + type S = { a: N, b: N, c: N }; +>S : S +>a : 0 | 2 | 1 +>b : 0 | 2 | 1 +>c : 0 | 2 | 1 + + type T = { a: 0, b: N, c: N } +>T : T +>a : 0 +>b : 0 | 2 | 1 +>c : 0 | 2 | 1 + + | { a: 1, b: N, c: N } +>a : 1 +>b : 0 | 2 | 1 +>c : 0 | 2 | 1 + + | { a: 2, b: N, c: N } +>a : 2 +>b : 0 | 2 | 1 +>c : 0 | 2 | 1 + + | { a: N, b: 0, c: N } +>a : 0 | 2 | 1 +>b : 0 +>c : 0 | 2 | 1 + + | { a: N, b: 1, c: N } +>a : 0 | 2 | 1 +>b : 1 +>c : 0 | 2 | 1 + + | { a: N, b: 2, c: N } +>a : 0 | 2 | 1 +>b : 2 +>c : 0 | 2 | 1 + + | { a: N, b: N, c: 0 } +>a : 0 | 2 | 1 +>b : 0 | 2 | 1 +>c : 0 + + | { a: N, b: N, c: 1 } +>a : 0 | 2 | 1 +>b : 0 | 2 | 1 +>c : 1 + + | { a: N, b: N, c: 2 }; +>a : 0 | 2 | 1 +>b : 0 | 2 | 1 +>c : 2 + + declare let s: S; +>s : S + + declare let t: T; +>t : T + + // S *should* be assignable but the number of + // combinations is too complex. + t = s; +>t = s : S +>t : T +>s : S +} + +// https://github.com/Microsoft/TypeScript/issues/14865 +namespace Example6 { +>Example6 : typeof Example6 + + type Style1 = { +>Style1 : Style1 + + type: "A"; +>type : "A" + + data: string; +>data : string + + } | { + type: "B"; +>type : "B" + + data: string; +>data : string + + }; + + type Style2 = { +>Style2 : Style2 + + type: "A" | "B"; +>type : "A" | "B" + + data: string; +>data : string + } + + const a: Style2 = { type: "A", data: "whatevs" }; +>a : Style2 +>{ type: "A", data: "whatevs" } : { type: "A"; data: string; } +>type : "A" +>"A" : "A" +>data : string +>"whatevs" : "whatevs" + + let b: Style1; +>b : Style1 + + a.type; // "A" | "B" +>a.type : "A" | "B" +>a : Style2 +>type : "A" | "B" + + b.type; // "A" | "B" +>b.type : "A" | "B" +>b : Style1 +>type : "A" | "B" + + b = a; // should be assignable +>b = a : Style2 +>b : Style1 +>a : Style2 +} diff --git a/tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts b/tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts new file mode 100644 index 0000000000000..8aec2294ef93a --- /dev/null +++ b/tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts @@ -0,0 +1,105 @@ +// see 'typeRelatedToDiscriminatedType' in checker.ts: + +// IteratorResult +namespace Example1 { + type S = { done: boolean, value: number }; + type T = + | { done: true, value: number } // T0 + | { done: false, value: number }; // T1 + + declare let s: S; + declare let t: T; + + // S is assignable to T0 when S["done"] is true + // S is assignable to T1 when S["done"] is false + t = s; +} + +// Dropping constituents of T +namespace Example2 { + type S = { a: 0 | 2, b: 4 }; + type T = { a: 0, b: 1 | 4 } // T0 + | { a: 1, b: 2 } // T1 + | { a: 2, b: 3 | 4 }; // T2 + declare let s: S; + declare let t: T; + + // S is assignable to T0 when S["a"] is 0 + // S is assignable to T2 when S["a"] is 2 + t = s; +} + +// Unmatched discriminants +namespace Example3 { + type S = { a: 0 | 2, b: 4 }; + type T = { a: 0, b: 1 | 4 } // T0 + | { a: 1, b: 2 | 4 } // T1 + | { a: 2, b: 3 }; // T2 + declare let s: S; + declare let t: T; + + // S is assignable to T0 when S["a"] is 0 + // S is *not* assignable to T1 when S["b"] is 4 + // S is *not* assignable to T2 when S["a"] is 2 + t = s; +} + +// Unmatched non-discriminants +namespace Example4 { + type S = { a: 0 | 2, b: 4 }; + type T = { a: 0, b: 1 | 4 } // T0 + | { a: 1, b: 2 } // T1 + | { a: 2, b: 3 | 4, c: string }; // T2 + declare let s: S; + declare let t: T; + + // S is assignable to T0 when S["a"] is 0 + // S is *not* assignable to T2 when S["a"] is 2 as S is missing "c" + t = s; +} + +// Maximum discriminant combinations +namespace Example5 { + // NOTE: MAX_DISCRIMINANT_COMBINATIONS is currently 25 + // 3 discriminant properties with 3 types a piece + // is 27 possible combinations. + type N = 0 | 1 | 2; + type S = { a: N, b: N, c: N }; + type T = { a: 0, b: N, c: N } + | { a: 1, b: N, c: N } + | { a: 2, b: N, c: N } + | { a: N, b: 0, c: N } + | { a: N, b: 1, c: N } + | { a: N, b: 2, c: N } + | { a: N, b: N, c: 0 } + | { a: N, b: N, c: 1 } + | { a: N, b: N, c: 2 }; + declare let s: S; + declare let t: T; + + // S *should* be assignable but the number of + // combinations is too complex. + t = s; +} + +// https://github.com/Microsoft/TypeScript/issues/14865 +namespace Example6 { + type Style1 = { + type: "A"; + data: string; + } | { + type: "B"; + data: string; + }; + + type Style2 = { + type: "A" | "B"; + data: string; + } + + const a: Style2 = { type: "A", data: "whatevs" }; + let b: Style1; + a.type; // "A" | "B" + b.type; // "A" | "B" + b = a; // should be assignable +} \ No newline at end of file