Skip to content

Commit 44f4e27

Browse files
authored
Constraints for generic tuple types (#53672)
1 parent 94564cf commit 44f4e27

File tree

7 files changed

+610
-401
lines changed

7 files changed

+610
-401
lines changed

src/compiler/checker.ts

+40-15
Original file line numberDiff line numberDiff line change
@@ -10409,7 +10409,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1040910409
// If the parent is a tuple type, the rest element has a tuple type of the
1041010410
// remaining tuple element types. Otherwise, the rest element has an array type with same
1041110411
// element type as the parent type.
10412-
const baseConstraint = getBaseConstraintOrType(parentType);
10412+
const baseConstraint = mapType(parentType, t => t.flags & TypeFlags.InstantiableNonPrimitive ? getBaseConstraintOrType(t) : t);
1041310413
type = everyType(baseConstraint, isTupleType) ?
1041410414
mapType(baseConstraint, t => sliceTupleType(t as TupleTypeReference, index)) :
1041510415
createArrayType(elementType);
@@ -12556,6 +12556,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1255612556
return needApparentType ? getApparentType(type) : type;
1255712557
}
1255812558

12559+
function getThisArgument(type: Type) {
12560+
return getObjectFlags(type) & ObjectFlags.Reference && length(getTypeArguments(type as TypeReference)) > getTypeReferenceArity(type as TypeReference) ? last(getTypeArguments(type as TypeReference)) : type;
12561+
}
12562+
1255912563
function resolveObjectTypeMembers(type: ObjectType, source: InterfaceTypeWithDeclaredMembers, typeParameters: readonly TypeParameter[], typeArguments: readonly Type[]) {
1256012564
let mapper: TypeMapper | undefined;
1256112565
let members: SymbolTable;
@@ -12685,7 +12689,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1268512689
return [sig.parameters];
1268612690

1268712691
function expandSignatureParametersWithTupleMembers(restType: TupleTypeReference, restIndex: number) {
12688-
const elementTypes = getTypeArguments(restType);
12692+
const elementTypes = getElementTypes(restType);
1268912693
const associatedNames = getUniqAssociatedNamesFromTupleType(restType);
1269012694
const restParams = map(elementTypes, (t, i) => {
1269112695
// Lookup the label from the individual tuple passed in before falling back to the signature `rest` parameter name
@@ -13583,7 +13587,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1358313587
type.flags & TypeFlags.IndexedAccess && isConstTypeVariable((type as IndexedAccessType).objectType) ||
1358413588
type.flags & TypeFlags.Conditional && isConstTypeVariable(getConstraintOfConditionalType(type as ConditionalType)) ||
1358513589
type.flags & TypeFlags.Substitution && isConstTypeVariable((type as SubstitutionType).baseType) ||
13586-
isGenericTupleType(type) && findIndex(getTypeArguments(type), (t, i) => !!(type.target.elementFlags[i] & ElementFlags.Variadic) && isConstTypeVariable(t)) >= 0));
13590+
isGenericTupleType(type) && findIndex(getElementTypes(type), (t, i) => !!(type.target.elementFlags[i] & ElementFlags.Variadic) && isConstTypeVariable(t)) >= 0));
1358713591
}
1358813592

1358913593
function getConstraintOfIndexedAccess(type: IndexedAccessType) {
@@ -13708,7 +13712,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1370813712
}
1370913713

1371013714
function getBaseConstraintOfType(type: Type): Type | undefined {
13711-
if (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral | TypeFlags.StringMapping)) {
13715+
if (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) || isGenericTupleType(type)) {
1371213716
const constraint = getResolvedBaseConstraint(type as InstantiableType | UnionOrIntersectionType);
1371313717
return constraint !== noConstraintType && constraint !== circularConstraintType ? constraint : undefined;
1371413718
}
@@ -13737,7 +13741,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1373713741
return type.resolvedBaseConstraint;
1373813742
}
1373913743
const stack: object[] = [];
13740-
return type.resolvedBaseConstraint = getTypeWithThisArgument(getImmediateBaseConstraint(type), type);
13744+
return type.resolvedBaseConstraint = getTypeWithThisArgument(getImmediateBaseConstraint(type), getThisArgument(type));
1374113745

