Skip to content

Commit 28c9f59

Browse files
committed
Complete support for rest elements in tuples
1 parent 88444fe commit 28c9f59

File tree

3 files changed

+129
-60
lines changed

3 files changed

+129
-60
lines changed

src/compiler/checker.ts

+108-60
Original file line numberDiff line numberDiff line change
@@ -6232,13 +6232,15 @@ namespace ts {
62326232
const restParameter = sig.parameters[restIndex];
62336233
const restType = getTypeOfSymbol(restParameter);
62346234
if (isTupleType(restType)) {
6235-
const elementTypes = (<TypeReference>restType).typeArguments || emptyArray;
6236-
const minLength = (<TupleType>(<TypeReference>restType).target).minLength;
6235+
const elementTypes = restType.typeArguments || emptyArray;
6236+
const minLength = restType.target.minLength;
6237+
const tupleRestIndex = restType.target.hasRestElement ? elementTypes.length - 1 : -1;
62376238
const restParams = map(elementTypes, (t, i) => {
62386239
const name = getParameterNameAtPosition(sig, restIndex + i);
6239-
const checkFlags = i >= minLength ? CheckFlags.OptionalParameter : 0;
6240+
const checkFlags = i === tupleRestIndex ? CheckFlags.RestParameter :
6241+
i >= minLength ? CheckFlags.OptionalParameter : 0;
62406242
const symbol = createSymbol(SymbolFlags.FunctionScopedVariable, name, checkFlags);
6241-
symbol.type = t;
6243+
symbol.type = i === tupleRestIndex ? createArrayType(t) : t;
62426244
return symbol;
62436245
});
62446246
return concatenate(sig.parameters.slice(0, restIndex), restParams);
@@ -8372,7 +8374,7 @@ namespace ts {
83728374
const minLength = findLastIndex(node.elementTypes, n => n.kind !== SyntaxKind.OptionalType && n !== restElement) + 1;
83738375
const elementTypes = map(node.elementTypes, n => {
83748376
const type = getTypeFromTypeNode(n);
8375-
return n === restElement ? getIndexTypeOfType(type, IndexKind.Number) || errorType : type;
8377+
return n === restElement && getIndexTypeOfType(type, IndexKind.Number) || type;
83768378
});
83778379
links.resolvedType = createTupleType(elementTypes, minLength, !!restElement);
83788380
}
@@ -8887,6 +8889,12 @@ namespace ts {
88878889
}
88888890
return getTypeOfSymbol(prop);
88898891
}
8892+
if (isTupleType(objectType)) {
8893+
const restType = getRestTypeOfTupleType(objectType);
8894+
if (restType && isNumericLiteralName(propName) && +propName >= 0) {
8895+
return restType;
8896+
}
8897+
}
88908898
}
88918899
if (!(indexType.flags & TypeFlags.Nullable) && isTypeAssignableToKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike)) {
88928900
if (isTypeAny(objectType)) {
@@ -11343,6 +11351,33 @@ namespace ts {
1134311351
}
1134411352
}
1134511353
let result = Ternary.True;
11354+
if (isTupleType(target)) {
11355+
const targetRestType = getRestTypeOfTupleType(target);
11356+
if (targetRestType) {
11357+
if (!isTupleType(source)) {
11358+
return Ternary.False;
11359+
}
11360+
const sourceRestType = getRestTypeOfTupleType(source);
11361+
if (sourceRestType && !isRelatedTo(sourceRestType, targetRestType, reportErrors)) {
11362+
if (reportErrors) {
11363+
reportError(Diagnostics.Rest_signatures_are_incompatible);
11364+
}
11365+
return Ternary.False;
11366+
}
11367+
const targetCount = getTypeReferenceArity(target) - 1;
11368+
const sourceCount = getTypeReferenceArity(source) - (sourceRestType ? 1 : 0);
11369+
for (let i = targetCount; i < sourceCount; i++) {
11370+
const related = isRelatedTo((<TypeReference>source).typeArguments![i], targetRestType, reportErrors);
11371+
if (!related) {
11372+
if (reportErrors) {
11373+
reportError(Diagnostics.Property_0_is_incompatible_with_rest_element_type, "" + i);
11374+
}
11375+
return Ternary.False;
11376+
}
11377+
result &= related;
11378+
}
11379+
}
11380+
}
1134611381
const properties = getPropertiesOfObjectType(target);
1134711382
for (const targetProp of properties) {
1134811383
if (!(targetProp.flags & SymbolFlags.Prototype)) {
@@ -11417,35 +11452,6 @@ namespace ts {
1141711452
}
1141811453
}
1141911454
}
11420-
if (isTupleType(target)) {
11421-
const targetRestType = getRestTypeOfTupleType(target);
11422-
if (targetRestType) {
11423-
if (!isTupleType(source)) {
11424-
return Ternary.False;
11425-
}
11426-
const sourceRestType = getRestTypeOfTupleType(source);
11427-
if (sourceRestType && !isRelatedTo(sourceRestType, targetRestType, reportErrors)) {
11428-
if (reportErrors) {
11429-
// !!! Rest element types are incompatible
11430-
reportError(Diagnostics.Index_signatures_are_incompatible);
11431-
}
11432-
return Ternary.False;
11433-
}
11434-
const targetCount = getTypeReferenceArity(target) - 1;
11435-
const sourceCount = getTypeReferenceArity(source) - (sourceRestType ? 1 : 0);
11436-
for (let i = targetCount; i < sourceCount; i++) {
11437-
const related = isRelatedTo((<TypeReference>source).typeArguments![i], targetRestType, reportErrors);
11438-
if (!related) {
11439-
if (reportErrors) {
11440-
// !!! Property {0} is incompatible with rest element type
11441-
reportError(Diagnostics.Property_0_is_incompatible_with_index_signature, "" + i);
11442-
}
11443-
return Ternary.False;
11444-
}
11445-
result &= related;
11446-
}
11447-
}
11448-
}
1144911455
return result;
1145011456
}
1145111457

