From aebb8069551fd927b2b9b28d608a2538c270ee1a Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 22 Nov 2016 00:09:38 -0500 Subject: [PATCH] Type check for bind, polish emit for pipeline --- src/compiler/checker.ts | 95 +++++++++++++++++++++----- src/compiler/parser.ts | 3 + src/compiler/transformers/esnext.ts | 100 +++++++++++++++------------- src/compiler/types.ts | 4 +- 4 files changed, 136 insertions(+), 66 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ace0f9e0461..d1417a9383a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4304,6 +4304,17 @@ namespace ts { sig.typePredicate, sig.minArgumentCount, sig.hasRestParameter, sig.hasLiteralTypes); } + function getSignatureWithoutThis(sig: Signature) { + if (sig.thisParameter) { + if (!sig.thisFreeSignatureCache) { + sig.thisFreeSignatureCache = cloneSignature(sig); + sig.thisFreeSignatureCache.thisParameter = undefined; + } + return sig.thisFreeSignatureCache; + } + return sig; + } + function getDefaultConstructSignatures(classType: InterfaceType): Signature[] { const baseConstructorType = getBaseConstructorTypeOfClass(classType); const baseSignatures = getSignaturesOfType(baseConstructorType, SignatureKind.Construct); @@ -5195,6 +5206,25 @@ namespace ts { return signature.erasedSignatureCache; } + function createTypeFromSignatures(signatures: Signature[]): ObjectType { + const type = createObjectType(ObjectFlags.Anonymous); + type.members = emptySymbols; + type.properties = emptyArray; + let callSignatures: Signature[]; + let constructSignatures: Signature[]; + for (const signature of signatures) { + if (signature.isConstruct) { + constructSignatures = append(constructSignatures, signature); + } + else { + callSignatures = append(callSignatures, signature); + } + } + type.callSignatures = callSignatures || emptyArray; + type.constructSignatures = constructSignatures || emptyArray; + return type; + } + function getOrCreateTypeFromSignature(signature: Signature): ObjectType { // There are two ways to declare a construct signature, one is by declaring a class constructor // using the constructor keyword, and the other is declaring a bare construct signature in an @@ -12186,6 +12216,12 @@ namespace ts { return getIndexedAccessType(objectType, indexType, node); } + function checkBindExpression(node: BindExpression): Type { + checkNonNullExpression(node.expression); + const signatures = getResolvedPartialSignatures(node); + return createTypeFromSignatures(map(signatures, getSignatureWithoutThis)); + } + function checkThatExpressionIsProperSymbolReference(expression: Expression, expressionType: Type, reportError: boolean): boolean { if (expressionType === unknownType) { // There is already an error, so no need to report one. @@ -12232,7 +12268,7 @@ namespace ts { if (node.kind === SyntaxKind.TaggedTemplateExpression) { checkExpression((node).template); } - else if (node.kind !== SyntaxKind.Decorator) { + else if (node.kind === SyntaxKind.CallExpression || node.kind === SyntaxKind.NewExpression) { forEach((node).arguments, argument => { checkExpression(argument); }); @@ -12313,7 +12349,6 @@ namespace ts { let argCount: number; // Apparent number of arguments we will have in this call let typeArguments: NodeArray; // Type arguments (undefined if none) let callIsIncomplete: boolean; // In incomplete call we want to be lenient when we have too few arguments - let isDecorator: boolean; let spreadArgIndex = -1; if (node.kind === SyntaxKind.TaggedTemplateExpression) { @@ -12341,8 +12376,7 @@ namespace ts { callIsIncomplete = !!templateLiteral.isUnterminated; } } - else if (node.kind === SyntaxKind.Decorator) { - isDecorator = true; + else if (node.kind === SyntaxKind.Decorator || node.kind === SyntaxKind.BindExpression) { typeArguments = undefined; argCount = getEffectiveArgumentCount(node, /*args*/ undefined, signature); } @@ -12574,6 +12608,9 @@ namespace ts { return (callee as ElementAccessExpression).expression; } } + else if (node.kind === SyntaxKind.BindExpression) { + return node.expression; + } } /** @@ -12596,7 +12633,7 @@ namespace ts { }); } } - else if (node.kind === SyntaxKind.Decorator) { + else if (node.kind === SyntaxKind.Decorator || node.kind === SyntaxKind.BindExpression) { // For a decorator, we return undefined as we will determine // the number and types of arguments for a decorator using // `getEffectiveArgumentCount` and `getEffectiveArgumentType` below. @@ -12675,6 +12712,9 @@ namespace ts { return 3; } } + else if (node.kind === SyntaxKind.BindExpression) { + return signature.minArgumentCount; + } else { return args.length; } @@ -12859,6 +12899,9 @@ namespace ts { if (node.kind === SyntaxKind.Decorator) { return getEffectiveDecoratorArgumentType(node, argIndex); } + else if (node.kind === SyntaxKind.BindExpression) { + return unknownType; + } else if (argIndex === 0 && node.kind === SyntaxKind.TaggedTemplateExpression) { return getGlobalTemplateStringsArrayType(); } @@ -12874,6 +12917,7 @@ namespace ts { function getEffectiveArgument(node: CallLikeExpression, args: Expression[], argIndex: number) { // For a decorator or the first argument of a tagged template expression we return undefined. if (node.kind === SyntaxKind.Decorator || + node.kind === SyntaxKind.BindExpression || (argIndex === 0 && node.kind === SyntaxKind.TaggedTemplateExpression)) { return undefined; } @@ -12902,10 +12946,11 @@ namespace ts { const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression; const isDecorator = node.kind === SyntaxKind.Decorator; const isPipeline = node.kind === SyntaxKind.BinaryExpression; + const isBind = node.kind === SyntaxKind.BindExpression; let typeArguments: TypeNode[]; - if (!isTaggedTemplate && !isDecorator && !isPipeline) { + if (!isTaggedTemplate && !isDecorator && !isPipeline && !isBind) { typeArguments = (node).typeArguments; // We already perform checking on the type arguments on the class declaration itself. @@ -12939,7 +12984,7 @@ namespace ts { // For a decorator, no arguments are susceptible to contextual typing due to the fact // decorators are applied to a declaration by the emitter, and not to an expression. let excludeArgument: boolean[]; - if (!isDecorator) { + if (!isDecorator && !isBind) { // We do not need to call `getEffectiveArgumentCount` here as it only // applies when calculating the number of arguments for a decorator. for (let i = isTaggedTemplate ? 1 : 0; i < args.length; i++) { @@ -13420,7 +13465,6 @@ namespace ts { } function resolvePipelineExpression(node: PipelineExpression, candidatesOutArray: Signature[]): Signature { - (Error).stackTraceLimit = Infinity; const funcType = checkExpression(node.right); const apparentType = getApparentType(funcType); if (apparentType === unknownType) { @@ -13440,6 +13484,26 @@ namespace ts { return resolveCall(node, callSignatures, /*partialSignaturesOutArray*/ undefined, candidatesOutArray); } + function resolveBindExpression(node: BindExpression, partialSignaturesOutArray: Signature[], candidatesOutArray?: Signature[]): Signature { + const funcType = checkExpression(node.targetExpression); + const apparentType = getApparentType(funcType); + if (apparentType === unknownType) { + return resolveErrorCall(node); + } + + const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); + const constructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct); + if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, constructSignatures.length)) { + return resolveUntypedCall(node); + } + + if (!callSignatures.length) { + return resolveErrorCall(node); + } + + return resolveCall(node, callSignatures, partialSignaturesOutArray, candidatesOutArray); + } + function resolveSignature(node: CallLikeExpression, partialSignaturesOutArray: Signature[], candidatesOutArray?: Signature[]): Signature { switch (node.kind) { case SyntaxKind.CallExpression: @@ -13452,6 +13516,8 @@ namespace ts { return resolveDecorator(node, candidatesOutArray); case SyntaxKind.BinaryExpression: return resolvePipelineExpression(node, candidatesOutArray); + case SyntaxKind.BindExpression: + return resolveBindExpression(node, partialSignaturesOutArray, candidatesOutArray); } Debug.fail("Branch in 'resolveSignature' should be unreachable."); } @@ -14238,19 +14304,12 @@ namespace ts { function createOperatorExpressionType(operator: SyntaxKind) { switch (operator) { case SyntaxKind.PlusToken: - const signatures = [ + return createTypeFromSignatures([ createBinaryOperatorSignature(undefined, stringType, stringType, stringType), createBinaryOperatorSignature(undefined, stringType, numberType, stringType), createBinaryOperatorSignature(undefined, numberType, stringType, stringType), createBinaryOperatorSignature(undefined, numberType, numberType, numberType), - ] - const plusType = createObjectType(ObjectFlags.Anonymous); - (plusType).members = emptySymbols; - (plusType).properties = emptyArray; - (plusType).callSignatures = signatures; - (plusType).constructSignatures = emptyArray; - return plusType; - + ]); case SyntaxKind.AsteriskToken: case SyntaxKind.AsteriskAsteriskToken: case SyntaxKind.SlashToken: @@ -15249,6 +15308,8 @@ namespace ts { return checkPropertyAccessExpression(node); case SyntaxKind.ElementAccessExpression: return checkIndexedAccess(node); + case SyntaxKind.BindExpression: + return checkBindExpression(node); case SyntaxKind.CallExpression: case SyntaxKind.NewExpression: return checkCallExpression(node); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index f3cb1a18535..43be7cc7b58 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -161,6 +161,9 @@ namespace ts { case SyntaxKind.ElementAccessExpression: return visitNode(cbNode, (node).expression) || visitNode(cbNode, (node).argumentExpression); + case SyntaxKind.BindExpression: + return visitNode(cbNode, (node).expression) || + visitNode(cbNode, (node).targetExpression); case SyntaxKind.CallExpression: case SyntaxKind.NewExpression: return visitNode(cbNode, (node).expression) || diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index 89e7f266dd9..f4b666e5f78 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -5,9 +5,9 @@ namespace ts { export function transformESNext(context: TransformationContext) { const { - startLexicalEnvironment, resumeLexicalEnvironment, - endLexicalEnvironment + endLexicalEnvironment, + hoistVariableDeclaration, } = context; return transformSourceFile; @@ -441,60 +441,64 @@ namespace ts { } function visitCallExpression(node: CallExpression): Expression { - const expression = visitNode(node.expression, visitor, isExpression); - let position = 0; - let positionalParameters: ParameterDeclaration[]; - let positionalRestParameter: ParameterDeclaration; - let argumentList: Expression[]; - for (let i = 0; i < node.arguments.length; i++) { - const argument = node.arguments[i]; - let updated: Expression; - if (isPositionalElement(argument)) { - if (!positionalParameters) { - positionalParameters = []; - } - if (argument.literal) { - position = +argument.literal.text; - } - const parameter = positionalParameters[position] || (positionalParameters[position] = createParameter()); - updated = parameter.name; - position++; - } - else if (isPositionalSpreadElement(argument)) { - const parameter = positionalRestParameter || (positionalRestParameter = createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, createToken(SyntaxKind.DotDotDotToken))); - updated = createSpreadElement(parameter.name); - } - else { - updated = visitNode(argument, visitor, isExpression); - } - if (argumentList || updated !== argument) { - if (!argumentList) { - argumentList = node.arguments.slice(0, i); - } - argumentList.push(updated); - } + let expression = visitNode(node.expression, visitor, isExpression) as Expression; + if (!forEach(node.arguments, isPositionalOrPositionalSpreadElement)) { + return updateCall( + node, + expression, + /*typeArguments*/ undefined, + visitNodes(node.arguments, visitor, isExpression)); } - if (positionalParameters || positionalRestParameter) { - startLexicalEnvironment(); + else { + const expressionTemp = createTempVariable(hoistVariableDeclaration); + const argumentList: Expression[] = []; + const pendingExpressions: Expression[] = [createAssignment(expressionTemp, expression)]; + const positionalParameters: ParameterDeclaration[] = []; + let positionalRestParameter: ParameterDeclaration; + let position = 0; + for (let i = 0; i < node.arguments.length; i++) { + let argument = node.arguments[i]; + if (isPositionalElement(argument)) { + if (argument.literal) { + position = +argument.literal.text; + } + const parameter = positionalParameters[position] || (positionalParameters[position] = createParameter()); + argument = parameter.name; + position++; + } + else if (isPositionalSpreadElement(argument)) { + const parameter = positionalRestParameter || (positionalRestParameter = createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, createToken(SyntaxKind.DotDotDotToken))); + argument = createSpreadElement(parameter.name); + } + else { + argument = visitNode(argument, visitor, isExpression); + const temp = createTempVariable(hoistVariableDeclaration); + pendingExpressions.push(createAssignment(temp, isSpreadElement(argument) ? argument.expression : argument)); + argument = isSpreadElement(argument) ? createSpreadElement(temp) : temp; + } + argumentList.push(argument); + } if (positionalRestParameter) { - positionalParameters = append(positionalParameters, positionalRestParameter); + positionalParameters.push(positionalRestParameter); } for (let i = 0; i < positionalParameters.length; i++) { if (!positionalParameters[i]) { positionalParameters[i] = createParameter(); } } - return createArrowFunction( - /*modifiers*/ undefined, - /*typeParameters*/ undefined, - positionalParameters, - /*type*/ undefined, - /*equalsGreaterThanToken*/ createToken(SyntaxKind.EqualsGreaterThanToken), - updateCall(node, expression, /*typeArguments*/ undefined, argumentList || node.arguments), - /*location*/ node + pendingExpressions.push( + createArrowFunction( + /*modifiers*/ undefined, + /*typeParameters*/ undefined, + positionalParameters, + /*type*/ undefined, + /*equalsGreaterThanToken*/ createToken(SyntaxKind.EqualsGreaterThanToken), + updateCall(node, expressionTemp, /*typeArguments*/ undefined, argumentList), + /*location*/ node + ) ); + return inlineExpressions(pendingExpressions); } - return updateCall(node, expression, /*typeArguments*/ undefined, argumentList || node.arguments); } function visitOperatorExpression(node: OperatorExpression) { @@ -542,7 +546,7 @@ namespace ts { function visitBindExpression(node: BindExpression) { const thisArg = createTempVariable(context.hoistVariableDeclaration); - return inlineExpressions([ + return createComma( createAssignment( thisArg, visitNode(node.expression, visitor, isExpression), @@ -554,7 +558,7 @@ namespace ts { [], node ) - ]); + ); } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 4bdbbe99783..2b82ed51a8c 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1456,7 +1456,7 @@ namespace ts { template: TemplateLiteral; } - export type CallLikeExpression = CallExpression | NewExpression | TaggedTemplateExpression | Decorator | PipelineExpression; + export type CallLikeExpression = CallExpression | NewExpression | TaggedTemplateExpression | Decorator | PipelineExpression | BindExpression; export interface PositionalElement extends Expression { kind: SyntaxKind.PositionalElement; @@ -3053,6 +3053,8 @@ namespace ts { /* @internal */ erasedSignatureCache?: Signature; // Erased version of signature (deferred) /* @internal */ + thisFreeSignatureCache?: Signature; // Signature without 'this' parameter. + /* @internal */ isolatedSignatureType?: ObjectType; // A manufactured type that just contains the signature for purposes of signature comparison /* @internal */ typePredicate?: TypePredicate;