diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c090dc1c84..61f31d7390 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17279,7 +17279,11 @@ namespace ts { result &= signaturesRelatedTo(source, type, SignatureKind.Construct, /*reportStructuralErrors*/ false); if (result) { result &= indexTypesRelatedTo(source, type, IndexKind.String, /*sourceIsPrimitive*/ false, /*reportStructuralErrors*/ false, IntersectionState.None); - if (result) { + // Comparing numeric index types when both `source` and `type` are tuples is unnecessary as the + // element types should be sufficiently covered by `propertiesRelatedTo`. It also causes problems + // with index type assignability as the types for the excluded discriminants are still included + // in the index type. + if (result && !(isTupleType(source) && isTupleType(type))) { result &= indexTypesRelatedTo(source, type, IndexKind.Number, /*sourceIsPrimitive*/ false, /*reportStructuralErrors*/ false, IntersectionState.None); } } @@ -17504,6 +17508,7 @@ namespace ts { for (let i = 0; i < maxArity; i++) { const targetFlags = i < targetArity ? target.target.elementFlags[i] : targetRestFlag; const sourceFlags = isTupleType(source) && i < sourceArity ? source.target.elementFlags[i] : sourceRestFlag; + let canExcludeDiscriminants = !!excludedProperties; if (sourceFlags && targetFlags) { if (targetFlags & ElementFlags.Variadic && !(sourceFlags & ElementFlags.Variadic) || (sourceFlags & ElementFlags.Variadic && !(targetFlags & ElementFlags.Variable))) { @@ -17520,6 +17525,15 @@ namespace ts { return Ternary.False; } } + // We can only exclude discriminant properties if we have not yet encountered a variable-length element. + if (canExcludeDiscriminants) { + if (sourceFlags & ElementFlags.Variable || targetFlags & ElementFlags.Variable) { + canExcludeDiscriminants = false; + } + if (canExcludeDiscriminants && excludedProperties?.has(("" + i) as __String)) { + continue; + } + } const sourceType = getTypeArguments(source)[Math.min(i, sourceArity - 1)]; const targetType = getTypeArguments(target)[Math.min(i, targetArity - 1)]; const targetCheckType = sourceFlags & ElementFlags.Variadic && targetFlags & ElementFlags.Rest ? createArrayType(targetType) : targetType; diff --git a/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.errors.txt b/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.errors.txt index e5e3ee8b49..ab5ed2fba8 100644 --- a/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.errors.txt +++ b/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.errors.txt @@ -221,4 +221,13 @@ tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignme type: obj1.type }; } - } \ No newline at end of file + } + + // https://github.com/microsoft/TypeScript/issues/39357 + namespace GH39357 { + type A = ["a", number] | ["b", number] | ["c", string]; + type B = "a" | "b" | "c"; + declare const b: B; + const a: A = b === "a" || b === "b" ? [b, 1] : ["c", ""]; + } + \ No newline at end of file diff --git a/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.js b/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.js index 2719a525ba..d05e303693 100644 --- a/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.js +++ b/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.js @@ -191,7 +191,16 @@ namespace GH20889 { type: obj1.type }; } -} +} + +// https://github.com/microsoft/TypeScript/issues/39357 +namespace GH39357 { + type A = ["a", number] | ["b", number] | ["c", string]; + type B = "a" | "b" | "c"; + declare const b: B; + const a: A = b === "a" || b === "b" ? [b, 1] : ["c", ""]; +} + //// [assignmentCompatWithDiscriminatedUnion.js] // see 'typeRelatedToDiscriminatedType' in checker.ts: @@ -289,3 +298,8 @@ var GH20889; }; } })(GH20889 || (GH20889 = {})); +// https://github.com/microsoft/TypeScript/issues/39357 +var GH39357; +(function (GH39357) { + var a = b === "a" || b === "b" ? [b, 1] : ["c", ""]; +})(GH39357 || (GH39357 = {})); diff --git a/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.symbols b/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.symbols index 1d0afd3fd9..2e62cc8b7a 100644 --- a/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.symbols +++ b/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.symbols @@ -492,3 +492,26 @@ namespace GH20889 { }; } } + +// https://github.com/microsoft/TypeScript/issues/39357 +namespace GH39357 { +>GH39357 : Symbol(GH39357, Decl(assignmentCompatWithDiscriminatedUnion.ts, 192, 1)) + + type A = ["a", number] | ["b", number] | ["c", string]; +>A : Symbol(A, Decl(assignmentCompatWithDiscriminatedUnion.ts, 195, 19)) + + type B = "a" | "b" | "c"; +>B : Symbol(B, Decl(assignmentCompatWithDiscriminatedUnion.ts, 196, 59)) + + declare const b: B; +>b : Symbol(b, Decl(assignmentCompatWithDiscriminatedUnion.ts, 198, 17)) +>B : Symbol(B, Decl(assignmentCompatWithDiscriminatedUnion.ts, 196, 59)) + + const a: A = b === "a" || b === "b" ? [b, 1] : ["c", ""]; +>a : Symbol(a, Decl(assignmentCompatWithDiscriminatedUnion.ts, 199, 9)) +>A : Symbol(A, Decl(assignmentCompatWithDiscriminatedUnion.ts, 195, 19)) +>b : Symbol(b, Decl(assignmentCompatWithDiscriminatedUnion.ts, 198, 17)) +>b : Symbol(b, Decl(assignmentCompatWithDiscriminatedUnion.ts, 198, 17)) +>b : Symbol(b, Decl(assignmentCompatWithDiscriminatedUnion.ts, 198, 17)) +} + diff --git a/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.types b/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.types index cde58aeb49..4677ed8780 100644 --- a/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.types +++ b/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.types @@ -464,3 +464,35 @@ namespace GH20889 { }; } } + +// https://github.com/microsoft/TypeScript/issues/39357 +namespace GH39357 { +>GH39357 : typeof GH39357 + + type A = ["a", number] | ["b", number] | ["c", string]; +>A : A + + type B = "a" | "b" | "c"; +>B : "a" | "b" | "c" + + declare const b: B; +>b : "a" | "b" | "c" + + const a: A = b === "a" || b === "b" ? [b, 1] : ["c", ""]; +>a : A +>b === "a" || b === "b" ? [b, 1] : ["c", ""] : ["a" | "b", number] | ["c", string] +>b === "a" || b === "b" : boolean +>b === "a" : boolean +>b : "a" | "b" | "c" +>"a" : "a" +>b === "b" : boolean +>b : "b" | "c" +>"b" : "b" +>[b, 1] : ["a" | "b", number] +>b : "a" | "b" +>1 : 1 +>["c", ""] : ["c", string] +>"c" : "c" +>"" : "" +} + diff --git a/tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts b/tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts index e264df259a..bea0b5ef78 100644 --- a/tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts +++ b/tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts @@ -190,4 +190,12 @@ namespace GH20889 { type: obj1.type }; } -} \ No newline at end of file +} + +// https://github.com/microsoft/TypeScript/issues/39357 +namespace GH39357 { + type A = ["a", number] | ["b", number] | ["c", string]; + type B = "a" | "b" | "c"; + declare const b: B; + const a: A = b === "a" || b === "b" ? [b, 1] : ["c", ""]; +}