1374213746
function getImmediateBaseConstraint(t: Type): Type {
1374313747
if (!t.immediateBaseConstraint) {
@@ -13839,6 +13843,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1383913843
if (t.flags & TypeFlags.Substitution) {
1384013844
return getBaseConstraint(getSubstitutionIntersection(t as SubstitutionType));
1384113845
}
13846+
if (isGenericTupleType(t)) {
13847+
// We substitute constraints for variadic elements only when the constraints are array types or
13848+
// non-variadic tuple types as we want to avoid further (possibly unbounded) recursion.
13849+
const newElements = map(getElementTypes(t), (v, i) => {
13850+
const constraint = t.target.elementFlags[i] & ElementFlags.Variadic && getBaseConstraint(v) || v;
13851+
return constraint && everyType(constraint, c => isArrayOrTupleType(c) && !isGenericTupleType(c)) ? constraint : v;
13852+
});
13853+
return createTupleType(newElements, t.target.elementFlags, t.target.readonly, t.target.labeledElementDeclarations);
13854+
}
1384213855
return t;
1384313856
}
1384413857
}
@@ -16147,7 +16160,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1614716160
addElement(type, ElementFlags.Variadic, target.labeledElementDeclarations?.[i]);
1614816161
}
1614916162
else if (isTupleType(type)) {
16150-
const elements = getTypeArguments(type);
16163+
const elements = getElementTypes(type);
1615116164
if (elements.length + expandedTypes.length >= 10_000) {
1615216165
error(currentNode, isPartOfTypeNode(currentNode!)
1615316166
? Diagnostics.Type_produces_a_tuple_type_that_is_too_large_to_represent
@@ -16229,6 +16242,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1622916242
return type.elementFlags.length - findLastIndex(type.elementFlags, f => !(f & flags)) - 1;
1623016243
}
1623116244

16245+
function getElementTypes(type: TupleTypeReference): readonly Type[] {
16246+
const typeArguments = getTypeArguments(type);
16247+
const arity = getTypeReferenceArity(type);
16248+
return typeArguments.length === arity ? typeArguments : typeArguments.slice(0, arity);
16249+
}
16250+
1623216251
function getTypeFromOptionalTypeNode(node: OptionalTypeNode): Type {
1623316252
return addOptionality(getTypeFromTypeNode(node.type), /*isProperty*/ true);
1623416253
}
@@ -17779,7 +17798,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1777917798
}
1778017799

1778117800
function isDeferredType(type: Type, checkTuples: boolean) {
17782-
return isGenericType(type) || checkTuples && isTupleType(type) && some(getTypeArguments(type), isGenericType);
17801+
return isGenericType(type) || checkTuples && isTupleType(type) && some(getElementTypes(type), isGenericType);
1778317802
}
1778417803

1778517804
function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type {
@@ -18920,7 +18939,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1892018939
// M<[A, B?, ...T, ...C[]] into [...M<[A]>, ...M<[B?]>, ...M<T>, ...M<C[]>] and then rely on tuple type
1892118940
// normalization to resolve the non-generic parts of the resulting tuple.
1892218941
const elementFlags = tupleType.target.elementFlags;
18923-
const elementTypes = map(getTypeArguments(tupleType), (t, i) => {
18942+
const elementTypes = map(getElementTypes(tupleType), (t, i) => {
1892418943
const singleton = elementFlags[i] & ElementFlags.Variadic ? t :
1892518944
elementFlags[i] & ElementFlags.Rest ? createArrayType(t) :
1892618945
createTupleType([t], [elementFlags[i]]);
@@ -18939,7 +18958,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1893918958

1894018959
function instantiateMappedTupleType(tupleType: TupleTypeReference, mappedType: MappedType, mapper: TypeMapper) {
1894118960
const elementFlags = tupleType.target.elementFlags;
18942-
const elementTypes = map(getTypeArguments(tupleType), (_, i) =>
18961+
const elementTypes = map(getElementTypes(tupleType), (_, i) =>
1894318962
instantiateMappedTypeTemplate(mappedType, getStringLiteralType("" + i), !!(elementFlags[i] & ElementFlags.Optional), mapper));
1894418963
const modifiers = getMappedTypeModifiers(mappedType);
1894518964
const newTupleModifiers = modifiers & MappedTypeModifiers.IncludeOptional ? map(elementFlags, f => f & ElementFlags.Required ? ElementFlags.Optional : f) :
@@ -20244,7 +20263,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2024420263
}
2024520264

2024620265
function getNormalizedTupleType(type: TupleTypeReference, writing: boolean): Type {
20247-
const elements = getTypeArguments(type);
20266+
const elements = getElementTypes(type);
2024820267
const normalizedElements = sameMap(elements, t => t.flags & TypeFlags.Simplifiable ? getSimplifiedType(t, writing) : t);
2024920268
return elements !== normalizedElements ? createNormalizedTupleType(type.target, normalizedElements) : type;
2025020269
}
@@ -21802,6 +21821,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2180221821
return Ternary.False;
2180321822
}
2180421823
}
21824+
else if (isGenericTupleType(source) && isTupleType(target) && !isGenericTupleType(target)) {
21825+
const constraint = getBaseConstraintOrType(source);
21826+
if (constraint !== source) {
21827+
return isRelatedTo(constraint, target, RecursionFlags.Source, reportErrors);
21828+
}
21829+
}
2180521830
// A fresh empty object type is never a subtype of a non-empty object type. This ensures fresh({}) <: { [x: string]: xxx }
2180621831
// but not vice-versa. Without this rule, those types would be mutual subtypes.
2180721832
else if ((relation === subtypeRelation || relation === strictSubtypeRelation) && isEmptyObjectType(target) && getObjectFlags(target) & ObjectFlags.FreshLiteral && !isEmptyObjectType(source)) {
@@ -24083,7 +24108,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2408324108
function isPartiallyInferableType(type: Type): boolean {
2408424109
return !(getObjectFlags(type) & ObjectFlags.NonInferrableType) ||
2408524110
isObjectLiteralType(type) && some(getPropertiesOfType(type), prop => isPartiallyInferableType(getTypeOfSymbol(prop))) ||
24086-
isTupleType(type) && some(getTypeArguments(type), isPartiallyInferableType);
24111+
isTupleType(type) && some(getElementTypes(type), isPartiallyInferableType);
2408724112
}
2408824113

2408924114
function createReverseMappedType(source: Type, target: MappedType, constraint: IndexType) {
@@ -24098,7 +24123,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2409824123
return createArrayType(inferReverseMappedType(getTypeArguments(source)[0], target, constraint), isReadonlyArrayType(source));
2409924124
}
2410024125
if (isTupleType(source)) {
24101-
const elementTypes = map(getTypeArguments(source), t => inferReverseMappedType(t, target, constraint));
24126+
const elementTypes = map(getElementTypes(source), t => inferReverseMappedType(t, target, constraint));
2410224127
const elementFlags = getMappedTypeModifiers(target) & MappedTypeModifiers.IncludeOptional ?
2410324128
sameMap(source.target.elementFlags, f => f & ElementFlags.Optional ? ElementFlags.Required : f) :
2410424129
source.target.elementFlags;
@@ -32491,7 +32516,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3249132516
function getMutableArrayOrTupleType(type: Type) {
3249232517
return type.flags & TypeFlags.Union ? mapType(type, getMutableArrayOrTupleType) :
3249332518
type.flags & TypeFlags.Any || isMutableArrayOrTuple(getBaseConstraintOfType(type) || type) ? type :
32494-
isTupleType(type) ? createTupleType(getTypeArguments(type), type.target.elementFlags, /*readonly*/ false, type.target.labeledElementDeclarations) :
32519+
isTupleType(type) ? createTupleType(getElementTypes(type), type.target.elementFlags, /*readonly*/ false, type.target.labeledElementDeclarations) :
3249532520
createTupleType([type], [ElementFlags.Variadic]);
3249632521
}
3249732522

@@ -32825,7 +32850,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3282532850
// We can call checkExpressionCached because spread expressions never have a contextual type.
3282632851
const spreadType = arg.kind === SyntaxKind.SpreadElement && (flowLoopCount ? checkExpression((arg as SpreadElement).expression) : checkExpressionCached((arg as SpreadElement).expression));
3282732852
if (spreadType && isTupleType(spreadType)) {
32828-
forEach(getTypeArguments(spreadType), (t, i) => {
32853+
forEach(getElementTypes(spreadType), (t, i) => {
3282932854
const flags = spreadType.target.elementFlags[i];
3283032855
const syntheticArg = createSyntheticExpression(arg, flags & ElementFlags.Rest ? createArrayType(t) : t,
3283132856
!!(flags & ElementFlags.Variable), spreadType.target.labeledElementDeclarations?.[i]);
@@ -37436,7 +37461,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3743637461

3743737462
function padTupleType(type: TupleTypeReference, pattern: ArrayBindingPattern) {
3743837463
const patternElements = pattern.elements;
37439-
const elementTypes = getTypeArguments(type).slice();
37464+
const elementTypes = getElementTypes(type).slice();
3744037465
const elementFlags = type.target.elementFlags.slice();
3744137466
for (let i = getTypeReferenceArity(type); i < patternElements.length; i++) {
3744237467
const e = patternElements[i];

tests/baselines/reference/variadicTuples1.errors.txt

+38-3
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,20 @@ tests/cases/conformance/types/tuple/variadicTuples1.ts(191,5): error TS2322: Typ
3535
'T' is assignable to the constraint of type 'U', but 'U' could be instantiated with a different subtype of constraint 'readonly string[]'.
3636
tests/cases/conformance/types/tuple/variadicTuples1.ts(203,5): error TS2322: Type 'string' is not assignable to type 'keyof [1, 2, ...T]'.
3737
Type '"2"' is not assignable to type '"0" | "1" | keyof T[]'.
38-
tests/cases/conformance/types/tuple/variadicTuples1.ts(357,26): error TS2322: Type 'string' is not assignable to type 'number'.
39-
tests/cases/conformance/types/tuple/variadicTuples1.ts(397,7): error TS2322: Type '[boolean, false]' is not assignable to type '[...number[], boolean]'.
38+
tests/cases/conformance/types/tuple/variadicTuples1.ts(213,5): error TS2322: Type '[...T, ...T]' is not assignable to type '[unknown, unknown]'.
39+
Type '[] | [unknown] | [unknown, unknown]' is not assignable to type '[unknown, unknown]'.
40+
Type '[]' is not assignable to type '[unknown, unknown]'.
41+
Source has 0 element(s) but target requires 2.
42+
tests/cases/conformance/types/tuple/variadicTuples1.ts(217,5): error TS2322: Type '[...T, ...T]' is not assignable to type '[unknown, unknown]'.
43+
Type 'unknown[]' is not assignable to type '[unknown, unknown]'.
44+
Target requires 2 element(s) but source may have fewer.
45+
tests/cases/conformance/types/tuple/variadicTuples1.ts(371,26): error TS2322: Type 'string' is not assignable to type 'number'.
46+
tests/cases/conformance/types/tuple/variadicTuples1.ts(411,7): error TS2322: Type '[boolean, false]' is not assignable to type '[...number[], boolean]'.
4047
Type at position 0 in source is not compatible with type at position 0 in target.
4148
Type 'boolean' is not assignable to type 'number'.
4249

4350

44-
==== tests/cases/conformance/types/tuple/variadicTuples1.ts (20 errors) ====
51+
==== tests/cases/conformance/types/tuple/variadicTuples1.ts (22 errors) ====
4552
// Variadics in tuple types
4653

4754
type TV0<T extends unknown[]> = [string, ...T];
@@ -303,6 +310,29 @@ tests/cases/conformance/types/tuple/variadicTuples1.ts(397,7): error TS2322: Typ
303310
!!! error TS2322: Type '"2"' is not assignable to type '"0" | "1" | keyof T[]'.
304311
}
305312

313+
// Constraints of variadic tuple types
314+
315+
function ft16<T extends [unknown]>(x: [unknown, unknown], y: [...T, ...T]) {
316+
x = y;
317+
}
318+
319+
function ft17<T extends [] | [unknown]>(x: [unknown, unknown], y: [...T, ...T]) {
320+
x = y;
321+
~
322+
!!! error TS2322: Type '[...T, ...T]' is not assignable to type '[unknown, unknown]'.
323+
!!! error TS2322: Type '[] | [unknown] | [unknown, unknown]' is not assignable to type '[unknown, unknown]'.
324+
!!! error TS2322: Type '[]' is not assignable to type '[unknown, unknown]'.
325+
!!! error TS2322: Source has 0 element(s) but target requires 2.
326+
}
327+
328+
function ft18<T extends unknown[]>(x: [unknown, unknown], y: [...T, ...T]) {
329+
x = y;
330+
~
331+
!!! error TS2322: Type '[...T, ...T]' is not assignable to type '[unknown, unknown]'.
332+
!!! error TS2322: Type 'unknown[]' is not assignable to type '[unknown, unknown]'.
333+
!!! error TS2322: Target requires 2 element(s) but source may have fewer.
334+
}
335+
306336
// Inference between variadic tuple types
307337

308338
type First<T extends readonly unknown[]> =
@@ -505,4 +535,9 @@ tests/cases/conformance/types/tuple/variadicTuples1.ts(397,7): error TS2322: Typ
505535
type U1 = [string, ...Numbers, boolean];
506536
type U2 = [...[string, ...Numbers], boolean];
507537
type U3 = [...[string, number], boolean];
538+
539+
// Repro from #53563
540+
541+
type ToStringLength1<T extends any[]> = `${T['length']}`;
542+
type ToStringLength2<T extends any[]> = `${[...T]['length']}`;
508543

0 commit comments

Comments
 (0)