From 8a765d7680fd44f17b070741f5f702688e063702 Mon Sep 17 00:00:00 2001 From: Jason Freeman Date: Thu, 11 Sep 2014 16:41:33 -0700 Subject: [PATCH] Semantic filtering of candidate signatures --- src/compiler/checker.ts | 64 +++++++++++++++--------- src/compiler/types.ts | 1 + src/services/services.ts | 105 ++++++++++++++++++++------------------- 3 files changed, 95 insertions(+), 75 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 953bd3de49a..559fdf6f90a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -66,7 +66,8 @@ module ts { getAugmentedPropertiesOfApparentType: getAugmentedPropertiesOfApparentType, getRootSymbol: getRootSymbol, getContextualType: getContextualType, - getFullyQualifiedName: getFullyQualifiedName + getFullyQualifiedName: getFullyQualifiedName, + getResolvedSignature: getResolvedSignature }; var undefinedSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "undefined"); @@ -4128,7 +4129,7 @@ module ts { return unknownSignature; } - function isCandidateSignature(node: CallExpression, signature: Signature) { + function signatureHasCorrectArity(node: CallExpression, signature: Signature) { var args = node.arguments || emptyArray; return args.length >= signature.minArgumentCount && (signature.hasRestParameter || args.length <= signature.parameters.length) && @@ -4142,15 +4143,15 @@ module ts { // interface B extends A { (x: 'foo'): string } // var b: B; // b('foo') // <- here overloads should be processed as [(x:'foo'): string, (x: string): void] - function collectCandidates(node: CallExpression, signatures: Signature[]): Signature[]{ - var result: Signature[] = []; + function collectCandidates(node: CallExpression, signatures: Signature[], candidatesOutArray: Signature[]): Signature[]{ + var result: Signature[] = candidatesOutArray || []; var lastParent: Node; var lastSymbol: Symbol; var cutoffPos: number = 0; var pos: number; for (var i = 0; i < signatures.length; i++) { var signature = signatures[i]; - if (isCandidateSignature(node, signature)) { + if (true) { var symbol = signature.declaration && getSymbolOfNode(signature.declaration); var parent = signature.declaration && signature.declaration.parent; if (!lastSymbol || symbol === lastSymbol) { @@ -4260,9 +4261,9 @@ module ts { return true; } - function resolveCall(node: CallExpression, signatures: Signature[]): Signature { + function resolveCall(node: CallExpression, signatures: Signature[], candidatesOutArray: Signature[]): Signature { forEach(node.typeArguments, checkSourceElement); - var candidates = collectCandidates(node, signatures); + var candidates = collectCandidates(node, signatures, candidatesOutArray); if (!candidates.length) { error(node, Diagnostics.Supplied_parameters_do_not_match_any_signature_of_call_target); return resolveErrorCall(node); @@ -4278,20 +4279,24 @@ module ts { var relation = candidates.length === 1 ? assignableRelation : subtypeRelation; while (true) { for (var i = 0; i < candidates.length; i++) { + if (!signatureHasCorrectArity(node, candidates[i])) { + continue; + } + while (true) { - var candidate = candidates[i]; - if (candidate.typeParameters) { + var candidateWithCorrectArity = candidates[i]; + if (candidateWithCorrectArity.typeParameters) { var typeArguments = node.typeArguments ? - checkTypeArguments(candidate, node.typeArguments) : - inferTypeArguments(candidate, args, excludeArgument); - candidate = getSignatureInstantiation(candidate, typeArguments); + checkTypeArguments(candidateWithCorrectArity, node.typeArguments) : + inferTypeArguments(candidateWithCorrectArity, args, excludeArgument); + candidateWithCorrectArity = getSignatureInstantiation(candidateWithCorrectArity, typeArguments); } - if (!checkApplicableSignature(node, candidate, relation, excludeArgument, /*reportErrors*/ false)) { + if (!checkApplicableSignature(node, candidateWithCorrectArity, relation, excludeArgument, /*reportErrors*/ false)) { break; } var index = excludeArgument ? indexOf(excludeArgument, true) : -1; if (index < 0) { - return candidate; + return candidateWithCorrectArity; } excludeArgument[index] = false; } @@ -4301,17 +4306,26 @@ module ts { } relation = assignableRelation; } + // No signatures were applicable. Now report errors based on the last applicable signature with // no arguments excluded from assignability checks. - checkApplicableSignature(node, candidate, relation, undefined, /*reportErrors*/ true); + // If candidate is undefined, it means that no candidates had a suitable arity. In that case, + // skip the checkApplicableSignature check. + if (candidateWithCorrectArity) { + checkApplicableSignature(node, candidateWithCorrectArity, relation, undefined, /*reportErrors*/ true); + } + else { + error(node, Diagnostics.Supplied_parameters_do_not_match_any_signature_of_call_target); + return resolveErrorCall(node); + } return resolveErrorCall(node); } - function resolveCallExpression(node: CallExpression): Signature { + function resolveCallExpression(node: CallExpression, candidatesOutArray: Signature[]): Signature { if (node.func.kind === SyntaxKind.SuperKeyword) { var superType = checkSuperExpression(node.func); if (superType !== unknownType) { - return resolveCall(node, getSignaturesOfType(superType, SignatureKind.Construct)); + return resolveCall(node, getSignaturesOfType(superType, SignatureKind.Construct), candidatesOutArray); } return resolveUntypedCall(node); } @@ -4359,10 +4373,10 @@ module ts { } return resolveErrorCall(node); } - return resolveCall(node, callSignatures); + return resolveCall(node, callSignatures, candidatesOutArray); } - function resolveNewExpression(node: NewExpression): Signature { + function resolveNewExpression(node: NewExpression, candidatesOutArray: Signature[]): Signature { var expressionType = checkExpression(node.func); if (expressionType === unknownType) { // Another error has already been reported @@ -4397,7 +4411,7 @@ module ts { // that the user will not add any. var constructSignatures = getSignaturesOfType(expressionType, SignatureKind.Construct); if (constructSignatures.length) { - return resolveCall(node, constructSignatures); + return resolveCall(node, constructSignatures, candidatesOutArray); } // If ConstructExpr's apparent type is an object type with no construct signatures but @@ -4406,7 +4420,7 @@ module ts { // operation is Any. var callSignatures = getSignaturesOfType(expressionType, SignatureKind.Call); if (callSignatures.length) { - var signature = resolveCall(node, callSignatures); + var signature = resolveCall(node, callSignatures, candidatesOutArray); if (getReturnTypeOfSignature(signature) !== voidType) { error(node, Diagnostics.Only_a_void_function_can_be_called_with_the_new_keyword); } @@ -4417,11 +4431,15 @@ module ts { return resolveErrorCall(node); } - function getResolvedSignature(node: CallExpression): Signature { + // 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 { var links = getNodeLinks(node); if (!links.resolvedSignature) { links.resolvedSignature = anySignature; - links.resolvedSignature = node.kind === SyntaxKind.CallExpression ? resolveCallExpression(node) : resolveNewExpression(node); + links.resolvedSignature = node.kind === SyntaxKind.CallExpression + ? resolveCallExpression(node, candidatesOutArray) + : resolveNewExpression(node, candidatesOutArray); } return links.resolvedSignature; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index d9c2a420fb6..89ead858052 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -644,6 +644,7 @@ module ts { getAugmentedPropertiesOfApparentType(type: Type): Symbol[]; getRootSymbol(symbol: Symbol): Symbol; getContextualType(node: Node): Type; + getResolvedSignature(node: CallExpression, candidatesOutArray?: Signature[]): Signature; } export interface TextWriter { diff --git a/src/services/services.ts b/src/services/services.ts index d763785ae1e..91ba4070b29 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3517,71 +3517,72 @@ module ts { return -1; } - //// Technically signature help should only be triggered on these characters - //if (node.kind !== SyntaxKind.CommaToken && node.kind !== SyntaxKind.OpenParenToken && node.kind !== SyntaxKind.LessThanToken) { - // return false; - //} - //if (node.kind === SyntaxKind.CommaToken) { - // if (node.parent.kind !== SyntaxKind.SyntaxList) { - // return false; - // } + function getSignatureHelpArgumentContext(node: Node): { + argumentNode: Node; + argumentIndex: number; + isTypeArgument: boolean; + } { + // We only want this node if it is a token and it strictly contains the current position. + // Otherwise we want the previous token + var isToken = node.kind < SyntaxKind.Missing; + if (!isToken || position <= node.getStart() || position >= node.getEnd()) { + // This is a temporary hack until we figure out our token story. + // The correct solution is to get the previous token + node = SignatureInfoHelpers.findClosestRightmostSiblingFromLeft(position, sourceFile); - // // node becomes the containing SyntaxList - // node = node.parent; - //} + if (!node) { + return undefined; + } + if (node.parent.kind === SyntaxKind.CallExpression || node.parent.kind === SyntaxKind.NewExpression) { + if (node === (node.parent).func) { + node = node.parent.getChildAt(1); + } + } + } - //// node is open paren, less than, or a syntax list containing a comma - //if (node.parent.kind === SyntaxKind.CallExpression || node.parent.kind === SyntaxKind.NewExpression) { - // return true; - //} + var signatureHelpAvailable = false; + for (var n = node; n.kind !== SyntaxKind.SourceFile; n = n.parent) { + if (n.kind === SyntaxKind.FunctionBlock) { + return undefined; + } + + var index = getArgumentIndex(n); + if (index >= 0) { + return { + argumentNode: n, + argumentIndex: index, + isTypeArgument: false + } + } + + + // TODO: Handle previous token logic + // TODO: Handle generic call with incomplete + + return undefined; + } + } synchronizeHostData(); // Decide whether to show signature help var sourceFile = getSourceFile(fileName); var node = getNodeAtPosition(sourceFile, position); - // We only want this node if it is a token and it strictly contains the current position. - // Otherwise we want the previous token - var isToken = node.kind < SyntaxKind.Missing; - if (!isToken || position <= node.getStart() || position >= node.getEnd()) { - // This is a temporary hack until we figure out our token story. - // The correct solution is to get the previous token - node = SignatureInfoHelpers.findClosestRightmostSiblingFromLeft(position, sourceFile); - - if (!node) { - return undefined; - } - if (node.parent.kind === SyntaxKind.CallExpression || node.parent.kind === SyntaxKind.NewExpression) { - if (node === (node.parent).func) { - node = node.parent.getChildAt(1); - } - } - } - - var signatureHelpAvailable = false; - for (var n = node; n.kind !== SyntaxKind.SourceFile; n = n.parent) { - if (n.kind === SyntaxKind.FunctionBlock) { - break; - } - - var index = getArgumentIndex(n); - if (index >= 0) { - signatureHelpAvailable = true; - break; - } - // TODO: Handle previous token logic - // TODO: Handle generic call with incomplete + // Semantic filtering of signature help + var signatureHelpContext = getSignatureHelpArgumentContext(node); + if (signatureHelpContext) { + var call = signatureHelpContext.argumentNode.parent; + var candidates = []; + var resolvedSignature = typeInfoResolver.getResolvedSignature(call, candidates); + return candidates.length + ? new SignatureHelpItems(undefined, undefined, undefined) + : undefined; } - - return signatureHelpAvailable - ? new SignatureHelpItems(undefined, undefined, undefined) - : undefined; - - + return undefined; } function getSignatureHelpCurrentArgumentState(fileName: string, position: number, applicableSpanStart: number): SignatureHelpState {