Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17200,8 +17200,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
covariant = !covariant;
}
// Always substitute on type parameters, regardless of variance, since even
// in contravariant positions, they may rely on substituted constraints to be valid
if ((covariant || type.flags & TypeFlags.TypeVariable) && parent.kind === SyntaxKind.ConditionalType && node === (parent as ConditionalTypeNode).trueType) {
// in contravariant positions, they may rely on substituted constraints to be valid.
// For union/intersection types that don't contain type variables, don't apply narrowing
// based on structural equality with the check type - different occurrences of
// `number | string` in the code are independent even if they resolve to the same
// canonical type. Named types (interfaces, etc.) should still be narrowed since
// different references to the same named type refer to the same entity.
const isStructuralTypeWithoutTypeVariables = !!(type.flags & TypeFlags.UnionOrIntersection) && !couldContainTypeVariables(type);
if (!isStructuralTypeWithoutTypeVariables && (covariant || type.flags & TypeFlags.TypeVariable) && parent.kind === SyntaxKind.ConditionalType && node === (parent as ConditionalTypeNode).trueType) {
const constraint = getImpliedConstraint(type, (parent as ConditionalTypeNode).checkType, (parent as ConditionalTypeNode).extendsType);
if (constraint) {
constraints = append(constraints, constraint);
Expand Down
97 changes: 97 additions & 0 deletions tests/baselines/reference/spreadTupleUnionDistribution.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//// [tests/cases/conformance/types/spread/spreadTupleUnionDistribution.ts] ////

//// [spreadTupleUnionDistribution.ts]
// Repro from #62812
// Spread operator fails to distribute over union when recursive type call is inlined instead of aliased

type CrossProduct<Union, Counter extends unknown[]> =
Counter extends [infer Zero, ...infer Rest]
? (Union extends infer Member
? [Member, ...CrossProduct<Union, Rest>]
: never)
: [];

// Basic test - this works
let test1: CrossProduct<number | string, [undefined]>; // [string] | [number]
type Depth1 = CrossProduct<number | string, [undefined]> // [string] | [number]

// With alias - this should work and give full cross product
let test2: (number | string extends infer Union ? (Union extends unknown ? [Union, ...Depth1]: never) : never);
// Expected: [string, string] | [number, number] | [string, number] | [number, string]

// With inlined type - this should also work but currently doesn't distribute properly
let test3: (number | string extends infer Union ? (Union extends unknown ? [Union, ...CrossProduct<number | string, [undefined]>]: never) : never);
// Expected: [string, string] | [number, number] | [string, number] | [number, string]
// Actual (bug): [string, string] | [number, number]

// With literal union - this works
let test4: (number | string extends infer Union ? (Union extends unknown ? [Union, ...([string] | [number])]: never) : never);
// Expected: [string, string] | [number, number] | [string, number] | [number, string]

// Test that the types are actually correct by checking assignability
type Expected = [string, string] | [number, number] | [string, number] | [number, string];

// These should all be true (no error)
type Test1Check = Expected extends typeof test2 ? true : false;
type Test2Check = typeof test2 extends Expected ? true : false;

// If the bug is fixed, these will also be true (no error)
type Test3Check = Expected extends typeof test3 ? true : false;
type Test4Check = typeof test3 extends Expected ? true : false;

type Test5Check = Expected extends typeof test4 ? true : false;
type Test6Check = typeof test4 extends Expected ? true : false;

// Force an error if checks fail
const _check1: Test1Check = true;
const _check2: Test2Check = true;
const _check3: Test3Check = true; // This will error if bug exists
const _check4: Test4Check = true; // This will error if bug exists
const _check5: Test5Check = true;
const _check6: Test6Check = true;


//// [spreadTupleUnionDistribution.js]
"use strict";
// Repro from #62812
// Spread operator fails to distribute over union when recursive type call is inlined instead of aliased
// Basic test - this works
var test1; // [string] | [number]
// With alias - this should work and give full cross product
var test2;
// Expected: [string, string] | [number, number] | [string, number] | [number, string]
// With inlined type - this should also work but currently doesn't distribute properly
var test3;
// Expected: [string, string] | [number, number] | [string, number] | [number, string]
// Actual (bug): [string, string] | [number, number]
// With literal union - this works
var test4;
// Force an error if checks fail
var _check1 = true;
var _check2 = true;
var _check3 = true; // This will error if bug exists
var _check4 = true; // This will error if bug exists
var _check5 = true;
var _check6 = true;


//// [spreadTupleUnionDistribution.d.ts]
type CrossProduct<Union, Counter extends unknown[]> = Counter extends [infer Zero, ...infer Rest] ? (Union extends infer Member ? [Member, ...CrossProduct<Union, Rest>] : never) : [];
declare let test1: CrossProduct<number | string, [undefined]>;
type Depth1 = CrossProduct<number | string, [undefined]>;
declare let test2: (number | string extends infer Union ? (Union extends unknown ? [Union, ...Depth1] : never) : never);
declare let test3: (number | string extends infer Union ? (Union extends unknown ? [Union, ...CrossProduct<number | string, [undefined]>] : never) : never);
declare let test4: (number | string extends infer Union ? (Union extends unknown ? [Union, ...([string] | [number])] : never) : never);
type Expected = [string, string] | [number, number] | [string, number] | [number, string];
type Test1Check = Expected extends typeof test2 ? true : false;
type Test2Check = typeof test2 extends Expected ? true : false;
type Test3Check = Expected extends typeof test3 ? true : false;
type Test4Check = typeof test3 extends Expected ? true : false;
type Test5Check = Expected extends typeof test4 ? true : false;
type Test6Check = typeof test4 extends Expected ? true : false;
declare const _check1: Test1Check;
declare const _check2: Test2Check;
declare const _check3: Test3Check;
declare const _check4: Test4Check;
declare const _check5: Test5Check;
declare const _check6: Test6Check;
129 changes: 129 additions & 0 deletions tests/baselines/reference/spreadTupleUnionDistribution.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
//// [tests/cases/conformance/types/spread/spreadTupleUnionDistribution.ts] ////

=== spreadTupleUnionDistribution.ts ===
// Repro from #62812
// Spread operator fails to distribute over union when recursive type call is inlined instead of aliased

type CrossProduct<Union, Counter extends unknown[]> =
>CrossProduct : Symbol(CrossProduct, Decl(spreadTupleUnionDistribution.ts, 0, 0))
>Union : Symbol(Union, Decl(spreadTupleUnionDistribution.ts, 3, 18))
>Counter : Symbol(Counter, Decl(spreadTupleUnionDistribution.ts, 3, 24))

Counter extends [infer Zero, ...infer Rest]
>Counter : Symbol(Counter, Decl(spreadTupleUnionDistribution.ts, 3, 24))
>Zero : Symbol(Zero, Decl(spreadTupleUnionDistribution.ts, 4, 26))
>Rest : Symbol(Rest, Decl(spreadTupleUnionDistribution.ts, 4, 41))

? (Union extends infer Member
>Union : Symbol(Union, Decl(spreadTupleUnionDistribution.ts, 3, 18))
>Member : Symbol(Member, Decl(spreadTupleUnionDistribution.ts, 5, 26))

? [Member, ...CrossProduct<Union, Rest>]
>Member : Symbol(Member, Decl(spreadTupleUnionDistribution.ts, 5, 26))
>CrossProduct : Symbol(CrossProduct, Decl(spreadTupleUnionDistribution.ts, 0, 0))
>Union : Symbol(Union, Decl(spreadTupleUnionDistribution.ts, 3, 18))
>Rest : Symbol(Rest, Decl(spreadTupleUnionDistribution.ts, 4, 41))

: never)
: [];

// Basic test - this works
let test1: CrossProduct<number | string, [undefined]>; // [string] | [number]
>test1 : Symbol(test1, Decl(spreadTupleUnionDistribution.ts, 11, 3))
>CrossProduct : Symbol(CrossProduct, Decl(spreadTupleUnionDistribution.ts, 0, 0))

type Depth1 = CrossProduct<number | string, [undefined]> // [string] | [number]
>Depth1 : Symbol(Depth1, Decl(spreadTupleUnionDistribution.ts, 11, 54))
>CrossProduct : Symbol(CrossProduct, Decl(spreadTupleUnionDistribution.ts, 0, 0))

// With alias - this should work and give full cross product
let test2: (number | string extends infer Union ? (Union extends unknown ? [Union, ...Depth1]: never) : never);
>test2 : Symbol(test2, Decl(spreadTupleUnionDistribution.ts, 15, 3))
>Union : Symbol(Union, Decl(spreadTupleUnionDistribution.ts, 15, 41))
>Union : Symbol(Union, Decl(spreadTupleUnionDistribution.ts, 15, 41))
>Union : Symbol(Union, Decl(spreadTupleUnionDistribution.ts, 15, 41))
>Depth1 : Symbol(Depth1, Decl(spreadTupleUnionDistribution.ts, 11, 54))

// Expected: [string, string] | [number, number] | [string, number] | [number, string]

// With inlined type - this should also work but currently doesn't distribute properly
let test3: (number | string extends infer Union ? (Union extends unknown ? [Union, ...CrossProduct<number | string, [undefined]>]: never) : never);
>test3 : Symbol(test3, Decl(spreadTupleUnionDistribution.ts, 19, 3))
>Union : Symbol(Union, Decl(spreadTupleUnionDistribution.ts, 19, 41))
>Union : Symbol(Union, Decl(spreadTupleUnionDistribution.ts, 19, 41))
>Union : Symbol(Union, Decl(spreadTupleUnionDistribution.ts, 19, 41))
>CrossProduct : Symbol(CrossProduct, Decl(spreadTupleUnionDistribution.ts, 0, 0))

// Expected: [string, string] | [number, number] | [string, number] | [number, string]
// Actual (bug): [string, string] | [number, number]

// With literal union - this works
let test4: (number | string extends infer Union ? (Union extends unknown ? [Union, ...([string] | [number])]: never) : never);
>test4 : Symbol(test4, Decl(spreadTupleUnionDistribution.ts, 24, 3))
>Union : Symbol(Union, Decl(spreadTupleUnionDistribution.ts, 24, 41))
>Union : Symbol(Union, Decl(spreadTupleUnionDistribution.ts, 24, 41))
>Union : Symbol(Union, Decl(spreadTupleUnionDistribution.ts, 24, 41))

// Expected: [string, string] | [number, number] | [string, number] | [number, string]

// Test that the types are actually correct by checking assignability
type Expected = [string, string] | [number, number] | [string, number] | [number, string];
>Expected : Symbol(Expected, Decl(spreadTupleUnionDistribution.ts, 24, 126))

// These should all be true (no error)
type Test1Check = Expected extends typeof test2 ? true : false;
>Test1Check : Symbol(Test1Check, Decl(spreadTupleUnionDistribution.ts, 28, 90))
>Expected : Symbol(Expected, Decl(spreadTupleUnionDistribution.ts, 24, 126))
>test2 : Symbol(test2, Decl(spreadTupleUnionDistribution.ts, 15, 3))

type Test2Check = typeof test2 extends Expected ? true : false;
>Test2Check : Symbol(Test2Check, Decl(spreadTupleUnionDistribution.ts, 31, 63))
>test2 : Symbol(test2, Decl(spreadTupleUnionDistribution.ts, 15, 3))
>Expected : Symbol(Expected, Decl(spreadTupleUnionDistribution.ts, 24, 126))

// If the bug is fixed, these will also be true (no error)
type Test3Check = Expected extends typeof test3 ? true : false;
>Test3Check : Symbol(Test3Check, Decl(spreadTupleUnionDistribution.ts, 32, 63))
>Expected : Symbol(Expected, Decl(spreadTupleUnionDistribution.ts, 24, 126))
>test3 : Symbol(test3, Decl(spreadTupleUnionDistribution.ts, 19, 3))

type Test4Check = typeof test3 extends Expected ? true : false;
>Test4Check : Symbol(Test4Check, Decl(spreadTupleUnionDistribution.ts, 35, 63))
>test3 : Symbol(test3, Decl(spreadTupleUnionDistribution.ts, 19, 3))
>Expected : Symbol(Expected, Decl(spreadTupleUnionDistribution.ts, 24, 126))

type Test5Check = Expected extends typeof test4 ? true : false;
>Test5Check : Symbol(Test5Check, Decl(spreadTupleUnionDistribution.ts, 36, 63))
>Expected : Symbol(Expected, Decl(spreadTupleUnionDistribution.ts, 24, 126))
>test4 : Symbol(test4, Decl(spreadTupleUnionDistribution.ts, 24, 3))

type Test6Check = typeof test4 extends Expected ? true : false;
>Test6Check : Symbol(Test6Check, Decl(spreadTupleUnionDistribution.ts, 38, 63))
>test4 : Symbol(test4, Decl(spreadTupleUnionDistribution.ts, 24, 3))
>Expected : Symbol(Expected, Decl(spreadTupleUnionDistribution.ts, 24, 126))

// Force an error if checks fail
const _check1: Test1Check = true;
>_check1 : Symbol(_check1, Decl(spreadTupleUnionDistribution.ts, 42, 5))
>Test1Check : Symbol(Test1Check, Decl(spreadTupleUnionDistribution.ts, 28, 90))

const _check2: Test2Check = true;
>_check2 : Symbol(_check2, Decl(spreadTupleUnionDistribution.ts, 43, 5))
>Test2Check : Symbol(Test2Check, Decl(spreadTupleUnionDistribution.ts, 31, 63))

const _check3: Test3Check = true; // This will error if bug exists
>_check3 : Symbol(_check3, Decl(spreadTupleUnionDistribution.ts, 44, 5))
>Test3Check : Symbol(Test3Check, Decl(spreadTupleUnionDistribution.ts, 32, 63))

const _check4: Test4Check = true; // This will error if bug exists
>_check4 : Symbol(_check4, Decl(spreadTupleUnionDistribution.ts, 45, 5))
>Test4Check : Symbol(Test4Check, Decl(spreadTupleUnionDistribution.ts, 35, 63))

const _check5: Test5Check = true;
>_check5 : Symbol(_check5, Decl(spreadTupleUnionDistribution.ts, 46, 5))
>Test5Check : Symbol(Test5Check, Decl(spreadTupleUnionDistribution.ts, 36, 63))

const _check6: Test6Check = true;
>_check6 : Symbol(_check6, Decl(spreadTupleUnionDistribution.ts, 47, 5))
>Test6Check : Symbol(Test6Check, Decl(spreadTupleUnionDistribution.ts, 38, 63))

151 changes: 151 additions & 0 deletions tests/baselines/reference/spreadTupleUnionDistribution.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
//// [tests/cases/conformance/types/spread/spreadTupleUnionDistribution.ts] ////

=== spreadTupleUnionDistribution.ts ===
// Repro from #62812
// Spread operator fails to distribute over union when recursive type call is inlined instead of aliased

type CrossProduct<Union, Counter extends unknown[]> =
>CrossProduct : CrossProduct<Union, Counter>
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Counter extends [infer Zero, ...infer Rest]
? (Union extends infer Member
? [Member, ...CrossProduct<Union, Rest>]
: never)
: [];

// Basic test - this works
let test1: CrossProduct<number | string, [undefined]>; // [string] | [number]
>test1 : [string] | [number]
> : ^^^^^^^^^^^^^^^^^^^

type Depth1 = CrossProduct<number | string, [undefined]> // [string] | [number]
>Depth1 : [string] | [number]
> : ^^^^^^^^^^^^^^^^^^^

// With alias - this should work and give full cross product
let test2: (number | string extends infer Union ? (Union extends unknown ? [Union, ...Depth1]: never) : never);
>test2 : [string, string] | [string, number] | [number, string] | [number, number]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

// Expected: [string, string] | [number, number] | [string, number] | [number, string]

// With inlined type - this should also work but currently doesn't distribute properly
let test3: (number | string extends infer Union ? (Union extends unknown ? [Union, ...CrossProduct<number | string, [undefined]>]: never) : never);
>test3 : [string, string] | [string, number] | [number, string] | [number, number]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

// Expected: [string, string] | [number, number] | [string, number] | [number, string]
// Actual (bug): [string, string] | [number, number]

// With literal union - this works
let test4: (number | string extends infer Union ? (Union extends unknown ? [Union, ...([string] | [number])]: never) : never);
>test4 : [string, string] | [string, number] | [number, string] | [number, number]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

// Expected: [string, string] | [number, number] | [string, number] | [number, string]

// Test that the types are actually correct by checking assignability
type Expected = [string, string] | [number, number] | [string, number] | [number, string];
>Expected : Expected
> : ^^^^^^^^

// These should all be true (no error)
type Test1Check = Expected extends typeof test2 ? true : false;
>Test1Check : true
> : ^^^^
>test2 : [string, string] | [string, number] | [number, string] | [number, number]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>true : true
> : ^^^^
>false : false
> : ^^^^^

type Test2Check = typeof test2 extends Expected ? true : false;
>Test2Check : true
> : ^^^^
>test2 : [string, string] | [string, number] | [number, string] | [number, number]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>true : true
> : ^^^^
>false : false
> : ^^^^^

// If the bug is fixed, these will also be true (no error)
type Test3Check = Expected extends typeof test3 ? true : false;
>Test3Check : true
> : ^^^^
>test3 : [string, string] | [string, number] | [number, string] | [number, number]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>true : true
> : ^^^^
>false : false
> : ^^^^^

type Test4Check = typeof test3 extends Expected ? true : false;
>Test4Check : true
> : ^^^^
>test3 : [string, string] | [string, number] | [number, string] | [number, number]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>true : true
> : ^^^^
>false : false
> : ^^^^^

type Test5Check = Expected extends typeof test4 ? true : false;
>Test5Check : true
> : ^^^^
>test4 : [string, string] | [string, number] | [number, string] | [number, number]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>true : true
> : ^^^^
>false : false
> : ^^^^^

type Test6Check = typeof test4 extends Expected ? true : false;
>Test6Check : true
> : ^^^^
>test4 : [string, string] | [string, number] | [number, string] | [number, number]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>true : true
> : ^^^^
>false : false
> : ^^^^^

// Force an error if checks fail
const _check1: Test1Check = true;
>_check1 : true
> : ^^^^
>true : true
> : ^^^^

const _check2: Test2Check = true;
>_check2 : true
> : ^^^^
>true : true
> : ^^^^

const _check3: Test3Check = true; // This will error if bug exists
>_check3 : true
> : ^^^^
>true : true
> : ^^^^

const _check4: Test4Check = true; // This will error if bug exists
>_check4 : true
> : ^^^^
>true : true
> : ^^^^

const _check5: Test5Check = true;
>_check5 : true
> : ^^^^
>true : true
> : ^^^^

const _check6: Test6Check = true;
>_check6 : true
> : ^^^^
>true : true
> : ^^^^

Loading