Skip to content

Commit 8a765d7

Browse files
committed
Semantic filtering of candidate signatures
1 parent 49fdb98 commit 8a765d7

File tree

3 files changed

+94
-74
lines changed

3 files changed

+94
-74
lines changed

src/compiler/checker.ts

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ module ts {
6666
getAugmentedPropertiesOfApparentType: getAugmentedPropertiesOfApparentType,
6767
getRootSymbol: getRootSymbol,
6868
getContextualType: getContextualType,
69-
getFullyQualifiedName: getFullyQualifiedName
69+
getFullyQualifiedName: getFullyQualifiedName,
70+
getResolvedSignature: getResolvedSignature
7071
};
7172

7273
var undefinedSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "undefined");
@@ -4128,7 +4129,7 @@ module ts {
41284129
return unknownSignature;
41294130
}
41304131

4131-
function isCandidateSignature(node: CallExpression, signature: Signature) {
4132+
function signatureHasCorrectArity(node: CallExpression, signature: Signature) {
41324133
var args = node.arguments || emptyArray;
41334134
return args.length >= signature.minArgumentCount &&
41344135
(signature.hasRestParameter || args.length <= signature.parameters.length) &&
@@ -4142,15 +4143,15 @@ module ts {
41424143
// interface B extends A { (x: 'foo'): string }
41434144
// var b: B;
41444145
// b('foo') // <- here overloads should be processed as [(x:'foo'): string, (x: string): void]
4145-
function collectCandidates(node: CallExpression, signatures: Signature[]): Signature[]{
4146-
var result: Signature[] = [];
4146+
function collectCandidates(node: CallExpression, signatures: Signature[], candidatesOutArray: Signature[]): Signature[]{
4147+
var result: Signature[] = candidatesOutArray || [];
41474148
var lastParent: Node;
41484149
var lastSymbol: Symbol;
41494150
var cutoffPos: number = 0;
41504151
var pos: number;
41514152
for (var i = 0; i < signatures.length; i++) {
41524153
var signature = signatures[i];
4153-
if (isCandidateSignature(node, signature)) {
4154+
if (true) {
41544155
var symbol = signature.declaration && getSymbolOfNode(signature.declaration);
41554156
var parent = signature.declaration && signature.declaration.parent;
41564157
if (!lastSymbol || symbol === lastSymbol) {
@@ -4260,9 +4261,9 @@ module ts {
42604261
return true;
42614262
}
42624263

4263-
function resolveCall(node: CallExpression, signatures: Signature[]): Signature {
4264+
function resolveCall(node: CallExpression, signatures: Signature[], candidatesOutArray: Signature[]): Signature {
42644265
forEach(node.typeArguments, checkSourceElement);
4265-
var candidates = collectCandidates(node, signatures);
4266+
var candidates = collectCandidates(node, signatures, candidatesOutArray);
42664267
if (!candidates.length) {
42674268
error(node, Diagnostics.Supplied_parameters_do_not_match_any_signature_of_call_target);
42684269
return resolveErrorCall(node);
@@ -4278,20 +4279,24 @@ module ts {
42784279
var relation = candidates.length === 1 ? assignableRelation : subtypeRelation;
42794280
while (true) {
42804281
for (var i = 0; i < candidates.length; i++) {
4282+
if (!signatureHasCorrectArity(node, candidates[i])) {
4283+
continue;
4284+
}
4285+
42814286
while (true) {
4282-
var candidate = candidates[i];
4283-
if (candidate.typeParameters) {
4287+
var candidateWithCorrectArity = candidates[i];
4288+
if (candidateWithCorrectArity.typeParameters) {
42844289
var typeArguments = node.typeArguments ?
4285-
checkTypeArguments(candidate, node.typeArguments) :
4286-
inferTypeArguments(candidate, args, excludeArgument);
4287-
candidate = getSignatureInstantiation(candidate, typeArguments);
4290+
checkTypeArguments(candidateWithCorrectArity, node.typeArguments) :
4291+
inferTypeArguments(candidateWithCorrectArity, args, excludeArgument);
4292+
candidateWithCorrectArity = getSignatureInstantiation(candidateWithCorrectArity, typeArguments);
42884293
}
4289-
if (!checkApplicableSignature(node, candidate, relation, excludeArgument, /*reportErrors*/ false)) {
4294+
if (!checkApplicableSignature(node, candidateWithCorrectArity, relation, excludeArgument, /*reportErrors*/ false)) {
42904295
break;
42914296
}
42924297
var index = excludeArgument ? indexOf(excludeArgument, true) : -1;
42934298
if (index < 0) {
4294-
return candidate;
4299+
return candidateWithCorrectArity;
42954300
}
42964301
excludeArgument[index] = false;
42974302
}
@@ -4301,17 +4306,26 @@ module ts {
43014306
}
43024307
relation = assignableRelation;
43034308
}
4309+
43044310
// No signatures were applicable. Now report errors based on the last applicable signature with
43054311
// no arguments excluded from assignability checks.
4306-
checkApplicableSignature(node, candidate, relation, undefined, /*reportErrors*/ true);
4312+
// If candidate is undefined, it means that no candidates had a suitable arity. In that case,
4313+
// skip the checkApplicableSignature check.
4314+
if (candidateWithCorrectArity) {
4315+
checkApplicableSignature(node, candidateWithCorrectArity, relation, undefined, /*reportErrors*/ true);
4316+
}
4317+
else {
4318+
error(node, Diagnostics.Supplied_parameters_do_not_match_any_signature_of_call_target);
4319+
return resolveErrorCall(node);
4320+
}
43074321
return resolveErrorCall(node);
43084322
}
43094323

4310-
function resolveCallExpression(node: CallExpression): Signature {
4324+
function resolveCallExpression(node: CallExpression, candidatesOutArray: Signature[]): Signature {
43114325
if (node.func.kind === SyntaxKind.SuperKeyword) {
43124326
var superType = checkSuperExpression(node.func);
43134327
if (superType !== unknownType) {
4314-
return resolveCall(node, getSignaturesOfType(superType, SignatureKind.Construct));
4328+
return resolveCall(node, getSignaturesOfType(superType, SignatureKind.Construct), candidatesOutArray);
43154329
}
43164330
return resolveUntypedCall(node);
43174331
}
@@ -4359,10 +4373,10 @@ module ts {
43594373
}
43604374
return resolveErrorCall(node);
43614375
}
4362-
return resolveCall(node, callSignatures);
4376+
return resolveCall(node, callSignatures, candidatesOutArray);
43634377
}
43644378

4365-
function resolveNewExpression(node: NewExpression): Signature {
4379+
function resolveNewExpression(node: NewExpression, candidatesOutArray: Signature[]): Signature {
43664380
var expressionType = checkExpression(node.func);
43674381
if (expressionType === unknownType) {
43684382
// Another error has already been reported
@@ -4397,7 +4411,7 @@ module ts {
43974411
// that the user will not add any.
43984412
var constructSignatures = getSignaturesOfType(expressionType, SignatureKind.Construct);
43994413
if (constructSignatures.length) {
4400-
return resolveCall(node, constructSignatures);
4414+
return resolveCall(node, constructSignatures, candidatesOutArray);
44014415
}
44024416

44034417
// If ConstructExpr's apparent type is an object type with no construct signatures but
@@ -4406,7 +4420,7 @@ module ts {
44064420
// operation is Any.
44074421
var callSignatures = getSignaturesOfType(expressionType, SignatureKind.Call);
44084422
if (callSignatures.length) {
4409-
var signature = resolveCall(node, callSignatures);
4423+
var signature = resolveCall(node, callSignatures, candidatesOutArray);
44104424
if (getReturnTypeOfSignature(signature) !== voidType) {
44114425
error(node, Diagnostics.Only_a_void_function_can_be_called_with_the_new_keyword);
44124426
}
@@ -4417,11 +4431,15 @@ module ts {
44174431
return resolveErrorCall(node);
44184432
}
44194433

4420-
function getResolvedSignature(node: CallExpression): Signature {
4434+
// candidatesOutArray is passed by signature help in the language service, and collectCandidates
4435+
// must fill it up with the appropriate candidate signatures
4436+
function getResolvedSignature(node: CallExpression, candidatesOutArray?: Signature[]): Signature {
44214437
var links = getNodeLinks(node);
44224438
if (!links.resolvedSignature) {
44234439
links.resolvedSignature = anySignature;
4424-
links.resolvedSignature = node.kind === SyntaxKind.CallExpression ? resolveCallExpression(node) : resolveNewExpression(node);
4440+
links.resolvedSignature = node.kind === SyntaxKind.CallExpression
4441+
? resolveCallExpression(node, candidatesOutArray)
4442+
: resolveNewExpression(node, candidatesOutArray);
44254443
}
44264444
return links.resolvedSignature;
44274445
}

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,7 @@ module ts {
644644
getAugmentedPropertiesOfApparentType(type: Type): Symbol[];
645645
getRootSymbol(symbol: Symbol): Symbol;
646646
getContextualType(node: Node): Type;
647+
getResolvedSignature(node: CallExpression, candidatesOutArray?: Signature[]): Signature;
647648
}
648649

649650
export interface TextWriter {

src/services/services.ts

Lines changed: 52 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3517,71 +3517,72 @@ module ts {
35173517

35183518
return -1;
35193519
}
3520-
//// Technically signature help should only be triggered on these characters
3521-
//if (node.kind !== SyntaxKind.CommaToken && node.kind !== SyntaxKind.OpenParenToken && node.kind !== SyntaxKind.LessThanToken) {
3522-
// return false;
3523-
//}
35243520

3525-
//if (node.kind === SyntaxKind.CommaToken) {
3526-
// if (node.parent.kind !== SyntaxKind.SyntaxList) {
3527-
// return false;
3528-
// }
3521+
function getSignatureHelpArgumentContext(node: Node): {
3522+
argumentNode: Node;
3523+
argumentIndex: number;
3524+
isTypeArgument: boolean;
3525+
} {
3526+
// We only want this node if it is a token and it strictly contains the current position.
3527+
// Otherwise we want the previous token
3528+
var isToken = node.kind < SyntaxKind.Missing;
3529+
if (!isToken || position <= node.getStart() || position >= node.getEnd()) {
3530+
// This is a temporary hack until we figure out our token story.
3531+
// The correct solution is to get the previous token
3532+
node = SignatureInfoHelpers.findClosestRightmostSiblingFromLeft(position, sourceFile);
3533+
3534+
if (!node) {
3535+
return undefined;
3536+
}
3537+
if (node.parent.kind === SyntaxKind.CallExpression || node.parent.kind === SyntaxKind.NewExpression) {
3538+
if (node === (<CallExpression>node.parent).func) {
3539+
node = node.parent.getChildAt(1);
3540+
}
3541+
}
3542+
}
35293543

3530-
// // node becomes the containing SyntaxList
3531-
// node = node.parent;
3532-
//}
3544+
var signatureHelpAvailable = false;
3545+
for (var n = node; n.kind !== SyntaxKind.SourceFile; n = n.parent) {
3546+
if (n.kind === SyntaxKind.FunctionBlock) {
3547+
return undefined;
3548+
}
35333549

3534-
//// node is open paren, less than, or a syntax list containing a comma
3535-
//if (node.parent.kind === SyntaxKind.CallExpression || node.parent.kind === SyntaxKind.NewExpression) {
3536-
// return true;
3537-
//}
3550+
var index = getArgumentIndex(n);
3551+
if (index >= 0) {
3552+
return {
3553+
argumentNode: n,
3554+
argumentIndex: index,
3555+
isTypeArgument: false
3556+
}
3557+
}
35383558

3539-
synchronizeHostData();
35403559

3541-
// Decide whether to show signature help
3542-
var sourceFile = getSourceFile(fileName);
3543-
var node = getNodeAtPosition(sourceFile, position);
3544-
// We only want this node if it is a token and it strictly contains the current position.
3545-
// Otherwise we want the previous token
3546-
var isToken = node.kind < SyntaxKind.Missing;
3547-
if (!isToken || position <= node.getStart() || position >= node.getEnd()) {
3548-
// This is a temporary hack until we figure out our token story.
3549-
// The correct solution is to get the previous token
3550-
node = SignatureInfoHelpers.findClosestRightmostSiblingFromLeft(position, sourceFile);
3560+
// TODO: Handle previous token logic
3561+
// TODO: Handle generic call with incomplete
35513562

3552-
if (!node) {
35533563
return undefined;
35543564
}
3555-
if (node.parent.kind === SyntaxKind.CallExpression || node.parent.kind === SyntaxKind.NewExpression) {
3556-
if (node === (<CallExpression>node.parent).func) {
3557-
node = node.parent.getChildAt(1);
3558-
}
3559-
}
35603565
}
3561-
3562-
var signatureHelpAvailable = false;
3563-
for (var n = node; n.kind !== SyntaxKind.SourceFile; n = n.parent) {
3564-
if (n.kind === SyntaxKind.FunctionBlock) {
3565-
break;
3566-
}
3567-
3568-
var index = getArgumentIndex(n);
3569-
if (index >= 0) {
3570-
signatureHelpAvailable = true;
3571-
break;
3572-
}
3573-
35743566

3575-
// TODO: Handle previous token logic
3576-
// TODO: Handle generic call with incomplete
3577-
}
3567+
synchronizeHostData();
35783568

3569+
// Decide whether to show signature help
3570+
var sourceFile = getSourceFile(fileName);
3571+
var node = getNodeAtPosition(sourceFile, position);
35793572

3580-
return signatureHelpAvailable
3581-
? new SignatureHelpItems(undefined, undefined, undefined)
3582-
: undefined;
35833573

3574+
// Semantic filtering of signature help
3575+
var signatureHelpContext = getSignatureHelpArgumentContext(node);
3576+
if (signatureHelpContext) {
3577+
var call = <CallExpression>signatureHelpContext.argumentNode.parent;
3578+
var candidates = <Signature[]>[];
3579+
var resolvedSignature = typeInfoResolver.getResolvedSignature(call, candidates);
3580+
return candidates.length
3581+
? new SignatureHelpItems(undefined, undefined, undefined)
3582+
: undefined;
3583+
}
35843584

3585+
return undefined;
35853586
}
35863587

35873588
function getSignatureHelpCurrentArgumentState(fileName: string, position: number, applicableSpanStart: number): SignatureHelpState {

0 commit comments

Comments
 (0)