Skip to content

Commit 410b717

Browse files
Merge pull request #32034 from dragomirtitian/GH-32013-improve-error-messages-for-calling-types
Improved error message for calling/constructing types
2 parents cfe4cbd + e4bca96 commit 410b717

File tree

99 files changed

+893
-348
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

99 files changed

+893
-348
lines changed

src/compiler/checker.ts

Lines changed: 121 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -21298,8 +21298,34 @@ namespace ts {
2129821298
return Debug.fail();
2129921299
}
2130021300
}
21301+
function getDiagnosticSpanForCallNode(node: CallExpression, doNotIncludeArguments?: boolean) {
21302+
let start: number;
21303+
let length: number;
21304+
const sourceFile = getSourceFileOfNode(node);
2130121305

21302-
function getArgumentArityError(node: Node, signatures: ReadonlyArray<Signature>, args: ReadonlyArray<Expression>) {
21306+
if (isPropertyAccessExpression(node.expression)) {
21307+
const nameSpan = getErrorSpanForNode(sourceFile, node.expression.name);
21308+
start = nameSpan.start;
21309+
length = doNotIncludeArguments ? nameSpan.length : node.end - start;
21310+
}
21311+
else {
21312+
const expressionSpan = getErrorSpanForNode(sourceFile, node.expression);
21313+
start = expressionSpan.start;
21314+
length = doNotIncludeArguments ? expressionSpan.length : node.end - start;
21315+
}
21316+
return { start, length, sourceFile };
21317+
}
21318+
function getDiagnosticForCallNode(node: CallLikeExpression, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): DiagnosticWithLocation {
21319+
if (isCallExpression(node)) {
21320+
const { sourceFile, start, length } = getDiagnosticSpanForCallNode(node);
21321+
return createFileDiagnostic(sourceFile, start, length, message, arg0, arg1, arg2, arg3);
21322+
}
21323+
else {
21324+
return createDiagnosticForNode(node, message, arg0, arg1, arg2, arg3);
21325+
}
21326+
}
21327+
21328+
function getArgumentArityError(node: CallLikeExpression, signatures: ReadonlyArray<Signature>, args: ReadonlyArray<Expression>) {
2130321329
let min = Number.POSITIVE_INFINITY;
2130421330
let max = Number.NEGATIVE_INFINITY;
2130521331
let belowArgCount = Number.NEGATIVE_INFINITY;
@@ -21346,11 +21372,11 @@ namespace ts {
2134621372
}
2134721373
}
2134821374
if (min < argCount && argCount < max) {
21349-
return createDiagnosticForNode(node, Diagnostics.No_overload_expects_0_arguments_but_overloads_do_exist_that_expect_either_1_or_2_arguments, argCount, belowArgCount, aboveArgCount);
21375+
return getDiagnosticForCallNode(node, Diagnostics.No_overload_expects_0_arguments_but_overloads_do_exist_that_expect_either_1_or_2_arguments, argCount, belowArgCount, aboveArgCount);
2135021376
}
2135121377

2135221378
if (!hasSpreadArgument && argCount < min) {
21353-
const diagnostic = createDiagnosticForNode(node, error, paramRange, argCount);
21379+
const diagnostic = getDiagnosticForCallNode(node, error, paramRange, argCount);
2135421380
return related ? addRelatedInfo(diagnostic, related) : diagnostic;
2135521381
}
2135621382

@@ -21425,8 +21451,7 @@ namespace ts {
2142521451
reorderCandidates(signatures, candidates);
2142621452
if (!candidates.length) {
2142721453
if (reportErrors) {
21428-
const errorNode = getCallErrorNode(node);
21429-
diagnostics.add(createDiagnosticForNode(errorNode, Diagnostics.Call_target_does_not_contain_any_signatures));
21454+
diagnostics.add(getDiagnosticForCallNode(node, Diagnostics.Call_target_does_not_contain_any_signatures));
2143021455
}
2143121456
return resolveErrorCall(node);
2143221457
}
@@ -21504,45 +21529,32 @@ namespace ts {
2150421529
// If candidate is undefined, it means that no candidates had a suitable arity. In that case,
2150521530
// skip the checkApplicableSignature check.
2150621531
if (reportErrors) {
21507-
const errorNode = getCallErrorNode(node);
2150821532

2150921533
if (candidateForArgumentError) {
2151021534
checkApplicableSignature(node, args, candidateForArgumentError, assignableRelation, CheckMode.Normal, /*reportErrors*/ true);
2151121535
}
2151221536
else if (candidateForArgumentArityError) {
21513-
diagnostics.add(getArgumentArityError(errorNode, [candidateForArgumentArityError], args));
21537+
diagnostics.add(getArgumentArityError(node, [candidateForArgumentArityError], args));
2151421538
}
2151521539
else if (candidateForTypeArgumentError) {
2151621540
checkTypeArguments(candidateForTypeArgumentError, (node as CallExpression | TaggedTemplateExpression | JsxOpeningLikeElement).typeArguments!, /*reportErrors*/ true, fallbackError);
2151721541
}
2151821542
else {
2151921543
const signaturesWithCorrectTypeArgumentArity = filter(signatures, s => hasCorrectTypeArgumentArity(s, typeArguments));
2152021544
if (signaturesWithCorrectTypeArgumentArity.length === 0) {
21521-
diagnostics.add(getTypeArgumentArityError(errorNode, signatures, typeArguments!));
21545+
diagnostics.add(getTypeArgumentArityError(node, signatures, typeArguments!));
2152221546
}
2152321547
else if (!isDecorator) {
21524-
diagnostics.add(getArgumentArityError(errorNode, signaturesWithCorrectTypeArgumentArity, args));
21548+
diagnostics.add(getArgumentArityError(node, signaturesWithCorrectTypeArgumentArity, args));
2152521549
}
2152621550
else if (fallbackError) {
21527-
diagnostics.add(createDiagnosticForNode(errorNode, fallbackError));
21551+
diagnostics.add(getDiagnosticForCallNode(node, fallbackError));
2152821552
}
2152921553
}
2153021554
}
2153121555

2153221556
return produceDiagnostics || !args ? resolveErrorCall(node) : getCandidateForOverloadFailure(node, candidates, args, !!candidatesOutArray);
2153321557

21534-
function getCallErrorNode(node: CallLikeExpression): Node {
21535-
if (isCallExpression(node)) {
21536-
if (isPropertyAccessExpression(node.expression)) {
21537-
return node.expression.name;
21538-
}
21539-
else {
21540-
return node.expression;
21541-
}
21542-
}
21543-
return node;
21544-
}
21545-
2154621558
function chooseOverload(candidates: Signature[], relation: Map<RelationComparisonResult>, signatureHelpTrailingComma = false) {
2154721559
candidateForArgumentError = undefined;
2154821560
candidateForArgumentArityError = undefined;
@@ -21825,7 +21837,7 @@ namespace ts {
2182521837
relatedInformation = createDiagnosticForNode(node.expression, Diagnostics.It_is_highly_likely_that_you_are_missing_a_semicolon);
2182621838
}
2182721839
}
21828-
invocationError(node, apparentType, SignatureKind.Call, relatedInformation);
21840+
invocationError(node.expression, apparentType, SignatureKind.Call, relatedInformation);
2182921841
}
2183021842
return resolveErrorCall(node);
2183121843
}
@@ -21942,7 +21954,7 @@ namespace ts {
2194221954
return signature;
2194321955
}
2194421956

21945-
invocationError(node, expressionType, SignatureKind.Construct);
21957+
invocationError(node.expression, expressionType, SignatureKind.Construct);
2194621958
return resolveErrorCall(node);
2194721959
}
2194821960

