Skip to content

Commit ee4aee0

Browse files
authored
Handle 'keyof' for generic tuple types (#39218)
* Handle keyof T where T is generic tuple type * Add tests * Accept new baselines * Address CR feedback * Accept new baselines
1 parent 8df85b5 commit ee4aee0

File tree

6 files changed

+693
-276
lines changed

6 files changed

+693
-276
lines changed

src/compiler/checker.ts

+33-23
Original file line numberDiff line numberDiff line change
@@ -10195,7 +10195,8 @@ namespace ts {
1019510195
return type;
1019610196
}
1019710197
if (type.flags & TypeFlags.Index) {
10198-
return getIndexType(getApparentType((<IndexType>type).type));
10198+
const t = getApparentType((<IndexType>type).type);
10199+
return isGenericTupleType(t) ? getKnownKeysOfTupleType(t) : getIndexType(t);
1019910200
}
1020010201
if (type.flags & TypeFlags.Conditional) {
1020110202
if ((<ConditionalType>type).root.isDistributive) {
@@ -10520,9 +10521,6 @@ namespace ts {
1052010521
return indexedAccess;
1052110522
}
1052210523
}
10523-
if (isGenericTupleType(type.objectType)) {
10524-
return getIndexTypeOfType(type.objectType, IndexKind.Number);
10525-
}
1052610524
const objectConstraint = getSimplifiedTypeOrConstraint(type.objectType);
1052710525
if (objectConstraint && objectConstraint !== type.objectType) {
1052810526
return getIndexedAccessTypeOrUndefined(objectConstraint, type.indexType);
@@ -10711,9 +10709,6 @@ namespace ts {
1071110709
return keyofConstraintType;
1071210710
}
1071310711
if (t.flags & TypeFlags.IndexedAccess) {
10714-
if (isGenericTupleType((<IndexedAccessType>t).objectType)) {
10715-
return getIndexTypeOfType((<IndexedAccessType>t).objectType, IndexKind.Number);
10716-
}
1071710712
const baseObjectType = getBaseConstraint((<IndexedAccessType>t).objectType);
1071810713
const baseIndexType = getBaseConstraint((<IndexedAccessType>t).indexType);
1071910714
const baseIndexedAccess = baseObjectType && baseIndexType && getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType);
@@ -12647,6 +12642,11 @@ namespace ts {
1264712642
/*readonly*/ false, target.labeledElementDeclarations && target.labeledElementDeclarations.slice(index, endIndex));
1264812643
}
1264912644

12645+
function getKnownKeysOfTupleType(type: TupleTypeReference) {
12646+
return getUnionType(append(arrayOf(type.target.fixedLength, i => getLiteralType("" + i)),
12647+
getIndexType(type.target.readonly ? globalReadonlyArrayType : globalArrayType)));
12648+
}
12649+
1265012650
function getTypeFromOptionalTypeNode(node: OptionalTypeNode): Type {
1265112651
const type = getTypeFromTypeNode(node.type);
1265212652
return strictNullChecks ? getOptionalType(type) : type;
@@ -16831,11 +16831,11 @@ namespace ts {
1683116831
}
1683216832
}
1683316833

16834-
// For a generic type T, [...T] is assignable to T, T is assignable to readonly [...T], and T is assignable
16835-
// to [...T] when T is constrained to a mutable array or tuple type.
16836-
if (isSingleElementGenericTupleType(source) && getTypeArguments(source)[0] === target && !source.target.readonly ||
16837-
isSingleElementGenericTupleType(target) && getTypeArguments(target)[0] === source && (target.target.readonly || isMutableArrayOrTuple(getBaseConstraintOfType(source) || source))) {
16838-
return Ternary.True;
16834+
// For a generic type T and a type U that is assignable to T, [...U] is assignable to T, U is assignable to readonly [...T],
16835+
// and U is assignable to [...T] when U is constrained to a mutable array or tuple type.
16836+
if (isSingleElementGenericTupleType(source) && !source.target.readonly && (result = isRelatedTo(getTypeArguments(source)[0], target)) ||
16837+
isSingleElementGenericTupleType(target) && (target.target.readonly || isMutableArrayOrTuple(getBaseConstraintOfType(source) || source)) && (result = isRelatedTo(source, getTypeArguments(target)[0]))) {
16838+
return result;
1683916839
}
1684016840

1684116841
if (target.flags & TypeFlags.TypeParameter) {
@@ -16851,22 +16851,32 @@ namespace ts {
1685116851
}
1685216852
}
1685316853
else if (target.flags & TypeFlags.Index) {
16854+
const targetType = (target as IndexType).type;
1685416855
// A keyof S is related to a keyof T if T is related to S.
1685516856
if (source.flags & TypeFlags.Index) {
16856-
if (result = isRelatedTo((<IndexType>target).type, (<IndexType>source).type, /*reportErrors*/ false)) {
16857+
if (result = isRelatedTo(targetType, (<IndexType>source).type, /*reportErrors*/ false)) {
1685716858
return result;
1685816859
}
1685916860
}
16860-
// A type S is assignable to keyof T if S is assignable to keyof C, where C is the
16861-
// simplified form of T or, if T doesn't simplify, the constraint of T.
16862-
const constraint = getSimplifiedTypeOrConstraint((<IndexType>target).type);
16863-
if (constraint) {
16864-
// We require Ternary.True here such that circular constraints don't cause
16865-
// false positives. For example, given 'T extends { [K in keyof T]: string }',
16866-
// 'keyof T' has itself as its constraint and produces a Ternary.Maybe when
16867-
// related to other types.
16868-
if (isRelatedTo(source, getIndexType(constraint, (target as IndexType).stringsOnly), reportErrors) === Ternary.True) {
16869-
return Ternary.True;
16861+
if (isTupleType(targetType)) {
16862+
// An index type can have a tuple type target when the tuple type contains variadic elements.
16863+
// Check if the source is related to the known keys of the tuple type.
16864+
if (result = isRelatedTo(source, getKnownKeysOfTupleType(targetType), reportErrors)) {
16865+
return result;
16866+
}
16867+
}
16868+
else {
16869+
// A type S is assignable to keyof T if S is assignable to keyof C, where C is the
16870+
// simplified form of T or, if T doesn't simplify, the constraint of T.
16871+
const constraint = getSimplifiedTypeOrConstraint(targetType);
16872+
if (constraint) {
16873+
// We require Ternary.True here such that circular constraints don't cause
16874+
// false positives. For example, given 'T extends { [K in keyof T]: string }',
16875+
// 'keyof T' has itself as its constraint and produces a Ternary.Maybe when
16876+
// related to other types.
16877+
if (isRelatedTo(source, getIndexType(constraint, (target as IndexType).stringsOnly), reportErrors) === Ternary.True) {
16878+
return Ternary.True;
16879+
}
1687016880
}
1687116881
}
1687216882
}

tests/baselines/reference/variadicTuples1.errors.txt

+76-2
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,29 @@ tests/cases/conformance/types/tuple/variadicTuples1.ts(169,5): error TS2322: Typ
2020
tests/cases/conformance/types/tuple/variadicTuples1.ts(170,5): error TS2322: Type 'T' is not assignable to type '[...T]'.
2121
The type 'readonly unknown[]' is 'readonly' and cannot be assigned to the mutable type '[...T]'.
2222
tests/cases/conformance/types/tuple/variadicTuples1.ts(171,5): error TS4104: The type 'readonly [...T]' is 'readonly' and cannot be assigned to the mutable type '[...T]'.
23-
tests/cases/conformance/types/tuple/variadicTuples1.ts(303,14): error TS7019: Rest parameter 'x' implicitly has an 'any[]' type.
23+
tests/cases/conformance/types/tuple/variadicTuples1.ts(181,5): error TS2322: Type 'T' is not assignable to type '[...U]'.
24+
Type 'string[]' is not assignable to type '[...U]'.
25+
Target requires 1 element(s) but source may have fewer.
26+
tests/cases/conformance/types/tuple/variadicTuples1.ts(182,5): error TS2322: Type '[...T]' is not assignable to type '[...U]'.
27+
Type 'T' is not assignable to type 'U'.
28+
'T' is assignable to the constraint of type 'U', but 'U' could be instantiated with a different subtype of constraint 'string[]'.
29+
Type 'string[]' is not assignable to type 'U'.
30+
'U' could be instantiated with an arbitrary type which could be unrelated to 'string[]'.
31+
tests/cases/conformance/types/tuple/variadicTuples1.ts(188,5): error TS2322: Type 'T' is not assignable to type '[...T]'.
32+
The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type '[...T]'.
33+
tests/cases/conformance/types/tuple/variadicTuples1.ts(190,5): error TS2322: Type 'T' is not assignable to type '[...U]'.
34+
The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type '[...U]'.
35+
tests/cases/conformance/types/tuple/variadicTuples1.ts(191,5): error TS2322: Type '[...T]' is not assignable to type '[...U]'.
36+
Type 'T' is not assignable to type 'U'.
37+
'T' is assignable to the constraint of type 'U', but 'U' could be instantiated with a different subtype of constraint 'readonly string[]'.
38+
Type 'readonly string[]' is not assignable to type 'U'.
39+
'U' could be instantiated with an arbitrary type which could be unrelated to 'readonly string[]'.
40+
tests/cases/conformance/types/tuple/variadicTuples1.ts(203,5): error TS2322: Type 'string' is not assignable to type 'keyof [1, 2, ...T]'.
41+
Type '"2"' is not assignable to type 'number | "0" | "length" | "toString" | "toLocaleString" | "pop" | "push" | "concat" | "join" | "reverse" | "shift" | "slice" | "sort" | "splice" | "unshift" | "indexOf" | "lastIndexOf" | "every" | "some" | "forEach" | "map" | "filter" | "reduce" | "reduceRight" | "1"'.
42+
tests/cases/conformance/types/tuple/variadicTuples1.ts(333,14): error TS7019: Rest parameter 'x' implicitly has an 'any[]' type.
2443

2544

26-
==== tests/cases/conformance/types/tuple/variadicTuples1.ts (13 errors) ====
45+
==== tests/cases/conformance/types/tuple/variadicTuples1.ts (19 errors) ====
2746
// Variadics in tuple types
2847

2948
type TV0<T extends unknown[]> = [string, ...T];
@@ -234,6 +253,61 @@ tests/cases/conformance/types/tuple/variadicTuples1.ts(303,14): error TS7019: Re
234253
r = m;
235254
}
236255

256+
function f13<T extends string[], U extends T>(t0: T, t1: [...T], t2: [...U]) {
257+
t0 = t1;
258+
t0 = t2;
259+
t1 = t0;
260+
t1 = t2;
261+
t2 = t0; // Error
262+
~~
263+
!!! error TS2322: Type 'T' is not assignable to type '[...U]'.
264+
!!! error TS2322: Type 'string[]' is not assignable to type '[...U]'.
265+
!!! error TS2322: Target requires 1 element(s) but source may have fewer.
266+
t2 = t1; // Error
267+
~~
268+
!!! error TS2322: Type '[...T]' is not assignable to type '[...U]'.
269+
!!! error TS2322: Type 'T' is not assignable to type 'U'.
270+
!!! error TS2322: 'T' is assignable to the constraint of type 'U', but 'U' could be instantiated with a different subtype of constraint 'string[]'.
271+
!!! error TS2322: Type 'string[]' is not assignable to type 'U'.
272+
!!! error TS2322: 'U' could be instantiated with an arbitrary type which could be unrelated to 'string[]'.
273+
}
274+
275+
function f14<T extends readonly string[], U extends T>(t0: T, t1: [...T], t2: [...U]) {
276+
t0 = t1;
277+
t0 = t2;
278+
t1 = t0; // Error
279+
~~
280+
!!! error TS2322: Type 'T' is not assignable to type '[...T]'.
281+
!!! error TS2322: The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type '[...T]'.
282+
t1 = t2;
283+
t2 = t0; // Error
284+
~~
285+
!!! error TS2322: Type 'T' is not assignable to type '[...U]'.
286+
!!! error TS2322: The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type '[...U]'.
287+
t2 = t1; // Error
288+
~~
289+
!!! error TS2322: Type '[...T]' is not assignable to type '[...U]'.
290+
!!! error TS2322: Type 'T' is not assignable to type 'U'.
291+
!!! error TS2322: 'T' is assignable to the constraint of type 'U', but 'U' could be instantiated with a different subtype of constraint 'readonly string[]'.
292+
!!! error TS2322: Type 'readonly string[]' is not assignable to type 'U'.
293+
!!! error TS2322: 'U' could be instantiated with an arbitrary type which could be unrelated to 'readonly string[]'.
294+
}
295+
296+
function f15<T extends string[], U extends T>(k0: keyof T, k1: keyof [...T], k2: keyof [...U], k3: keyof [1, 2, ...T]) {
297+
k0 = 'length';
298+
k1 = 'length';
299+
k2 = 'length';
300+
k0 = 'slice';
301+
k1 = 'slice';
302+
k2 = 'slice';
303+
k3 = '0';
304+
k3 = '1';
305+
k3 = '2'; // Error
306+
~~
307+
!!! error TS2322: Type 'string' is not assignable to type 'keyof [1, 2, ...T]'.
308+
!!! error TS2322: Type '"2"' is not assignable to type 'number | "0" | "length" | "toString" | "toLocaleString" | "pop" | "push" | "concat" | "join" | "reverse" | "shift" | "slice" | "sort" | "splice" | "unshift" | "indexOf" | "lastIndexOf" | "every" | "some" | "forEach" | "map" | "filter" | "reduce" | "reduceRight" | "1"'.
309+
}
310+
237311
// Inference between variadic tuple types
238312

239313
type First<T extends readonly unknown[]> = T[0];

tests/baselines/reference/variadicTuples1.js

+60
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,36 @@ function f12<T extends readonly unknown[]>(t: T, m: [...T], r: readonly [...T])
174174
r = m;
175175
}
176176

177+
function f13<T extends string[], U extends T>(t0: T, t1: [...T], t2: [...U]) {
178+
t0 = t1;
179+
t0 = t2;
180+
t1 = t0;
181+
t1 = t2;
182+
t2 = t0; // Error
183+
t2 = t1; // Error
184+
}
185+
186+
function f14<T extends readonly string[], U extends T>(t0: T, t1: [...T], t2: [...U]) {
187+
t0 = t1;
188+
t0 = t2;
189+
t1 = t0; // Error
190+
t1 = t2;
191+
t2 = t0; // Error
192+
t2 = t1; // Error
193+
}
194+
195+
function f15<T extends string[], U extends T>(k0: keyof T, k1: keyof [...T], k2: keyof [...U], k3: keyof [1, 2, ...T]) {
196+
k0 = 'length';
197+
k1 = 'length';
198+
k2 = 'length';
199+
k0 = 'slice';
200+
k1 = 'slice';
201+
k2 = 'slice';
202+
k3 = '0';
203+
k3 = '1';
204+
k3 = '2'; // Error
205+
}
206+
177207
// Inference between variadic tuple types
178208

179209
type First<T extends readonly unknown[]> = T[0];
@@ -414,6 +444,33 @@ function f12(t, m, r) {
414444
r = t;
415445
r = m;
416446
}
447+
function f13(t0, t1, t2) {
448+
t0 = t1;
449+
t0 = t2;
450+
t1 = t0;
451+
t1 = t2;
452+
t2 = t0; // Error
453+
t2 = t1; // Error
454+
}
455+
function f14(t0, t1, t2) {
456+
t0 = t1;
457+
t0 = t2;
458+
t1 = t0; // Error
459+
t1 = t2;
460+
t2 = t0; // Error
461+
t2 = t1; // Error
462+
}
463+
function f15(k0, k1, k2, k3) {
464+
k0 = 'length';
465+
k1 = 'length';
466+
k2 = 'length';
467+
k0 = 'slice';
468+
k1 = 'slice';
469+
k2 = 'slice';
470+
k3 = '0';
471+
k3 = '1';
472+
k3 = '2'; // Error
473+
}
417474
// Inference to [...T, ...U] with implied arity for T
418475
function curry(f) {
419476
var a = [];
@@ -522,6 +579,9 @@ declare function gx2<U extends unknown[], V extends readonly unknown[]>(u: U, v:
522579
declare function f10<T extends string[], U extends T>(x: [string, ...unknown[]], y: [string, ...T], z: [string, ...U]): void;
523580
declare function f11<T extends unknown[]>(t: T, m: [...T], r: readonly [...T]): void;
524581
declare function f12<T extends readonly unknown[]>(t: T, m: [...T], r: readonly [...T]): void;
582+
declare function f13<T extends string[], U extends T>(t0: T, t1: [...T], t2: [...U]): void;
583+
declare function f14<T extends readonly string[], U extends T>(t0: T, t1: [...T], t2: [...U]): void;
584+
declare function f15<T extends string[], U extends T>(k0: keyof T, k1: keyof [...T], k2: keyof [...U], k3: keyof [1, 2, ...T]): void;
525585
declare type First<T extends readonly unknown[]> = T[0];
526586
declare type DropFirst<T extends readonly unknown[]> = T extends readonly [any, ...infer U] ? U : [...T];
527587
declare type Last<T extends readonly unknown[]> = T extends readonly [...infer _, infer U] ? U : undefined;

0 commit comments

Comments
 (0)