Skip to content

Commit 5a126e2

Browse files
authored
Merge pull request microsoft#27587 from Microsoft/fixUnionOfTupleIndexing
Fix indexing and destructuring of unions of tuple types
2 parents 6365eb0 + b040ea6 commit 5a126e2

40 files changed

+2167
-1437
lines changed

src/compiler/checker.ts

Lines changed: 32 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4705,14 +4705,14 @@ namespace ts {
47054705
// If the parent is a tuple type, the rest element has a tuple type of the
47064706
// remaining tuple element types. Otherwise, the rest element has an array type with same
47074707
// element type as the parent type.
4708-
type = isTupleType(parentType) ?
4709-
sliceTupleType(parentType, index) :
4708+
type = everyType(parentType, isTupleType) ?
4709+
mapType(parentType, t => sliceTupleType(<TupleTypeReference>t, index)) :
47104710
createArrayType(elementType);
47114711
}
47124712
else {
47134713
// Use specific property type when parent is a tuple or numeric index type when parent is an array
47144714
const index = pattern.elements.indexOf(declaration);
4715-
type = isTupleLikeType(parentType) ?
4715+
type = everyType(parentType, isTupleLikeType) ?
47164716
getTupleElementType(parentType, index) || declaration.initializer && checkDeclarationInitializer(declaration) :
47174717
elementType;
47184718
if (!type) {
@@ -4728,11 +4728,11 @@ namespace ts {
47284728
}
47294729
// In strict null checking mode, if a default value of a non-undefined type is specified, remove
47304730
// undefined from the final type.
4731-
if (strictNullChecks && declaration.initializer && !(getFalsyFlags(checkExpressionCached(declaration.initializer)) & TypeFlags.Undefined)) {
4731+
if (strictNullChecks && declaration.initializer && !(getFalsyFlags(checkDeclarationInitializer(declaration)) & TypeFlags.Undefined)) {
47324732
type = getTypeWithFacts(type, TypeFacts.NEUndefined);
47334733
}
47344734
return declaration.initializer && !getEffectiveTypeAnnotationNode(walkUpBindingElementsAndPatterns(declaration)) ?
4735-
getUnionType([type, checkExpressionCached(declaration.initializer)], UnionReduction.Subtype) :
4735+
getUnionType([type, checkDeclarationInitializer(declaration)], UnionReduction.Subtype) :
47364736
type;
47374737
}
47384738

@@ -7331,10 +7331,10 @@ namespace ts {
73317331
}
73327332
}
73337333
else if (isUnion) {
7334-
const index = !isLateBoundName(name) && ((isNumericLiteralName(name) && getIndexInfoOfType(type, IndexKind.Number)) || getIndexInfoOfType(type, IndexKind.String));
7335-
if (index) {
7336-
checkFlags |= index.isReadonly ? CheckFlags.Readonly : 0;
7337-
indexTypes = append(indexTypes, index.type);
7334+
const indexInfo = !isLateBoundName(name) && (isNumericLiteralName(name) && getIndexInfoOfType(type, IndexKind.Number) || getIndexInfoOfType(type, IndexKind.String));
7335+
if (indexInfo) {
7336+
checkFlags |= indexInfo.isReadonly ? CheckFlags.Readonly : 0;
7337+
indexTypes = append(indexTypes, isTupleType(type) ? getRestTypeOfTupleType(type) || undefinedType : indexInfo.type);
73387338
}
73397339
else {
73407340
checkFlags |= CheckFlags.Partial;
@@ -9342,11 +9342,12 @@ namespace ts {
93429342
getFlowTypeOfReference(accessExpression, propType) :
93439343
propType;
93449344
}
9345-
if (isTupleType(objectType)) {
9346-
const restType = getRestTypeOfTupleType(objectType);
9347-
if (restType && isNumericLiteralName(propName) && +propName >= 0) {
9348-
return restType;
9345+
if (everyType(objectType, isTupleType) && isNumericLiteralName(propName) && +propName >= 0) {
9346+
if (accessNode && everyType(objectType, t => !(<TupleTypeReference>t).target.hasRestElement)) {
9347+
const indexNode = accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode.argumentExpression : accessNode.indexType;
9348+
error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType));
93499349
}
9350+
return mapType(objectType, t => getRestTypeOfTupleType(<TupleTypeReference>t) || undefinedType);
93509351
}
93519352
}
93529353
if (!(indexType.flags & TypeFlags.Nullable) && isTypeAssignableToKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike)) {
@@ -12908,9 +12909,14 @@ namespace ts {
1290812909
}
1290912910

1291012911
function getTupleElementType(type: Type, index: number) {
12911-
return isTupleType(type) ?
12912-
index < getLengthOfTupleType(type) ? type.typeArguments![index] : getRestTypeOfTupleType(type) :
12913-
getTypeOfPropertyOfType(type, "" + index as __String);
12912+
const propType = getTypeOfPropertyOfType(type, "" + index as __String);
12913+
if (propType) {
12914+
return propType;
12915+
}
12916+
if (everyType(type, isTupleType) && !everyType(type, t => !(<TupleTypeReference>t).target.hasRestElement)) {
12917+
return mapType(type, t => getRestTypeOfTupleType(<TupleTypeReference>t) || undefinedType);
12918+
}
12919+
return undefined;
1291412920
}
1291512921

1291612922
function isNeitherUnitTypeNorNever(type: Type): boolean {
@@ -14402,7 +14408,7 @@ namespace ts {
1440214408
}
1440314409

1440414410
function getTypeOfDestructuredArrayElement(type: Type, index: number) {
14405-
return isTupleLikeType(type) && getTupleElementType(type, index) ||
14411+
return everyType(type, isTupleLikeType) && getTupleElementType(type, index) ||
1440614412
checkIteratedTypeOrElementType(type, /*errorNode*/ undefined, /*allowStringInput*/ false, /*allowAsyncIterables*/ false) ||
1440714413
errorType;
1440814414
}
@@ -14600,6 +14606,10 @@ namespace ts {
1460014606
return type.flags & TypeFlags.Union ? forEach((<UnionType>type).types, f) : f(type);
1460114607
}
1460214608

14609+
function everyType(type: Type, f: (t: Type) => boolean): boolean {
14610+
return type.flags & TypeFlags.Union ? every((<UnionType>type).types, f) : f(type);
14611+
}
14612+
1460314613
function filterType(type: Type, f: (t: Type) => boolean): Type {
1460414614
if (type.flags & TypeFlags.Union) {
1460514615
const types = (<UnionType>type).types;
@@ -16707,11 +16717,6 @@ namespace ts {
1670716717
return mapType(type, t => getIndexTypeOfStructuredType(t, kind), /*noReductions*/ true);
1670816718
}
1670916719

16710-
// Return true if the given contextual type is a tuple-like type
16711-
function contextualTypeIsTupleLikeType(type: Type): boolean {
16712-
return !!(type.flags & TypeFlags.Union ? forEach((<UnionType>type).types, isTupleLikeType) : isTupleLikeType(type));
16713-
}
16714-
1671516720
// In an object literal contextually typed by a type T, the contextual type of a property assignment is the type of
1671616721
// the matching property in T, if one exists. Otherwise, it is the type of the numeric index signature in T, if one
1671716722
// exists. Otherwise, it is the type of the string index signature in T, if one exists.
@@ -17263,7 +17268,8 @@ namespace ts {
1726317268
}
1726417269

1726517270
function getArrayLiteralTupleTypeIfApplicable(elementTypes: Type[], contextualType: Type | undefined, hasRestElement: boolean, elementCount = elementTypes.length) {
17266-
if (contextualType && contextualTypeIsTupleLikeType(contextualType)) {
17271+
// Infer a tuple type when the contextual type is or contains a tuple-like type
17272+
if (contextualType && forEachType(contextualType, isTupleLikeType)) {
1726717273
const minLength = elementCount - (hasRestElement ? 1 : 0);
1726817274
const pattern = contextualType.pattern;
1726917275
// If array literal is contextually typed by a binding pattern or an assignment pattern, pad the resulting
@@ -18993,13 +18999,6 @@ namespace ts {
1899318999
error(indexExpression, Diagnostics.A_const_enum_member_can_only_be_accessed_using_a_string_literal);
1899419000
return errorType;
1899519001
}
18996-
if (isTupleType(objectType) && !objectType.target.hasRestElement && isNumericLiteral(indexExpression)) {
18997-
const index = +indexExpression.text;
18998-
const maximumIndex = length(objectType.target.typeParameters);
18999-
if (index >= maximumIndex) {
19000-
error(indexExpression, Diagnostics.Index_0_is_out_of_bounds_in_tuple_of_length_1, index, maximumIndex);
19001-
}
19002-
}
1900319002

1900419003
return checkIndexedAccessIndexType(getIndexedAccessType(objectType, isForInVariableForNumericPropertyNames(indexExpression) ? numberType : indexType, node), node);
1900519004
}
@@ -21740,7 +21739,7 @@ namespace ts {
2174021739
if (element.kind !== SyntaxKind.SpreadElement) {
2174121740
const propName = "" + elementIndex as __String;
2174221741
const type = isTypeAny(sourceType) ? sourceType :
21743-
isTupleLikeType(sourceType) ? getTupleElementType(sourceType, elementIndex) :
21742+
everyType(sourceType, isTupleLikeType) ? getTupleElementType(sourceType, elementIndex) :
2174421743
elementType;
2174521744
if (type) {
2174621745
return checkDestructuringAssignment(element, type, checkMode);
@@ -21766,8 +21765,8 @@ namespace ts {
2176621765
}
2176721766
else {
2176821767
checkGrammarForDisallowedTrailingComma(node.elements, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma);
21769-
const type = isTupleType(sourceType) ?
21770-
sliceTupleType(sourceType, elementIndex) :
21768+
const type = everyType(sourceType, isTupleType) ?
21769+
mapType(sourceType, t => sliceTupleType(<TupleTypeReference>t, elementIndex)) :
2177121770
createArrayType(elementType);
2177221771
return checkDestructuringAssignment(restExpression, type, checkMode);
2177321772
}

src/compiler/diagnosticMessages.json

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2485,10 +2485,6 @@
24852485
"category": "Error",
24862486
"code": 2732
24872487
},
2488-
"Index '{0}' is out-of-bounds in tuple of length {1}.": {
2489-
"category": "Error",
2490-
"code": 2733
2491-
},
24922488
"It is highly likely that you are missing a semicolon.": {
24932489
"category": "Error",
24942490
"code": 2734

src/compiler/sys.ts

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,23 +36,6 @@ namespace ts {
3636
Low = 250
3737
}
3838

39-
function getPriorityValues(highPriorityValue: number): [number, number, number] {
40-
const mediumPriorityValue = highPriorityValue * 2;
41-
const lowPriorityValue = mediumPriorityValue * 4;
42-
return [highPriorityValue, mediumPriorityValue, lowPriorityValue];
43-
}
44-
45-
function pollingInterval(watchPriority: PollingInterval): number {
46-
return pollingIntervalsForPriority[watchPriority];
47-
}
48-
49-
const pollingIntervalsForPriority = getPriorityValues(250);
50-
51-
/* @internal */
52-
export function watchFileUsingPriorityPollingInterval(host: System, fileName: string, callback: FileWatcherCallback, watchPriority: PollingInterval): FileWatcher {
53-
return host.watchFile!(fileName, callback, pollingInterval(watchPriority));
54-
}
55-
5639
/* @internal */
5740
export type HostWatchFile = (fileName: string, callback: FileWatcherCallback, pollingInterval: PollingInterval | undefined) => FileWatcher;
5841
/* @internal */

tests/baselines/reference/bestCommonTypeOfTuple.errors.txt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple.ts(22,13): error TS2733: Index '2' is out-of-bounds in tuple of length 2.
2-
tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple.ts(23,13): error TS2733: Index '2' is out-of-bounds in tuple of length 2.
3-
tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple.ts(24,13): error TS2733: Index '2' is out-of-bounds in tuple of length 2.
4-
tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple.ts(25,13): error TS2733: Index '3' is out-of-bounds in tuple of length 3.
1+
tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple.ts(22,13): error TS2339: Property '2' does not exist on type '[(x: number) => string, (x: number) => number]'.
2+
tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple.ts(23,13): error TS2339: Property '2' does not exist on type '[E1, E2]'.
3+
tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple.ts(24,13): error TS2339: Property '2' does not exist on type '[number, any]'.
4+
tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple.ts(25,13): error TS2339: Property '3' does not exist on type '[E1, E2, number]'.
55

66

77
==== tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple.ts (4 errors) ====
@@ -28,13 +28,13 @@ tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfT
2828
t4 = [E1.one, E2.two, 20];
2929
var e1 = t1[2]; // {}
3030
~
31-
!!! error TS2733: Index '2' is out-of-bounds in tuple of length 2.
31+
!!! error TS2339: Property '2' does not exist on type '[(x: number) => string, (x: number) => number]'.
3232
var e2 = t2[2]; // {}
3333
~
34-
!!! error TS2733: Index '2' is out-of-bounds in tuple of length 2.
34+
!!! error TS2339: Property '2' does not exist on type '[E1, E2]'.
3535
var e3 = t3[2]; // any
3636
~
37-
!!! error TS2733: Index '2' is out-of-bounds in tuple of length 2.
37+
!!! error TS2339: Property '2' does not exist on type '[number, any]'.
3838
var e4 = t4[3]; // number
3939
~
40-
!!! error TS2733: Index '3' is out-of-bounds in tuple of length 3.
40+
!!! error TS2339: Property '3' does not exist on type '[E1, E2, number]'.

tests/baselines/reference/bestCommonTypeOfTuple.types

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -76,26 +76,26 @@ t4 = [E1.one, E2.two, 20];
7676
>20 : 20
7777

7878
var e1 = t1[2]; // {}
79-
>e1 : ((x: number) => string) | ((x: number) => number)
80-
>t1[2] : ((x: number) => string) | ((x: number) => number)
79+
>e1 : undefined
80+
>t1[2] : undefined
8181
>t1 : [(x: number) => string, (x: number) => number]
8282
>2 : 2
8383

8484
var e2 = t2[2]; // {}
85-
>e2 : E1 | E2
86-
>t2[2] : E1 | E2
85+
>e2 : undefined
86+
>t2[2] : undefined
8787
>t2 : [E1, E2]
8888
>2 : 2
8989

9090
var e3 = t3[2]; // any
91-
>e3 : any
92-
>t3[2] : any
91+
>e3 : undefined
92+
>t3[2] : undefined
9393
>t3 : [number, any]
9494
>2 : 2
9595

9696
var e4 = t4[3]; // number
97-
>e4 : number
98-
>t4[3] : number
97+
>e4 : undefined
98+
>t4[3] : undefined
9999
>t4 : [E1, E2, number]
100100
>3 : 3
101101

tests/baselines/reference/bestCommonTypeOfTuple2.errors.txt

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple2.ts(17,14): error TS2733: Index '4' is out-of-bounds in tuple of length 2.
2-
tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple2.ts(18,14): error TS2733: Index '4' is out-of-bounds in tuple of length 2.
3-
tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple2.ts(19,14): error TS2733: Index '4' is out-of-bounds in tuple of length 2.
4-
tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple2.ts(20,14): error TS2733: Index '2' is out-of-bounds in tuple of length 2.
5-
tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple2.ts(21,14): error TS2733: Index '2' is out-of-bounds in tuple of length 2.
1+
tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple2.ts(17,14): error TS2339: Property '4' does not exist on type '[C, base]'.
2+
tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple2.ts(18,14): error TS2339: Property '4' does not exist on type '[C, D]'.
3+
tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple2.ts(19,14): error TS2339: Property '4' does not exist on type '[C1, D1]'.
4+
tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple2.ts(20,14): error TS2339: Property '2' does not exist on type '[base1, C1]'.
5+
tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple2.ts(21,14): error TS2339: Property '2' does not exist on type '[C1, F]'.
66

77

88
==== tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple2.ts (5 errors) ====
@@ -24,17 +24,17 @@ tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfT
2424

2525
var e11 = t1[4]; // base
2626
~
27-
!!! error TS2733: Index '4' is out-of-bounds in tuple of length 2.
27+
!!! error TS2339: Property '4' does not exist on type '[C, base]'.
2828
var e21 = t2[4]; // {}
2929
~
30-
!!! error TS2733: Index '4' is out-of-bounds in tuple of length 2.
30+
!!! error TS2339: Property '4' does not exist on type '[C, D]'.
3131
var e31 = t3[4]; // C1
3232
~
33-
!!! error TS2733: Index '4' is out-of-bounds in tuple of length 2.
33+
!!! error TS2339: Property '4' does not exist on type '[C1, D1]'.
3434
var e41 = t4[2]; // base1
3535
~
36-
!!! error TS2733: Index '2' is out-of-bounds in tuple of length 2.
36+
!!! error TS2339: Property '2' does not exist on type '[base1, C1]'.
3737
var e51 = t5[2]; // {}
3838
~
39-
!!! error TS2733: Index '2' is out-of-bounds in tuple of length 2.
39+
!!! error TS2339: Property '2' does not exist on type '[C1, F]'.
4040

tests/baselines/reference/bestCommonTypeOfTuple2.types

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,32 +49,32 @@ var t5: [C1, F]
4949
>t5 : [C1, F]
5050

5151
var e11 = t1[4]; // base
52-
>e11 : base | C
53-
>t1[4] : base | C
52+
>e11 : undefined
53+
>t1[4] : undefined
5454
>t1 : [C, base]
5555
>4 : 4
5656

5757
var e21 = t2[4]; // {}
58-
>e21 : C | D
59-
>t2[4] : C | D
58+
>e21 : undefined
59+
>t2[4] : undefined
6060
>t2 : [C, D]
6161
>4 : 4
6262

6363
var e31 = t3[4]; // C1
64-
>e31 : C1 | D1
65-
>t3[4] : C1 | D1
64+
>e31 : undefined
65+
>t3[4] : undefined
6666
>t3 : [C1, D1]
6767
>4 : 4
6868

6969
var e41 = t4[2]; // base1
70-
>e41 : base1 | C1
71-
>t4[2] : base1 | C1
70+
>e41 : undefined
71+
>t4[2] : undefined
7272
>t4 : [base1, C1]
7373
>2 : 2
7474

7575
var e51 = t5[2]; // {}
76-
>e51 : F | C1
77-
>t5[2] : F | C1
76+
>e51 : undefined
77+
>t5[2] : undefined
7878
>t5 : [C1, F]
7979
>2 : 2
8080

0 commit comments

Comments
 (0)