@@ -22015,11 +22027,88 @@ namespace ts {
2201522027
return true;
2201622028
}
2201722029

22018-
function invocationError(node: Node, apparentType: Type, kind: SignatureKind, relatedInformation?: DiagnosticRelatedInformation) {
22019-
const diagnostic = error(node, (kind === SignatureKind.Call ?
22020-
Diagnostics.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures :
22021-
Diagnostics.Cannot_use_new_with_an_expression_whose_type_lacks_a_call_or_construct_signature
22022-
), typeToString(apparentType));
22030+
function invocationErrorDetails(apparentType: Type, kind: SignatureKind): DiagnosticMessageChain {
22031+
let errorInfo: DiagnosticMessageChain | undefined;
22032+
const isCall = kind === SignatureKind.Call;
22033+
if (apparentType.flags & TypeFlags.Union) {
22034+
const types = (apparentType as UnionType).types;
22035+
let hasSignatures = false;
22036+
for (const constituent of types) {
22037+
const signatures = getSignaturesOfType(constituent, kind);
22038+
if (signatures.length !== 0) {
22039+
hasSignatures = true;
22040+
if (errorInfo) {
22041+
// Bail early if we already have an error, no chance of "No constituent of type is callable"
22042+
break;
22043+
}
22044+
}
22045+
else {
22046+
// Error on the first non callable constituent only
22047+
if (!errorInfo) {
22048+
errorInfo = chainDiagnosticMessages(
22049+
errorInfo,
22050+
isCall ?
22051+
Diagnostics.Type_0_has_no_call_signatures :
22052+
Diagnostics.Type_0_has_no_construct_signatures,
22053+
typeToString(constituent)
22054+
);
22055+
errorInfo = chainDiagnosticMessages(
22056+
errorInfo,
22057+
isCall ?
22058+
Diagnostics.Not_all_constituents_of_type_0_are_callable :
22059+
Diagnostics.Not_all_constituents_of_type_0_are_constructable,
22060+
typeToString(apparentType)
22061+
);
22062+
}
22063+
if (hasSignatures) {
22064+
// Bail early if we already found a siganture, no chance of "No constituent of type is callable"
22065+
break;
22066+
}
22067+
}
22068+
}
22069+
if (!hasSignatures) {
22070+
errorInfo = chainDiagnosticMessages(
22071+
/* detials */ undefined,
22072+
isCall ?
22073+
Diagnostics.No_constituent_of_type_0_is_callable :
22074+
Diagnostics.No_constituent_of_type_0_is_constructable,
22075+
typeToString(apparentType)
22076+
);
22077+
}
22078+
if (!errorInfo) {
22079+
errorInfo = chainDiagnosticMessages(
22080+
errorInfo,
22081+
isCall ?
22082+
Diagnostics.Each_member_of_the_union_type_0_has_signatures_but_none_of_those_signatures_are_compatible_with_each_other :
22083+
Diagnostics.Each_member_of_the_union_type_0_has_construct_signatures_but_none_of_those_signatures_are_compatible_with_each_other,
22084+
typeToString(apparentType)
22085+
);
22086+
}
22087+
}
22088+
else {
22089+
errorInfo = chainDiagnosticMessages(
22090+
errorInfo,
22091+
isCall ?
22092+
Diagnostics.Type_0_has_no_call_signatures :
22093+
Diagnostics.Type_0_has_no_construct_signatures,
22094+
typeToString(apparentType)
22095+
);
22096+
}
22097+
return chainDiagnosticMessages(
22098+
errorInfo,
22099+
isCall ?
22100+
Diagnostics.This_expression_is_not_callable :
22101+
Diagnostics.This_expression_is_not_constructable
22102+
);
22103+
}
22104+
function invocationError(errorTarget: Node, apparentType: Type, kind: SignatureKind, relatedInformation?: DiagnosticRelatedInformation) {
22105+
const diagnostic = createDiagnosticForNodeFromMessageChain(errorTarget, invocationErrorDetails(apparentType, kind));
22106+
if (isCallExpression(errorTarget.parent)) {
22107+
const { start, length } = getDiagnosticSpanForCallNode(errorTarget.parent, /* doNotIncludeArguments */ true);
22108+
diagnostic.start = start;
22109+
diagnostic.length = length;
22110+
}
22111+
diagnostics.add(diagnostic);
2202322112
invocationErrorRecovery(apparentType, kind, relatedInformation ? addRelatedInfo(diagnostic, relatedInformation) : diagnostic);
2202422113
}
2202522114

@@ -22057,7 +22146,7 @@ namespace ts {
2205722146
}
2205822147

2205922148
if (!callSignatures.length) {
22060-
invocationError(node, apparentType, SignatureKind.Call);
22149+
invocationError(node.tag, apparentType, SignatureKind.Call);
2206122150
return resolveErrorCall(node);
2206222151
}
2206322152

@@ -22113,9 +22202,9 @@ namespace ts {
2211322202

2211422203
const headMessage = getDiagnosticHeadMessageForDecoratorResolution(node);
2211522204
if (!callSignatures.length) {
22116-
let errorInfo = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures, typeToString(apparentType));
22205+
let errorInfo = invocationErrorDetails(apparentType, SignatureKind.Call);
2211722206
errorInfo = chainDiagnosticMessages(errorInfo, headMessage);
22118-
const diag = createDiagnosticForNodeFromMessageChain(node, errorInfo);
22207+
const diag = createDiagnosticForNodeFromMessageChain(node.expression, errorInfo);
2211922208
diagnostics.add(diag);
2212022209
invocationErrorRecovery(apparentType, SignatureKind.Call, diag);
2212122210
return resolveErrorCall(node);

src/compiler/diagnosticMessages.json

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,15 +1236,15 @@
12361236
"category": "Error",
12371237
"code": 2348
12381238
},
1239-
"Cannot invoke an expression whose type lacks a call signature. Type '{0}' has no compatible call signatures.": {
1239+
"This expression is not callable.": {
12401240
"category": "Error",
12411241
"code": 2349
12421242
},
12431243
"Only a void function can be called with the 'new' keyword.": {
12441244
"category": "Error",
12451245
"code": 2350
12461246
},
1247-
"Cannot use 'new' with an expression whose type lacks a call or construct signature.": {
1247+
"This expression is not constructable.": {
12481248
"category": "Error",
12491249
"code": 2351
12501250
},
@@ -2621,6 +2621,38 @@
26212621
"category": "Error",
26222622
"code": 2754
26232623
},
2624+
"No constituent of type '{0}' is callable.": {
2625+
"category": "Error",
2626+
"code": 2755
2627+
},
2628+
"Not all constituents of type '{0}' are callable.": {
2629+
"category": "Error",
2630+
"code": 2756
2631+
},
2632+
"Type '{0}' has no call signatures.": {
2633+
"category": "Error",
2634+
"code": 2757
2635+
},
2636+
"Each member of the union type '{0}' has signatures, but none of those signatures are compatible with each other.": {
2637+
"category": "Error",
2638+
"code": 2758
2639+
},
2640+
"No constituent of type '{0}' is constructable.": {
2641+
"category": "Error",
2642+
"code": 2759
2643+
},
2644+
"Not all constituents of type '{0}' are constructable.": {
2645+
"category": "Error",
2646+
"code": 2760
2647+
},
2648+
"Type '{0}' has no construct signatures.": {
2649+
"category": "Error",
2650+
"code": 2761
2651+
},
2652+
"Each member of the union type '{0}' has construct signatures, but none of those signatures are compatible with each other.": {
2653+
"category": "Error",
2654+
"code": 2762
2655+
},
26242656

26252657
"Import declaration '{0}' is using private name '{1}'.": {
26262658
"category": "Error",

src/services/codefixes/fixInvalidImportSyntax.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,16 @@ namespace ts.codefix {
3131

3232
registerCodeFix({
3333
errorCodes: [
34-
Diagnostics.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures.code,
35-
Diagnostics.Cannot_use_new_with_an_expression_whose_type_lacks_a_call_or_construct_signature.code,
34+
Diagnostics.This_expression_is_not_callable.code,
35+
Diagnostics.This_expression_is_not_constructable.code,
3636
],
3737
getCodeActions: getActionsForUsageOfInvalidImport
3838
});
3939

4040
function getActionsForUsageOfInvalidImport(context: CodeFixContext): CodeFixAction[] | undefined {
4141
const sourceFile = context.sourceFile;
42-
const targetKind = Diagnostics.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures.code === context.errorCode ? SyntaxKind.CallExpression : SyntaxKind.NewExpression;
43-
const node = findAncestor(getTokenAtPosition(sourceFile, context.span.start), a => a.kind === targetKind && a.getStart() === context.span.start && a.getEnd() === (context.span.start + context.span.length)) as CallExpression | NewExpression;
42+
const targetKind = Diagnostics.This_expression_is_not_callable.code === context.errorCode ? SyntaxKind.CallExpression : SyntaxKind.NewExpression;
43+
const node = findAncestor(getTokenAtPosition(sourceFile, context.span.start), a => a.kind === targetKind) as CallExpression | NewExpression;
4444
if (!node) {
4545
return [];
4646
}

tests/baselines/reference/arityErrorRelatedSpanBindingPattern.errors.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ tests/cases/compiler/arityErrorRelatedSpanBindingPattern.ts(7,1): error TS2554:
88
function bar(a, b, [c]): void {}
99

1010
foo("", 0);
11-
~~~
11+
~~~~~~~~~~
1212
!!! error TS2554: Expected 3 arguments, but got 2.
1313
!!! related TS6211 tests/cases/compiler/arityErrorRelatedSpanBindingPattern.ts:1:20: An argument matching this binding pattern was not provided.
1414

1515
bar("", 0);
16-
~~~
16+
~~~~~~~~~~
1717
!!! error TS2554: Expected 3 arguments, but got 2.
1818
!!! related TS6211 tests/cases/compiler/arityErrorRelatedSpanBindingPattern.ts:3:20: An argument matching this binding pattern was not provided.
1919

tests/baselines/reference/baseCheck.errors.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ tests/cases/compiler/baseCheck.ts(26,9): error TS2304: Cannot find name 'x'.
3030
}
3131

3232
class D extends C { constructor(public z: number) { super(this.z) } } // too few params
33-
~~~~~
33+
~~~~~~~~~~~~~
3434
!!! error TS2554: Expected 2 arguments, but got 1.
3535
!!! related TS6210 tests/cases/compiler/baseCheck.ts:1:34: An argument for 'y' was not provided.
3636
~~~~

0 commit comments

Comments
 (0)