diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index 504d3ce474d..bacce85a34f 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -6,7 +6,6 @@
///
module ts {
-
var nextSymbolId = 1;
var nextNodeId = 1;
var nextMergeId = 1;
@@ -57,6 +56,8 @@ module ts {
return stringWriters.pop();
}
+ type CallLikeExpression = CallExpression | TaggedTemplateExpression;
+
/// fullTypeCheck denotes if this instance of the typechecker will be used to get semantic diagnostics.
/// If fullTypeCheck === true, then the typechecker should do every possible check to produce all errors
/// If fullTypeCheck === false, the typechecker can take shortcuts and skip checks that only produce errors.
@@ -5183,7 +5184,7 @@ module ts {
return unknownType;
}
- function resolveUntypedCall(node: CallExpression | TaggedTemplateExpression): Signature {
+ function resolveUntypedCall(node: CallLikeExpression): Signature {
if (node.kind === SyntaxKind.TaggedTemplateExpression) {
checkExpression((node).template);
}
@@ -5195,52 +5196,78 @@ module ts {
return anySignature;
}
- function resolveErrorCall(node: CallExpression | TaggedTemplateExpression): Signature {
+ function resolveErrorCall(node: CallLikeExpression): Signature {
resolveUntypedCall(node);
return unknownSignature;
}
- function signatureHasCorrectArity(node: CallExpression | TaggedTemplateExpression, args: Expression[], signature: Signature): boolean {
- var isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression;
-
- if (!isTaggedTemplate && !(node).arguments) {
- // This only happens when we have something of the form:
- // new C
- //
- Debug.assert(node.kind === SyntaxKind.NewExpression);
- return signature.minArgumentCount === 0;
- }
-
- // For IDE scenarios, since we may have an incomplete call, we make two modifications
- // to arity checking.
- // 1. A trailing comma is tantamount to adding another argument
- // 2. If the call is incomplete (no closing paren) allow fewer arguments than expected
- var numberOfArgs = !isTaggedTemplate && (node).arguments.hasTrailingComma
- ? args.length + 1
- : args.length;
- var hasTooManyArguments = !signature.hasRestParameter && numberOfArgs > signature.parameters.length;
- var hasRightNumberOfTypeArguments = !(node).typeArguments ||
- (signature.typeParameters && (node).typeArguments.length === signature.typeParameters.length);
-
- if (hasTooManyArguments || !hasRightNumberOfTypeArguments) {
- return false;
- }
-
- // If we are missing the close paren, the call is incomplete, and we should skip
- // the lower bound check.
+ function hasCorrectArity(node: CallLikeExpression, args: Expression[], signature: Signature) {
+ var adjustedArgCount: number;
+ var typeArguments: NodeArray;
var callIsIncomplete = false;
- if (isTaggedTemplate) {
- var template = (node).template;
- if (template.kind === SyntaxKind.TemplateExpression) {
- var lastSpan = lastOrUndefined((template).templateSpans)
+
+ if (node.kind === SyntaxKind.TaggedTemplateExpression) {
+ var tagExpression = node;
+
+ adjustedArgCount = args.length;
+ typeArguments = undefined;
+
+ if (tagExpression.kind === SyntaxKind.TemplateExpression) {
+ // If a tagged template expression lacks a tail literal, the call is incomplete.
+ var template = tagExpression.template;
+ var lastSpan = lastOrUndefined(template.templateSpans);
+ Debug.assert(lastSpan !== undefined); // we should always have at least one span.
callIsIncomplete = lastSpan === undefined || lastSpan.literal.kind !== SyntaxKind.TemplateTail;
}
}
else {
- callIsIncomplete = (node).arguments.end === node.end;
+ var callExpression = node;
+ if (!callExpression.arguments) {
+ // This only happens when we have something of the form: 'new C'
+ Debug.assert(callExpression.kind === SyntaxKind.NewExpression);
+
+ return signature.minArgumentCount === 0;
+ }
+ else {
+ // For IDE scenarios we may have an incomplete call, so a trailing comma is tantamount to adding another argument.
+ adjustedArgCount = callExpression.arguments.hasTrailingComma ? args.length + 1 : args.length;
+
+ // If we are missing the close paren, the call is incomplete.
+ callIsIncomplete = (callExpression).arguments.end === callExpression.end;
+ }
+
+ typeArguments = callExpression.typeArguments;
+ }
+
+ return checkArity(adjustedArgCount, typeArguments, callIsIncomplete, signature);
+
+ /**
+ * @param adjustedArgCount The "apparent" number of arguments that we will have in this call.
+ * @param typeArguments Type arguments node of the call if it exists; undefined otherwise.
+ * @param callIsIncomplete Whether or not a call is unfinished, and we should be "lenient" when we have too few arguments.
+ * @param signature The signature whose arity we are comparing.
+ */
+ function checkArity(adjustedArgCount: number,
+ typeArguments: NodeArray,
+ callIsIncomplete: boolean,
+ signature: Signature): boolean {
+ // Too many arguments implies incorrect arity.
+ if (!signature.hasRestParameter && adjustedArgCount > signature.parameters.length) {
+ return false;
+ }
+
+ // If the user supplied type arguments, but the number of type arguments does not match
+ // the declared number of type parameters, the call has an incorrect arity.
+ var hasRightNumberOfTypeArgs = !typeArguments ||
+ (signature.typeParameters && typeArguments.length === signature.typeParameters.length);
+ if (!hasRightNumberOfTypeArgs) {
+ return false;
+ }
+
+ // If the call is incomplete, we should skip the lower bound check.
+ var hasEnoughArguments = adjustedArgCount >= signature.minArgumentCount;
+ return callIsIncomplete || hasEnoughArguments;
}
- var hasEnoughArguments = numberOfArgs >= signature.minArgumentCount;
- return callIsIncomplete || hasEnoughArguments;
}
// If type has a single call signature and no other members, return that signature. Otherwise, return undefined.
@@ -5332,7 +5359,7 @@ module ts {
return typeArgumentsAreAssignable;
}
- function checkApplicableSignature(node: CallExpression | TaggedTemplateExpression, args: Node[], signature: Signature, relation: Map, excludeArgument: boolean[], reportErrors: boolean) {
+ function checkApplicableSignature(node: CallLikeExpression, args: Node[], signature: Signature, relation: Map, excludeArgument: boolean[], reportErrors: boolean) {
for (var i = 0; i < args.length; i++) {
var arg = args[i];
var argType: Type;
@@ -5371,16 +5398,18 @@ module ts {
*
* If 'node' is a CallExpression or a NewExpression, then its argument list is returned.
* If 'node' is a TaggedTemplateExpression, a new argument list is constructed from the substitution
- * expressions, where the first element of the argument list is the template portion for error reporting purposes.
+ * expressions, where the first element of the list is the template for error reporting purposes.
*/
- function getEffectiveCallArguments(node: CallExpression | TaggedTemplateExpression): Expression[] {
+ function getEffectiveCallArguments(node: CallLikeExpression): Expression[] {
var args: Expression[];
if (node.kind === SyntaxKind.TaggedTemplateExpression) {
var template = (node).template;
args = [template];
if (template.kind === SyntaxKind.TemplateExpression) {
- args.push.apply(args, map((template).templateSpans, span => span.expression));
+ forEach((template).templateSpans, span => {
+ args.push(span.expression);
+ });
}
}
else {
@@ -5390,7 +5419,7 @@ module ts {
return args;
}
- function resolveCall(node: CallExpression | TaggedTemplateExpression, signatures: Signature[], candidatesOutArray: Signature[]): Signature {
+ function resolveCall(node: CallLikeExpression, signatures: Signature[], candidatesOutArray: Signature[]): Signature {
var typeArguments = (node).typeArguments;
forEach(typeArguments, checkSourceElement);
@@ -5410,7 +5439,12 @@ module ts {
// - undefined: the argument at 'i' is *not* susceptible to permanent contextual typing.
// - false: the argument at 'i' *was* and *has been* permanently contextually typed.
//
- // If the expression is a tagged template, then the first argument is implicitly the "cooked" strings array.
+ // The idea is that we will perform type argument inference & assignability checking once
+ // without using the susceptible parameters, and once more for each susceptible parameter,
+ // contextually typing each as we go along.
+ //
+ // For a tagged template, then the first argument be 'undefined' if necessary
+ // because it represents a TemplateStringsArray.
var excludeArgument: boolean[];
for (var i = isTaggedTemplate ? 1 : 0; i < args.length; i++) {
if (isContextSensitiveExpression(args[i])) {
@@ -5484,7 +5518,7 @@ module ts {
checkApplicableSignature(node, args, candidateForArgumentError, assignableRelation, /*excludeArgument*/ undefined, /*reportErrors*/ true);
}
else if (candidateForTypeArgumentError) {
- if ((node).typeArguments) {
+ if (!isTaggedTemplate && (node).typeArguments) {
checkTypeArguments(candidateForTypeArgumentError, (node).typeArguments, [], /*reportErrors*/ true)
}
else {
@@ -5510,7 +5544,7 @@ module ts {
// f({ |
if (!fullTypeCheck) {
for (var i = 0, n = candidates.length; i < n; i++) {
- if (signatureHasCorrectArity(node, args, candidates[i])) {
+ if (hasCorrectArity(node, args, candidates[i])) {
return candidates[i];
}
}
@@ -5520,7 +5554,7 @@ module ts {
function chooseOverload(candidates: Signature[], relation: Map, excludeArgument: boolean[]) {
for (var i = 0; i < candidates.length; i++) {
- if (!signatureHasCorrectArity(node, args, candidates[i])) {
+ if (!hasCorrectArity(node, args, candidates[i])) {
continue;
}
@@ -5751,7 +5785,7 @@ module ts {
// candidatesOutArray is passed by signature help in the language service, and collectCandidates
// must fill it up with the appropriate candidate signatures
- function getResolvedSignature(node: CallExpression | TaggedTemplateExpression, candidatesOutArray?: Signature[]): Signature {
+ function getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[]): Signature {
var links = getNodeLinks(node);
// If getResolvedSignature has already been called, we will have cached the resolvedSignature.
// However, it is possible that either candidatesOutArray was not passed in the first time,