Add elaboration when call fails all overloads but succeeds against the implementation signature

This commit is contained in:
Wesley Wigham
2020-10-08 14:55:14 -07:00
parent 23c5f9260c
commit a49099fd15
25 changed files with 174 additions and 14 deletions

View File

@@ -274,7 +274,7 @@ namespace ts {
result.relatedInformation = relatedInformation ?
relatedInformation.length ?
relatedInformation.map(r => convertToDiagnosticRelatedInformation(r, newProgram, toPath)) :
emptyArray :
[] :
undefined;
return result;
});
@@ -824,7 +824,7 @@ namespace ts {
result.relatedInformation = relatedInformation ?
relatedInformation.length ?
relatedInformation.map(r => convertToReusableDiagnosticRelatedInformation(r, relativeToBuildInfo)) :
emptyArray :
[] :
undefined;
return result;
});

View File

@@ -27227,10 +27227,10 @@ namespace ts {
// is just important for choosing the best signature. So in the case where there is only one
// signature, the subtype pass is useless. So skipping it is an optimization.
if (candidates.length > 1) {
result = chooseOverload(candidates, subtypeRelation, signatureHelpTrailingComma);
result = chooseOverload(candidates, subtypeRelation, isSingleNonGenericCandidate, signatureHelpTrailingComma);
}
if (!result) {
result = chooseOverload(candidates, assignableRelation, signatureHelpTrailingComma);
result = chooseOverload(candidates, assignableRelation, isSingleNonGenericCandidate, signatureHelpTrailingComma);
}
if (result) {
return result;
@@ -27255,6 +27255,7 @@ namespace ts {
if (last.declaration && candidatesForArgumentError.length > 3) {
addRelatedInfo(d, createDiagnosticForNode(last.declaration, Diagnostics.The_last_overload_is_declared_here));
}
addImplementationSuccessElaboration(last, d);
diagnostics.add(d);
}
}
@@ -27290,14 +27291,19 @@ namespace ts {
const chain = chainDiagnosticMessages(
map(diags, d => typeof d.messageText === "string" ? (d as DiagnosticMessageChain) : d.messageText),
Diagnostics.No_overload_matches_this_call);
const related = flatMap(diags, d => (d as Diagnostic).relatedInformation) as DiagnosticRelatedInformation[];
// The below is a spread to guarantee we get a new (mutable) array - our `flatMap` helper tries to do "smart" optimizations where it reuses input
// arrays and the emptyArray singleton where possible, which is decidedly not what we want while we're still constructing this diagnostic
const related = [...flatMap(diags, d => (d as Diagnostic).relatedInformation) as DiagnosticRelatedInformation[]];
let diag: Diagnostic;
if (every(diags, d => d.start === diags[0].start && d.length === diags[0].length && d.file === diags[0].file)) {
const { file, start, length } = diags[0];
diagnostics.add({ file, start, length, code: chain.code, category: chain.category, messageText: chain, relatedInformation: related });
diag = { file, start, length, code: chain.code, category: chain.category, messageText: chain, relatedInformation: related };
}
else {
diagnostics.add(createDiagnosticForNodeFromMessageChain(node, chain, related));
diag = createDiagnosticForNodeFromMessageChain(node, chain, related);
}
addImplementationSuccessElaboration(candidatesForArgumentError[0], diag);
diagnostics.add(diag);
}
}
else if (candidateForArgumentArityError) {
@@ -27322,7 +27328,30 @@ namespace ts {
return getCandidateForOverloadFailure(node, candidates, args, !!candidatesOutArray);
function chooseOverload(candidates: Signature[], relation: ESMap<string, RelationComparisonResult>, signatureHelpTrailingComma = false) {
function addImplementationSuccessElaboration(failed: Signature, d: Diagnostic) {
const oldCandidatesForArgumentError = candidatesForArgumentError;
const oldCandidateForArgumentArityError = candidateForArgumentArityError;
const oldCandidateForTypeArgumentError = candidateForTypeArgumentError;
const declCount = length(failed.declaration?.symbol.declarations);
const isOverload = declCount > 1;
const implDecl = isOverload ? failed.declaration?.symbol.declarations[declCount - 1] : undefined;
if (isOverload && implDecl && nodeIsPresent((implDecl as FunctionLikeDeclaration).body)) {
const candidate = getSignatureFromDeclaration(implDecl as FunctionLikeDeclaration);
const isSingleNonGenericCandidate = !candidate.typeParameters;
if (!!chooseOverload([candidate], assignableRelation, isSingleNonGenericCandidate)) {
addRelatedInfo(d, createDiagnosticForNode(implDecl, Diagnostics.The_call_would_have_succeeded_against_this_implementation_but_implementation_signatures_of_overloads_are_not_externally_visible));
}
}
candidatesForArgumentError = oldCandidatesForArgumentError;
candidateForArgumentArityError = oldCandidateForArgumentArityError;
candidateForTypeArgumentError = oldCandidateForTypeArgumentError;
}
function chooseOverload(candidates: Signature[], relation: ESMap<string, RelationComparisonResult>, isSingleNonGenericCandidate: boolean, signatureHelpTrailingComma = false) {
candidatesForArgumentError = undefined;
candidateForArgumentArityError = undefined;
candidateForTypeArgumentError = undefined;

View File

@@ -3043,7 +3043,10 @@
"category": "Error",
"code": 2792
},
"The call would have succeeded against this implementation, but implementation signatures of overloads are not externally visible.": {
"category": "Error",
"code": 2793
},
"Expected {0} arguments, but got {1}. Did you forget to include 'void' in your type argument to 'Promise'?": {
"category": "Error",
"code": 2794

View File

@@ -6653,6 +6653,7 @@ namespace ts {
if (!diagnostic.relatedInformation) {
diagnostic.relatedInformation = [];
}
Debug.assert(diagnostic.relatedInformation !== emptyArray, "Diagnostic had empty array singleton for related info, but is still being constructed!");
diagnostic.relatedInformation.push(...relatedInformation);
return diagnostic;
}