Skip to content

Commit 2494b2d

Browse files
committed
Support spread operator in call expressions
1 parent bbbec22 commit 2494b2d

File tree

5 files changed

+231
-152
lines changed

5 files changed

+231
-152
lines changed

src/compiler/checker.ts

Lines changed: 134 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -5818,10 +5818,74 @@ module ts {
58185818
return unknownSignature;
58195819
}
58205820

5821+
// Re-order candidate signatures into the result array. Assumes the result array to be empty.
5822+
// The candidate list orders groups in reverse, but within a group signatures are kept in declaration order
5823+
// A nit here is that we reorder only signatures that belong to the same symbol,
5824+
// so order how inherited signatures are processed is still preserved.
5825+
// interface A { (x: string): void }
5826+
// interface B extends A { (x: 'foo'): string }
5827+
// var b: B;
5828+
// b('foo') // <- here overloads should be processed as [(x:'foo'): string, (x: string): void]
5829+
function reorderCandidates(signatures: Signature[], result: Signature[]): void {
5830+
var lastParent: Node;
5831+
var lastSymbol: Symbol;
5832+
var cutoffIndex: number = 0;
5833+
var index: number;
5834+
var specializedIndex: number = -1;
5835+
var spliceIndex: number;
5836+
Debug.assert(!result.length);
5837+
for (var i = 0; i < signatures.length; i++) {
5838+
var signature = signatures[i];
5839+
var symbol = signature.declaration && getSymbolOfNode(signature.declaration);
5840+
var parent = signature.declaration && signature.declaration.parent;
5841+
if (!lastSymbol || symbol === lastSymbol) {
5842+
if (lastParent && parent === lastParent) {
5843+
index++;
5844+
}
5845+
else {
5846+
lastParent = parent;
5847+
index = cutoffIndex;
5848+
}
5849+
}
5850+
else {
5851+
// current declaration belongs to a different symbol
5852+
// set cutoffIndex so re-orderings in the future won't change result set from 0 to cutoffIndex
5853+
index = cutoffIndex = result.length;
5854+
lastParent = parent;
5855+
}
5856+
lastSymbol = symbol;
5857+
5858+
// specialized signatures always need to be placed before non-specialized signatures regardless
5859+
// of the cutoff position; see GH#1133
5860+
if (signature.hasStringLiterals) {
5861+
specializedIndex++;
5862+
spliceIndex = specializedIndex;
5863+
// The cutoff index always needs to be greater than or equal to the specialized signature index
5864+
// in order to prevent non-specialized signatures from being added before a specialized
5865+
// signature.
5866+
cutoffIndex++;
5867+
}
5868+
else {
5869+
spliceIndex = index;
5870+
}
5871+
5872+
result.splice(spliceIndex, 0, signature);
5873+
}
5874+
}
5875+
5876+
function getSpreadArgumentIndex(args: Expression[]): number {
5877+
for (var i = 0; i < args.length; i++) {
5878+
if (args[i].kind === SyntaxKind.SpreadElementExpression) {
5879+
return i;
5880+
}
5881+
}
5882+
return -1;
5883+
}
5884+
58215885
function hasCorrectArity(node: CallLikeExpression, args: Expression[], signature: Signature) {
5822-
var adjustedArgCount: number;
5823-
var typeArguments: NodeArray<TypeNode>;
5824-
var callIsIncomplete: boolean;
5886+
var adjustedArgCount: number; // Apparent number of arguments we will have in this call
5887+
var typeArguments: NodeArray<TypeNode>; // Type arguments (undefined if none)
5888+
var callIsIncomplete: boolean; // In incomplete call we want to be lenient when we have too few arguments
58255889

58265890
if (node.kind === SyntaxKind.TaggedTemplateExpression) {
58275891
var tagExpression = <TaggedTemplateExpression>node;
@@ -5866,35 +5930,29 @@ module ts {
58665930
typeArguments = callExpression.typeArguments;
58675931
}
58685932

5869-
Debug.assert(adjustedArgCount !== undefined, "'adjustedArgCount' undefined");
5870-
Debug.assert(callIsIncomplete !== undefined, "'callIsIncomplete' undefined");
5871-
5872-
return checkArity(adjustedArgCount, typeArguments, callIsIncomplete, signature);
5873-
5874-
/**
5875-
* @param adjustedArgCount The "apparent" number of arguments that we will have in this call.
5876-
* @param typeArguments Type arguments node of the call if it exists; undefined otherwise.
5877-
* @param callIsIncomplete Whether or not a call is unfinished, and we should be "lenient" when we have too few arguments.
5878-
* @param signature The signature whose arity we are comparing.
5879-
*/
5880-
function checkArity(adjustedArgCount: number, typeArguments: NodeArray<TypeNode>, callIsIncomplete: boolean, signature: Signature): boolean {
5881-
// Too many arguments implies incorrect arity.
5882-
if (!signature.hasRestParameter && adjustedArgCount > signature.parameters.length) {
5883-
return false;
5884-
}
5933+
// If the user supplied type arguments, but the number of type arguments does not match
5934+
// the declared number of type parameters, the call has an incorrect arity.
5935+
var hasRightNumberOfTypeArgs = !typeArguments ||
5936+
(signature.typeParameters && typeArguments.length === signature.typeParameters.length);
5937+
if (!hasRightNumberOfTypeArgs) {
5938+
return false;
5939+
}
58855940

5886-
// If the user supplied type arguments, but the number of type arguments does not match
5887-
// the declared number of type parameters, the call has an incorrect arity.
5888-
var hasRightNumberOfTypeArgs = !typeArguments ||
5889-
(signature.typeParameters && typeArguments.length === signature.typeParameters.length);
5890-
if (!hasRightNumberOfTypeArgs) {
5891-
return false;
5892-
}
5941+
// If spread arguments are present, check that they correspond to a rest parameter. If so, no
5942+
// further checking is necessary.
5943+
var spreadArgIndex = getSpreadArgumentIndex(args);
5944+
if (spreadArgIndex >= 0) {
5945+
return signature.hasRestParameter && spreadArgIndex >= signature.parameters.length - 1;
5946+
}
58935947

5894-
// If the call is incomplete, we should skip the lower bound check.
5895-
var hasEnoughArguments = adjustedArgCount >= signature.minArgumentCount;
5896-
return callIsIncomplete || hasEnoughArguments;
5948+
// Too many arguments implies incorrect arity.
5949+
if (!signature.hasRestParameter && adjustedArgCount > signature.parameters.length) {
5950+
return false;
58975951
}
5952+
5953+
// If the call is incomplete, we should skip the lower bound check.
5954+
var hasEnoughArguments = adjustedArgCount >= signature.minArgumentCount;
5955+
return callIsIncomplete || hasEnoughArguments;
58985956
}
58995957

59005958
// If type has a single call signature and no other members, return that signature. Otherwise, return undefined.
@@ -5927,32 +5985,32 @@ module ts {
59275985
// We perform two passes over the arguments. In the first pass we infer from all arguments, but use
59285986
// wildcards for all context sensitive function expressions.
59295987
for (var i = 0; i < args.length; i++) {
5930-
if (args[i].kind === SyntaxKind.OmittedExpression) {
5931-
continue;
5932-
}
5933-
var parameterType = getTypeAtPosition(signature, i);
5934-
if (i === 0 && args[i].parent.kind === SyntaxKind.TaggedTemplateExpression) {
5935-
inferTypes(context, globalTemplateStringsArrayType, parameterType);
5936-
continue;
5988+
var arg = args[i];
5989+
if (arg.kind !== SyntaxKind.OmittedExpression) {
5990+
var paramType = getTypeAtPosition(signature, arg.kind === SyntaxKind.SpreadElementExpression ? -1 : i);
5991+
if (i === 0 && args[i].parent.kind === SyntaxKind.TaggedTemplateExpression) {
5992+
var argType = globalTemplateStringsArrayType;
5993+
}
5994+
else {
5995+
// For context sensitive arguments we pass the identityMapper, which is a signal to treat all
5996+
// context sensitive function expressions as wildcards
5997+
var mapper = excludeArgument && excludeArgument[i] !== undefined ? identityMapper : inferenceMapper;
5998+
var argType = checkExpressionWithContextualType(arg, paramType, mapper);
5999+
}
6000+
inferTypes(context, argType, paramType);
59376001
}
5938-
// For context sensitive arguments we pass the identityMapper, which is a signal to treat all
5939-
// context sensitive function expressions as wildcards
5940-
var mapper = excludeArgument && excludeArgument[i] !== undefined ? identityMapper : inferenceMapper;
5941-
inferTypes(context, checkExpressionWithContextualType(args[i], parameterType, mapper), parameterType);
59426002
}
59436003

59446004
// In the second pass we visit only context sensitive arguments, and only those that aren't excluded, this
59456005
// time treating function expressions normally (which may cause previously inferred type arguments to be fixed
59466006
// as we construct types for contextually typed parameters)
59476007
if (excludeArgument) {
59486008
for (var i = 0; i < args.length; i++) {
5949-
if (args[i].kind === SyntaxKind.OmittedExpression) {
5950-
continue;
5951-
}
5952-
// No need to special-case tagged templates; their excludeArgument value will be 'undefined'.
6009+
// No need to check for omitted args and template expressions, their exlusion value is always undefined
59536010
if (excludeArgument[i] === false) {
5954-
var parameterType = getTypeAtPosition(signature, i);
5955-
inferTypes(context, checkExpressionWithContextualType(args[i], parameterType, inferenceMapper), parameterType);
6011+
var arg = args[i];
6012+
var paramType = getTypeAtPosition(signature, arg.kind === SyntaxKind.SpreadElementExpression ? -1 : i);
6013+
inferTypes(context, checkExpressionWithContextualType(arg, paramType, inferenceMapper), paramType);
59566014
}
59576015
}
59586016
}
@@ -5990,37 +6048,24 @@ module ts {
59906048
return typeArgumentsAreAssignable;
59916049
}
59926050

5993-
function checkApplicableSignature(node: CallLikeExpression, args: Node[], signature: Signature, relation: Map<RelationComparisonResult>, excludeArgument: boolean[], reportErrors: boolean) {
6051+
function checkApplicableSignature(node: CallLikeExpression, args: Expression[], signature: Signature, relation: Map<RelationComparisonResult>, excludeArgument: boolean[], reportErrors: boolean) {
59946052
for (var i = 0; i < args.length; i++) {
59956053
var arg = args[i];
5996-
var argType: Type;
5997-
5998-
if (arg.kind === SyntaxKind.OmittedExpression) {
5999-
continue;
6000-
}
6001-
6002-
var paramType = getTypeAtPosition(signature, i);
6003-
6004-
if (i === 0 && node.kind === SyntaxKind.TaggedTemplateExpression) {
6005-
// A tagged template expression has something of a
6006-
// "virtual" parameter with the "cooked" strings array type.
6007-
argType = globalTemplateStringsArrayType;
6008-
}
6009-
else {
6010-
// String literals get string literal types unless we're reporting errors
6011-
argType = arg.kind === SyntaxKind.StringLiteral && !reportErrors
6012-
? getStringLiteralType(<LiteralExpression>arg)
6013-
: checkExpressionWithContextualType(<LiteralExpression>arg, paramType, excludeArgument && excludeArgument[i] ? identityMapper : undefined);
6014-
}
6015-
6016-
// Use argument expression as error location when reporting errors
6017-
var isValidArgument = checkTypeRelatedTo(argType, paramType, relation, reportErrors ? arg : undefined,
6018-
Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1);
6019-
if (!isValidArgument) {
6020-
return false;
6054+
if (arg.kind !== SyntaxKind.OmittedExpression) {
6055+
// Check spread elements against rest type (from arity check we know spread argument corresponds to a rest parameter)
6056+
var paramType = getTypeAtPosition(signature, arg.kind === SyntaxKind.SpreadElementExpression ? -1 : i);
6057+
// A tagged template expression provides a special first argument, and string literals get string literal types
6058+
// unless we're reporting errors
6059+
var argType = i === 0 && node.kind === SyntaxKind.TaggedTemplateExpression ? globalTemplateStringsArrayType :
6060+
arg.kind === SyntaxKind.StringLiteral && !reportErrors ? getStringLiteralType(<LiteralExpression>arg) :
6061+
checkExpressionWithContextualType(arg, paramType, excludeArgument && excludeArgument[i] ? identityMapper : undefined);
6062+
// Use argument expression as error location when reporting errors
6063+
if (!checkTypeRelatedTo(argType, paramType, relation, reportErrors ? arg : undefined,
6064+
Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1)) {
6065+
return false;
6066+
}
60216067
}
60226068
}
6023-
60246069
return true;
60256070
}
60266071

@@ -6087,8 +6132,8 @@ module ts {
60876132
}
60886133

60896134
var candidates = candidatesOutArray || [];
6090-
// collectCandidates fills up the candidates array directly
6091-
collectCandidates();
6135+
// reorderCandidates fills up the candidates array directly
6136+
reorderCandidates(signatures, candidates);
60926137
if (!candidates.length) {
60936138
error(node, Diagnostics.Supplied_parameters_do_not_match_any_signature_of_call_target);
60946139
return resolveErrorCall(node);
@@ -6278,60 +6323,6 @@ module ts {
62786323
return undefined;
62796324
}
62806325

6281-
// The candidate list orders groups in reverse, but within a group signatures are kept in declaration order
6282-
// A nit here is that we reorder only signatures that belong to the same symbol,
6283-
// so order how inherited signatures are processed is still preserved.
6284-
// interface A { (x: string): void }
6285-
// interface B extends A { (x: 'foo'): string }
6286-
// var b: B;
6287-
// b('foo') // <- here overloads should be processed as [(x:'foo'): string, (x: string): void]
6288-
function collectCandidates(): void {
6289-
var result = candidates;
6290-
var lastParent: Node;
6291-
var lastSymbol: Symbol;
6292-
var cutoffIndex: number = 0;
6293-
var index: number;
6294-
var specializedIndex: number = -1;
6295-
var spliceIndex: number;
6296-
Debug.assert(!result.length);
6297-
for (var i = 0; i < signatures.length; i++) {
6298-
var signature = signatures[i];
6299-
var symbol = signature.declaration && getSymbolOfNode(signature.declaration);
6300-
var parent = signature.declaration && signature.declaration.parent;
6301-
if (!lastSymbol || symbol === lastSymbol) {
6302-
if (lastParent && parent === lastParent) {
6303-
index++;
6304-
}
6305-
else {
6306-
lastParent = parent;
6307-
index = cutoffIndex;
6308-
}
6309-
}
6310-
else {
6311-
// current declaration belongs to a different symbol
6312-
// set cutoffIndex so re-orderings in the future won't change result set from 0 to cutoffIndex
6313-
index = cutoffIndex = result.length;
6314-
lastParent = parent;
6315-
}
6316-
lastSymbol = symbol;
6317-
6318-
// specialized signatures always need to be placed before non-specialized signatures regardless
6319-
// of the cutoff position; see GH#1133
6320-
if (signature.hasStringLiterals) {
6321-
specializedIndex++;
6322-
spliceIndex = specializedIndex;
6323-
// The cutoff index always needs to be greater than or equal to the specialized signature index
6324-
// in order to prevent non-specialized signatures from being added before a specialized
6325-
// signature.
6326-
cutoffIndex++;
6327-
}
6328-
else {
6329-
spliceIndex = index;
6330-
}
6331-
6332-
result.splice(spliceIndex, 0, signature);
6333-
}
6334-
}
63356326
}
63366327

63376328
function resolveCallExpression(node: CallExpression, candidatesOutArray: Signature[]): Signature {
@@ -6387,6 +6378,13 @@ module ts {
63876378
}
63886379

63896380
function resolveNewExpression(node: NewExpression, candidatesOutArray: Signature[]): Signature {
6381+
if (node.arguments && languageVersion < ScriptTarget.ES6) {
6382+
var spreadIndex = getSpreadArgumentIndex(node.arguments);
6383+
if (spreadIndex >= 0) {
6384+
error(node.arguments[spreadIndex], Diagnostics.Spread_operator_in_new_expressions_is_only_available_when_targeting_ECMAScript_6_and_higher);
6385+
}
6386+
}
6387+
63906388
var expressionType = checkExpression(node.expression);
63916389
// TS 1.0 spec: 4.11
63926390
// If ConstructExpr is of type Any, Args can be any argument
@@ -6532,9 +6530,14 @@ module ts {
65326530
}
65336531

65346532
function getTypeAtPosition(signature: Signature, pos: number): Type {
6533+
if (pos >= 0) {
6534+
return signature.hasRestParameter ?
6535+
pos < signature.parameters.length - 1 ? getTypeOfSymbol(signature.parameters[pos]) : getRestTypeOfSignature(signature) :
6536+
pos < signature.parameters.length ? getTypeOfSymbol(signature.parameters[pos]) : anyType;
6537+
}
65356538
return signature.hasRestParameter ?
6536-
pos < signature.parameters.length - 1 ? getTypeOfSymbol(signature.parameters[pos]) : getRestTypeOfSignature(signature) :
6537-
pos < signature.parameters.length ? getTypeOfSymbol(signature.parameters[pos]) : anyType;
6539+
getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]) :
6540+
anyArrayType;
65386541
}
65396542

65406543
function assignContextualParameterTypes(signature: Signature, context: Signature, mapper: TypeMapper) {

0 commit comments

Comments
 (0)