@@ -12506,7 +12512,7 @@ namespace ts {
1250612512
}
1250712513
const minArgumentCount = getMinArgumentCount(source);
1250812514
const minLength = minArgumentCount < paramCount ? 0 : minArgumentCount - paramCount;
12509-
const rest = sourceHasRest ? createArrayType(getUnionType(types)) : createTupleType(types, minLength, /*hasRestElement*/ false, names);
12515+
const rest = createTupleType(types, minLength, sourceHasRest, names);
1251012516
callback(rest, targetRestTypeVariable);
1251112517
}
1251212518
}
@@ -17865,14 +17871,12 @@ namespace ts {
1786517871
}
1786617872
}
1786717873

17874+
function isSpreadArgument(arg: Expression | undefined) {
17875+
return !!arg && (arg.kind === SyntaxKind.SpreadElement || arg.kind === SyntaxKind.SyntheticExpression && (<SyntheticExpression>arg).isSpread);
17876+
}
17877+
1786817878
function getSpreadArgumentIndex(args: ReadonlyArray<Expression>): number {
17869-
for (let i = 0; i < args.length; i++) {
17870-
const arg = args[i];
17871-
if (arg && arg.kind === SyntaxKind.SpreadElement) {
17872-
return i;
17873-
}
17874-
}
17875-
return -1;
17879+
return findIndex(args, isSpreadArgument);
1787617880
}
1787717881

