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,