From 56027663bf8010e80ba3ce29a0f642a3adcd6642 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Tue, 4 Nov 2014 15:05:05 -0800 Subject: [PATCH 1/7] Initial work on overload resolution with tagged templates. Currently type argument inference breaks hard when the first parameter of a tag has a generic type. --- src/compiler/checker.ts | 206 +++++++++++++----- src/compiler/core.ts | 11 + src/lib/core.d.ts | 4 + .../reference/noDefaultLib.errors.txt | 2 + .../reference/parser509698.errors.txt | 2 + .../noDefaultLib/amd/noDefaultLib.errors.txt | 2 + .../noDefaultLib/node/noDefaultLib.errors.txt | 2 + ...tringsWithIncompatibleTypedTags.errors.txt | 28 ++- ...ngsWithIncompatibleTypedTagsES6.errors.txt | 53 +++++ ...lateStringsWithIncompatibleTypedTagsES6.js | 6 +- ...eStringsWithIncompatibleTypedTagsES6.types | 82 ------- ...ithManyCallAndMemberExpressions.errors.txt | 2 +- ...ingsWithManyCallAndMemberExpressionsES6.js | 2 +- ...sWithManyCallAndMemberExpressionsES6.types | 12 +- ...eStringsWithOverloadResolution1.errors.txt | 55 +++++ ...ingsWithOverloadResolution1_ES6.errors.txt | 37 ++++ ...plateStringsWithOverloadResolution1_ES6.js | 44 ++++ ...aggedTemplateStringsWithTypedTagsES6.types | 24 +- .../templateStringInObjectLiteral.errors.txt | 7 +- ...emplateStringInObjectLiteralES6.errors.txt | 7 +- .../templateStringInPropertyName1.errors.txt | 6 +- .../templateStringInPropertyName2.errors.txt | 6 +- ...mplateStringInPropertyNameES6_1.errors.txt | 6 +- ...mplateStringInPropertyNameES6_2.errors.txt | 6 +- .../typeCheckTypeArgument.errors.txt | 2 + ...emplateStringsWithIncompatibleTypedTags.ts | 2 + ...lateStringsWithIncompatibleTypedTagsES6.ts | 4 +- ...StringsWithManyCallAndMemberExpressions.ts | 2 +- ...ingsWithManyCallAndMemberExpressionsES6.ts | 2 +- ...dTemplateStringsWithOverloadResolution1.ts | 21 ++ ...plateStringsWithOverloadResolution1_ES6.ts | 22 ++ 31 files changed, 500 insertions(+), 167 deletions(-) create mode 100644 tests/baselines/reference/taggedTemplateStringsWithIncompatibleTypedTagsES6.errors.txt delete mode 100644 tests/baselines/reference/taggedTemplateStringsWithIncompatibleTypedTagsES6.types create mode 100644 tests/baselines/reference/taggedTemplateStringsWithOverloadResolution1.errors.txt create mode 100644 tests/baselines/reference/taggedTemplateStringsWithOverloadResolution1_ES6.errors.txt create mode 100644 tests/baselines/reference/taggedTemplateStringsWithOverloadResolution1_ES6.js create mode 100644 tests/cases/conformance/es6/templates/taggedTemplateStringsWithOverloadResolution1.ts create mode 100644 tests/cases/conformance/es6/templates/taggedTemplateStringsWithOverloadResolution1_ES6.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9f612e11673b6..7b49753577821 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -147,6 +147,7 @@ module ts { var globalNumberType: ObjectType; var globalBooleanType: ObjectType; var globalRegExpType: ObjectType; + var globalTemplateStringsArrayType: ObjectType; var tupleTypes: Map = {}; var unionTypes: Map = {}; @@ -5155,23 +5156,31 @@ module ts { return unknownType; } - function resolveUntypedCall(node: CallExpression): Signature { - forEach(node.arguments, argument => { - checkExpression(argument); - }); + function resolveUntypedCall(node: CallExpression | TaggedTemplateExpression): Signature { + if (node.kind === SyntaxKind.TaggedTemplateExpression) { + checkExpression((node).template); + } + else { + forEach((node).arguments, argument => { + checkExpression(argument); + }); + } return anySignature; } - function resolveErrorCall(node: CallExpression): Signature { + function resolveErrorCall(node: CallExpression | TaggedTemplateExpression): Signature { resolveUntypedCall(node); return unknownSignature; } - function signatureHasCorrectArity(node: CallExpression, signature: Signature): boolean { - if (!node.arguments) { + 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; } @@ -5179,11 +5188,12 @@ module ts { // 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 args = node.arguments; - var numberOfArgs = args.hasTrailingComma ? args.length + 1 : args.length; + 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); + var hasRightNumberOfTypeArguments = !(node).typeArguments || + (signature.typeParameters && (node).typeArguments.length === signature.typeParameters.length); if (hasTooManyArguments || !hasRightNumberOfTypeArguments) { return false; @@ -5191,7 +5201,17 @@ module ts { // If we are missing the close paren, the call is incomplete, and we should skip // the lower bound check. - var callIsIncomplete = args.end === node.end; + var callIsIncomplete = false; + if (isTaggedTemplate) { + var template = (node).template; + if (template.kind === SyntaxKind.TemplateExpression) { + var lastSpan = lastOrUndefined((template).templateSpans) + callIsIncomplete = lastSpan === undefined || lastSpan.literal.kind !== SyntaxKind.TemplateTail; + } + } + else { + callIsIncomplete = (node).arguments.end === node.end; + } var hasEnoughArguments = numberOfArgs >= signature.minArgumentCount; return callIsIncomplete || hasEnoughArguments; } @@ -5224,6 +5244,7 @@ module ts { var mapper = createInferenceMapper(context); // First infer from arguments that are not context sensitive for (var i = 0; i < args.length; i++) { + // TODO (drosen): This breaks hard when inferring on a tagged template. if (args[i].kind === SyntaxKind.OmittedExpression) { continue; } @@ -5277,31 +5298,69 @@ module ts { return typeArgumentsAreAssignable; } - function checkApplicableSignature(node: CallExpression, signature: Signature, relation: Map, excludeArgument: boolean[], reportErrors: boolean) { - if (node.arguments) { - for (var i = 0; i < node.arguments.length; i++) { - var arg = node.arguments[i]; - if (arg.kind === SyntaxKind.OmittedExpression) { - continue; - } - var paramType = getTypeAtPosition(signature, i); + function checkApplicableSignature(node: CallExpression | TaggedTemplateExpression, callArguments: Node[], signature: Signature, relation: Map, excludeArgument: boolean[], reportErrors: boolean) { + for (var i = 0; i < callArguments.length; i++) { + var arg = callArguments[i]; + var argType: Type; + + if (arg && arg.kind === SyntaxKind.OmittedExpression) { + continue; + } + + var paramType = getTypeAtPosition(signature, i); + + if (i === 0 && node.kind === SyntaxKind.TaggedTemplateExpression) { + arg = (node).template; // just to report an error on the template + argType = globalTemplateStringsArrayType; + } + else { // String literals get string literal types unless we're reporting errors - var argType = arg.kind === SyntaxKind.StringLiteral && !reportErrors ? - getStringLiteralType(arg) : - checkExpressionWithContextualType(arg, paramType, excludeArgument && excludeArgument[i] ? identityMapper : undefined); - // Use argument expression as error location when reporting errors - var isValidArgument = checkTypeRelatedTo(argType, paramType, relation, reportErrors ? arg : undefined, - Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1); - if (!isValidArgument) { - return false; - } + argType = arg.kind === SyntaxKind.StringLiteral && !reportErrors + ? getStringLiteralType(arg) + : checkExpressionWithContextualType(arg, paramType, excludeArgument && excludeArgument[i] ? identityMapper : undefined); + } + + // Use argument expression as error location when reporting errors + var isValidArgument = checkTypeRelatedTo(argType, paramType, relation, reportErrors ? arg : undefined, + Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1); + if (!isValidArgument) { + return false; } } + return true; } - function resolveCall(node: CallExpression, signatures: Signature[], candidatesOutArray: Signature[]): Signature { - forEach(node.typeArguments, checkSourceElement); + /** + * Returns the effective arguments for an expression that works like a function invokation. + * + * 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 'undefined' to represent the "cooked" strings array. + */ + function getEffectiveCallArguments(node: CallExpression | TaggedTemplateExpression): Expression[] { + var args: Expression[]; + if (node.kind === SyntaxKind.TaggedTemplateExpression) { + var template = (node).template; + // REVIEW: Should this be undefined or template? + // I currently use 'undefined' mostly to catch places we are not accounting for. + args = [undefined]; + + if (template.kind === SyntaxKind.TemplateExpression) { + args.push.apply(args, map((template).templateSpans, span => span.expression)); + } + } + else { + args = (node).arguments || emptyArray; + } + + return args; + } + + function resolveCall(node: CallExpression | TaggedTemplateExpression, signatures: Signature[], candidatesOutArray: Signature[]): Signature { + var typeArguments = (node).typeArguments; + forEach(typeArguments, checkSourceElement); + var candidates = candidatesOutArray || []; // collectCandidates fills up the candidates array directly collectCandidates(); @@ -5309,11 +5368,22 @@ module ts { error(node, Diagnostics.Supplied_parameters_do_not_match_any_signature_of_call_target); return resolveErrorCall(node); } - var args = node.arguments || emptyArray; + + var args = getEffectiveCallArguments(node); + var isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression; + + // The following applies to any value of 'excludeArgument[i]': + // - true: the argument at 'i' is susceptible to a one-time permanent contextual typing. + // - 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. var excludeArgument: boolean[]; - for (var i = 0; i < args.length; i++) { + for (var i = isTaggedTemplate ? 1 : 0; i < args.length; i++) { if (isContextSensitiveExpression(args[i])) { - if (!excludeArgument) excludeArgument = new Array(args.length); + if (!excludeArgument) { + excludeArgument = new Array(args.length); + } excludeArgument[i] = true; } } @@ -5378,11 +5448,11 @@ module ts { // in arguments too early. If possible, we'd like to only type them once we know the correct // overload. However, this matters for the case where the call is correct. When the call is // an error, we don't need to exclude any arguments, although it would cause no harm to do so. - checkApplicableSignature(node, candidateForArgumentError, assignableRelation, /*excludeArgument*/ undefined, /*reportErrors*/ true); + checkApplicableSignature(node, args, candidateForArgumentError, assignableRelation, /*excludeArgument*/ undefined, /*reportErrors*/ true); } else if (candidateForTypeArgumentError) { - if (node.typeArguments) { - checkTypeArguments(candidateForTypeArgumentError, node.typeArguments, [], /*reportErrors*/ true) + if ((node).typeArguments) { + checkTypeArguments(candidateForTypeArgumentError, (node).typeArguments, [], /*reportErrors*/ true) } else { Debug.assert(resultOfFailedInference.failedTypeParameterIndex >= 0); @@ -5393,7 +5463,7 @@ module ts { Diagnostics.The_type_argument_for_type_parameter_0_cannot_be_inferred_from_the_usage_Consider_specifying_the_type_arguments_explicitly, typeToString(failedTypeParameter)); - reportNoCommonSupertypeError(inferenceCandidates, node.func, diagnosticChainHead); + reportNoCommonSupertypeError(inferenceCandidates, (node).func || (node).tag, diagnosticChainHead); } } else { @@ -5407,7 +5477,7 @@ module ts { // f({ | if (!fullTypeCheck) { for (var i = 0, n = candidates.length; i < n; i++) { - if (signatureHasCorrectArity(node, candidates[i])) { + if (signatureHasCorrectArity(node, args, candidates[i])) { return candidates[i]; } } @@ -5417,7 +5487,7 @@ module ts { function chooseOverload(candidates: Signature[], relation: Map, excludeArgument: boolean[]) { for (var i = 0; i < candidates.length; i++) { - if (!signatureHasCorrectArity(node, candidates[i])) { + if (!signatureHasCorrectArity(node, args, candidates[i])) { continue; } @@ -5429,9 +5499,9 @@ module ts { if (candidate.typeParameters) { var typeArgumentTypes: Type[]; var typeArgumentsAreValid: boolean; - if (node.typeArguments) { + if ((node).typeArguments) { typeArgumentTypes = new Array(candidate.typeParameters.length); - typeArgumentsAreValid = checkTypeArguments(candidate, node.typeArguments, typeArgumentTypes, /*reportErrors*/ false) + typeArgumentsAreValid = checkTypeArguments(candidate, (node).typeArguments, typeArgumentTypes, /*reportErrors*/ false) } else { inferenceResult = inferTypeArguments(candidate, args, excludeArgument); @@ -5443,7 +5513,7 @@ module ts { } candidate = getSignatureInstantiation(candidate, typeArgumentTypes); } - if (!checkApplicableSignature(node, candidate, relation, excludeArgument, /*reportErrors*/ false)) { + if (!checkApplicableSignature(node, args, candidate, relation, excludeArgument, /*reportErrors*/ false)) { break; } var index = excludeArgument ? indexOf(excludeArgument, true) : -1; @@ -5465,7 +5535,7 @@ module ts { } else { candidateForTypeArgumentError = originalCandidate; - if (!node.typeArguments) { + if (!(node).typeArguments) { resultOfFailedInference = inferenceResult; } } @@ -5576,7 +5646,6 @@ module ts { function resolveNewExpression(node: NewExpression, candidatesOutArray: Signature[]): Signature { var expressionType = checkExpression(node.func); - // TS 1.0 spec: 4.11 // If ConstructExpr is of type Any, Args can be any argument // list and the result of the operation is of type Any. @@ -5624,9 +5693,32 @@ module ts { return resolveErrorCall(node); } + function resolveTaggedTemplateExpression(node: TaggedTemplateExpression, candidatesOutArray: Signature[]): Signature { + var tagType = checkExpression(node.tag); + var apparentType = getApparentType(tagType); + + if (apparentType === unknownType) { + // Another error has already been reported + return resolveErrorCall(node); + } + + var callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); + + if (tagType === anyType || (!callSignatures.length && !(tagType.flags & TypeFlags.Union) && isTypeAssignableTo(tagType, globalFunctionType))) { + return resolveUntypedCall(node); + } + + if (!callSignatures.length) { + error(node, Diagnostics.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature); + return resolveErrorCall(node); + } + + return resolveCall(node, callSignatures, candidatesOutArray); + } + // 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, candidatesOutArray?: Signature[]): Signature { + function getResolvedSignature(node: CallExpression | TaggedTemplateExpression, 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, @@ -5634,9 +5726,19 @@ module ts { // to correctly fill the candidatesOutArray. if (!links.resolvedSignature || candidatesOutArray) { links.resolvedSignature = anySignature; - links.resolvedSignature = node.kind === SyntaxKind.CallExpression - ? resolveCallExpression(node, candidatesOutArray) - : resolveNewExpression(node, candidatesOutArray); + + if (node.kind === SyntaxKind.CallExpression) { + links.resolvedSignature = resolveCallExpression(node, candidatesOutArray); + } + else if (node.kind === SyntaxKind.NewExpression) { + links.resolvedSignature = resolveNewExpression(node, candidatesOutArray); + } + else if (node.kind === SyntaxKind.TaggedTemplateExpression) { + links.resolvedSignature = resolveTaggedTemplateExpression(node, candidatesOutArray); + } + else { + Debug.fail("Branch in 'getResolvedSignature' should be unreachable."); + } } return links.resolvedSignature; } @@ -5660,10 +5762,7 @@ module ts { } function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type { - // TODO (drosen): Make sure substitutions are assignable to the tag's arguments. - checkExpression(node.tag); - checkExpression(node.template); - return anyType; + return getReturnTypeOfSignature(getResolvedSignature(node)); } function checkTypeAssertion(node: TypeAssertion): Type { @@ -8752,6 +8851,7 @@ module ts { globalNumberType = getGlobalType("Number"); globalBooleanType = getGlobalType("Boolean"); globalRegExpType = getGlobalType("RegExp"); + globalTemplateStringsArrayType = getGlobalType("TemplateStringsArray"); } initializeTypeChecker(); diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 6fd07fe4a6a89..8056dc0d44929 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -122,6 +122,17 @@ module ts { return result; } + /** + * Returns the last element of an array if non-empty, undefined otherwise. + */ + export function lastOrUndefined(array: T[]): T { + if (array.length === 0) { + return undefined; + } + + return array[array.length - 1]; + } + export function binarySearch(array: number[], value: number): number { var low = 0; var high = array.length - 1; diff --git a/src/lib/core.d.ts b/src/lib/core.d.ts index 8e16e4bdd6cd5..7f498488f2136 100644 --- a/src/lib/core.d.ts +++ b/src/lib/core.d.ts @@ -484,6 +484,10 @@ declare var Number: { POSITIVE_INFINITY: number; } +interface TemplateStringsArray extends Array { + raw: string[]; +} + interface Math { /** The mathematical constant e. This is Euler's number, the base of natural logarithms. */ E: number; diff --git a/tests/baselines/reference/noDefaultLib.errors.txt b/tests/baselines/reference/noDefaultLib.errors.txt index 020989500556d..f2c23d56d25ff 100644 --- a/tests/baselines/reference/noDefaultLib.errors.txt +++ b/tests/baselines/reference/noDefaultLib.errors.txt @@ -1,10 +1,12 @@ error TS2318: Cannot find global type 'Boolean'. error TS2318: Cannot find global type 'IArguments'. +error TS2318: Cannot find global type 'TemplateStringsArray'. tests/cases/compiler/noDefaultLib.ts(4,11): error TS2317: Global type 'Array' must have 1 type parameter(s). !!! error TS2318: Cannot find global type 'Boolean'. !!! error TS2318: Cannot find global type 'IArguments'. +!!! error TS2318: Cannot find global type 'TemplateStringsArray'. ==== tests/cases/compiler/noDefaultLib.ts (1 errors) ==== /// var x; diff --git a/tests/baselines/reference/parser509698.errors.txt b/tests/baselines/reference/parser509698.errors.txt index 2aaaec18dc29c..2a8cc607e1e7a 100644 --- a/tests/baselines/reference/parser509698.errors.txt +++ b/tests/baselines/reference/parser509698.errors.txt @@ -6,6 +6,7 @@ error TS2318: Cannot find global type 'Number'. error TS2318: Cannot find global type 'Object'. error TS2318: Cannot find global type 'RegExp'. error TS2318: Cannot find global type 'String'. +error TS2318: Cannot find global type 'TemplateStringsArray'. !!! error TS2318: Cannot find global type 'Array'. @@ -16,6 +17,7 @@ error TS2318: Cannot find global type 'String'. !!! error TS2318: Cannot find global type 'Object'. !!! error TS2318: Cannot find global type 'RegExp'. !!! error TS2318: Cannot find global type 'String'. +!!! error TS2318: Cannot find global type 'TemplateStringsArray'. ==== tests/cases/conformance/parser/ecmascript5/RegressionTests/parser509698.ts (0 errors) ==== ///