1787817882
function hasCorrectArity(node: CallLikeExpression, args: ReadonlyArray<Expression>, signature: Signature, signatureHelpTrailingComma = false) {
@@ -18091,7 +18095,7 @@ namespace ts {
1809118095
function getSpreadArgumentType(node: CallLikeExpression, args: ReadonlyArray<Expression>, index: number, argCount: number, restType: TypeParameter, context: InferenceContext | undefined) {
1809218096
if (index === argCount - 1) {
1809318097
const arg = getEffectiveArgument(node, args, index);
18094-
if (arg && arg.kind === SyntaxKind.SpreadElement) {
18098+
if (isSpreadArgument(arg)) {
1809518099
// We are inferring from a spread expression in the last argument position, i.e. both the parameter
1809618100
// and the argument are ...x forms.
1809718101
return checkExpressionWithContextualType((<SpreadElement>arg).expression, restType, context);
@@ -18100,16 +18104,20 @@ namespace ts {
1810018104
const contextualType = getIndexTypeOfType(restType, IndexKind.Number) || anyType;
1810118105
const hasPrimitiveContextualType = maybeTypeOfKind(contextualType, TypeFlags.Primitive | TypeFlags.Index);
1810218106
const types = [];
18103-
let hasSpreadExpression = false;
18107+
let spreadIndex = -1;
1810418108
for (let i = index; i < argCount; i++) {
1810518109
let argType = getEffectiveArgumentType(node, i);
1810618110
if (!argType) {
1810718111
argType = checkExpressionWithContextualType(args[i], contextualType, context);
18108-
hasSpreadExpression = hasSpreadExpression || args[i].kind === SyntaxKind.SpreadElement;
18112+
if (spreadIndex < 0 && isSpreadArgument(args[i])) {
18113+
spreadIndex = i - index;
18114+
}
1810918115
}
1811018116
types.push(hasPrimitiveContextualType ? getRegularTypeOfLiteralType(argType) : getWidenedLiteralType(argType));
1811118117
}
18112-
return hasSpreadExpression ? createArrayType(getUnionType(types)) : createTupleType(types);
18118+
return spreadIndex < 0 ?
18119+
createTupleType(types) :
18120+
createTupleType(append(types.slice(0, spreadIndex), getUnionType(types.slice(spreadIndex))), spreadIndex, /*hasRestElement*/ true);
1811318121
}
1811418122

1811518123
function checkTypeArguments(signature: Signature, typeArgumentNodes: ReadonlyArray<TypeNode>, reportErrors: boolean, headMessage?: DiagnosticMessage): Type[] | false {
@@ -18205,7 +18213,7 @@ namespace ts {
1820518213
const arg = getEffectiveArgument(node, args, i);
1820618214
// If the effective argument is 'undefined', then it is an argument that is present but is synthetic.
1820718215
if (arg === undefined || arg.kind !== SyntaxKind.OmittedExpression) {
18208-
if (i === restIndex && (restType.flags & TypeFlags.TypeParameter || arg && arg.kind === SyntaxKind.SpreadElement && !isArrayType(restType))) {
18216+
if (i === restIndex && (restType.flags & TypeFlags.TypeParameter || isSpreadArgument(arg) && !isArrayType(restType))) {
1820918217
const spreadType = getSpreadArgumentType(node, args, i, argCount, restType, /*context*/ undefined);
1821018218
return checkTypeRelatedTo(spreadType, restType, relation, arg, headMessage);
1821118219
}
@@ -18275,17 +18283,20 @@ namespace ts {
1827518283
else {
1827618284
const args = node.arguments || emptyArray;
1827718285
const length = args.length;
18278-
if (length && args[length - 1].kind === SyntaxKind.SpreadElement && getSpreadArgumentIndex(args) === length - 1) {
18286+
if (length && isSpreadArgument(args[length - 1]) && getSpreadArgumentIndex(args) === length - 1) {
1827918287
// We have a spread argument in the last position and no other spread arguments. If the type
1828018288
// of the argument is a tuple type, spread the tuple elements into the argument list. We can
1828118289
// call checkExpressionCached because spread expressions never have a contextual type.
1828218290
const spreadArgument = <SpreadElement>args[length - 1];
1828318291
const type = checkExpressionCached(spreadArgument.expression);
1828418292
if (isTupleType(type)) {
18285-
const syntheticArgs = map((<TypeReference>type).typeArguments || emptyArray, t => {
18293+
const typeArguments = (<TypeReference>type).typeArguments || emptyArray;
18294+
const restIndex = type.target.hasRestElement ? typeArguments.length - 1 : -1;
18295+
const syntheticArgs = map(typeArguments, (t, i) => {
1828618296
const arg = <SyntheticExpression>createNode(SyntaxKind.SyntheticExpression, spreadArgument.pos, spreadArgument.end);
1828718297
arg.parent = spreadArgument;
1828818298
arg.type = t;
18299+
arg.isSpread = i === restIndex;
1828918300
return arg;
1829018301
});
1829118302
return concatenate(args.slice(0, length - 1), syntheticArgs);
@@ -19601,9 +19612,12 @@ namespace ts {
1960119612
if (signature.hasRestParameter) {
1960219613
const restType = getTypeOfSymbol(signature.parameters[paramCount]);
1960319614
if (isTupleType(restType)) {
19604-
const elementCount = ((<TypeReference>restType).typeArguments || emptyArray).length;
19605-
if (pos - paramCount < elementCount) {
19606-
return (<TypeReference>restType).typeArguments![pos - paramCount];
19615+
if (pos - paramCount < getLengthOfTupleType(restType)) {
19616+
return restType.typeArguments![pos - paramCount];
19617+
}
19618+
const tupleRestType = getRestTypeOfTupleType(restType);
19619+
if (tupleRestType) {
19620+
return tupleRestType;
1960719621
}
1960819622
}
1960919623
return getIndexTypeOfType(restType, IndexKind.Number) || anyType;
@@ -19612,26 +19626,35 @@ namespace ts {
1961219626
}
1961319627

1961419628
function getTypeOfRestParameter(signature: Signature) {
19615-
return signature.hasRestParameter ? getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]) : undefined;
19629+
if (signature.hasRestParameter) {
19630+
const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]);
19631+
if (isTupleType(restType)) {
19632+
return getRestTypeOfTupleType(restType);
19633+
}
19634+
return restType;
19635+
}
19636+
return undefined;
1961619637
}
1961719638

1961819639
function getParameterCount(signature: Signature) {
1961919640
const length = signature.parameters.length;
1962019641
if (signature.hasRestParameter) {
1962119642
const restType = getTypeOfSymbol(signature.parameters[length - 1]);
1962219643
if (isTupleType(restType)) {
19623-
return length + ((<TypeReference>restType).typeArguments || emptyArray).length - 1;
19644+
return length + (restType.typeArguments || emptyArray).length - 1;
1962419645
}
1962519646
}
1962619647
return length;
1962719648
}
1962819649

1962919650
function getMinArgumentCount(signature: Signature) {
19630-
const restType = getTypeOfRestParameter(signature);
19631-
if (restType && isTupleType(restType)) {
19632-
const minLength = (<TupleType>(<TypeReference>restType).target).minLength;
19633-
if (minLength > 0) {
19634-
return signature.parameters.length - 1 + minLength;
19651+
if (signature.hasRestParameter) {
19652+
const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]);
19653+
if (isTupleType(restType)) {
19654+
const minLength = restType.target.minLength;
19655+
if (minLength > 0) {
19656+
return signature.parameters.length - 1 + minLength;
19657+
}
1963519658
}
1963619659
}
1963719660
return signature.minArgumentCount;
@@ -19648,7 +19671,11 @@ namespace ts {
1964819671
}
1964919672

1965019673
function hasEffectiveRestParameter(signature: Signature) {
19651-
return signature.hasRestParameter && !isTupleType(getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]));
19674+
if (signature.hasRestParameter) {
19675+
const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]);
19676+
return !isTupleType(restType) || restType.target.hasRestElement;
19677+
}
19678+
return false;
1965219679
}
1965319680

1965419681
function getTypeOfFirstParameterOfSignature(signature: Signature) {
@@ -21928,6 +21955,27 @@ namespace ts {
2192821955
}
2192921956

2193021957
function checkTupleType(node: TupleTypeNode) {
21958+
const elementTypes = node.elementTypes;
21959+
let seenOptionalElement = false;
21960+
for (let i = 0; i < elementTypes.length; i++) {
21961+
const e = elementTypes[i];
21962+
if (e.kind === SyntaxKind.RestType) {
21963+
if (i !== elementTypes.length - 1) {
21964+
grammarErrorOnNode(e, Diagnostics.A_rest_element_must_be_last_in_a_tuple_type);
21965+
break;
21966+
}
21967+
if (!isArrayType(getTypeFromTypeNode(e))) {
21968+
error(e, Diagnostics.A_rest_element_type_must_be_an_array_type);
21969+
}
21970+
}
21971+
else if (e.kind === SyntaxKind.OptionalType) {
21972+
seenOptionalElement = true;
21973+
}
21974+
else if (seenOptionalElement) {
21975+
grammarErrorOnNode(e, Diagnostics.A_required_element_cannot_follow_an_optional_element);
21976+
break;
21977+
}
21978+
}
2193121979
checkGrammarForDisallowedTrailingComma(node.elementTypes);
2193221980
forEach(node.elementTypes, checkSourceElement);
2193321981
}

src/compiler/diagnosticMessages.json

+20
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,14 @@
843843
"category": "Error",
844844
"code": 1255
845845
},
846+
"A rest element must be last in a tuple type.": {
847+
"category": "Error",
848+
"code": 1256
849+
},
850+
"A required element cannot follow an optional element.": {
851+
"category": "Error",
852+
"code": 1257
853+
},
846854
"'with' statements are not allowed in an async function block.": {
847855
"category": "Error",
848856
"code": 1300
@@ -2028,6 +2036,18 @@
20282036
"category": "Error",
20292037
"code": 2571
20302038
},
2039+
"Rest signatures are incompatible.": {
2040+
"category": "Error",
2041+
"code": 2572
2042+
},
2043+
"Property '{0}' is incompatible with rest element type.": {
2044+
"category": "Error",
2045+
"code": 2573
2046+
},
2047+
"A rest element type must be an array type.": {
2048+
"category": "Error",
2049+
"code": 2574
2050+
},
20312051
"JSX element attributes type '{0}' may not be a union type.": {
20322052
"category": "Error",
20332053
"code": 2600

src/compiler/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1303,6 +1303,7 @@ namespace ts {
13031303

13041304
export interface SyntheticExpression extends Expression {
13051305
kind: SyntaxKind.SyntheticExpression;
1306+
isSpread: boolean;
13061307
type: Type;
13071308
}
13081309

0 commit comments

Comments
 (0)