Skip to content

Commit 1c13792

Browse files
authored
Prefer elaborating on expressions which could be called to produce a correct type by suggesting such (#27016)
* Prefer elaborating on expressions which could be called to produce a correct type by suggesting such * Pass relation through elaboration machinery
1 parent aa9230f commit 1c13792

16 files changed

+376
-45
lines changed

src/compiler/checker.ts

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10573,7 +10573,7 @@ namespace ts {
1057310573

1057410574
function checkTypeRelatedToAndOptionallyElaborate(source: Type, target: Type, relation: Map<RelationComparisonResult>, errorNode: Node | undefined, expr: Expression | undefined, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean {
1057510575
if (isTypeRelatedTo(source, target, relation)) return true;
10576-
if (!errorNode || !elaborateError(expr, source, target)) {
10576+
if (!errorNode || !elaborateError(expr, source, target, relation)) {
1057710577
return checkTypeRelatedTo(source, target, relation, errorNode, headMessage, containingMessageChain);
1057810578
}
1057910579
return false;
@@ -10583,25 +10583,49 @@ namespace ts {
1058310583
return !!(type.flags & TypeFlags.Conditional || (type.flags & TypeFlags.Intersection && some((type as IntersectionType).types, isOrHasGenericConditional)));
1058410584
}
1058510585

10586-
function elaborateError(node: Expression | undefined, source: Type, target: Type): boolean {
10586+
function elaborateError(node: Expression | undefined, source: Type, target: Type, relation: Map<RelationComparisonResult>): boolean {
1058710587
if (!node || isOrHasGenericConditional(target)) return false;
10588+
if (!checkTypeRelatedTo(source, target, relation, /*errorNode*/ undefined) && elaborateDidYouMeanToCallOrConstruct(node, source, target, relation)) {
10589+
return true;
10590+
}
1058810591
switch (node.kind) {
1058910592
case SyntaxKind.JsxExpression:
1059010593
case SyntaxKind.ParenthesizedExpression:
10591-
return elaborateError((node as ParenthesizedExpression | JsxExpression).expression, source, target);
10594+
return elaborateError((node as ParenthesizedExpression | JsxExpression).expression, source, target, relation);
1059210595
case SyntaxKind.BinaryExpression:
1059310596
switch ((node as BinaryExpression).operatorToken.kind) {
1059410597
case SyntaxKind.EqualsToken:
1059510598
case SyntaxKind.CommaToken:
10596-
return elaborateError((node as BinaryExpression).right, source, target);
10599+
return elaborateError((node as BinaryExpression).right, source, target, relation);
1059710600
}
1059810601
break;
1059910602
case SyntaxKind.ObjectLiteralExpression:
10600-
return elaborateObjectLiteral(node as ObjectLiteralExpression, source, target);
10603+
return elaborateObjectLiteral(node as ObjectLiteralExpression, source, target, relation);
1060110604
case SyntaxKind.ArrayLiteralExpression:
10602-
return elaborateArrayLiteral(node as ArrayLiteralExpression, source, target);
10605+
return elaborateArrayLiteral(node as ArrayLiteralExpression, source, target, relation);
1060310606
case SyntaxKind.JsxAttributes:
10604-
return elaborateJsxAttributes(node as JsxAttributes, source, target);
10607+
return elaborateJsxAttributes(node as JsxAttributes, source, target, relation);
10608+
}
10609+
return false;
10610+
}
10611+
10612+
function elaborateDidYouMeanToCallOrConstruct(node: Expression, source: Type, target: Type, relation: Map<RelationComparisonResult>): boolean {
10613+
const callSignatures = getSignaturesOfType(source, SignatureKind.Call);
10614+
const constructSignatures = getSignaturesOfType(source, SignatureKind.Construct);
10615+
for (const signatures of [constructSignatures, callSignatures]) {
10616+
if (some(signatures, s => {
10617+
const returnType = getReturnTypeOfSignature(s);
10618+
return !(returnType.flags & (TypeFlags.Any | TypeFlags.Never)) && checkTypeRelatedTo(returnType, target, relation, /*errorNode*/ undefined);
10619+
})) {
10620+
const resultObj: { error?: Diagnostic } = {};
10621+
checkTypeAssignableTo(source, target, node, /*errorMessage*/ undefined, /*containingChain*/ undefined, resultObj);
10622+
const diagnostic = resultObj.error!;
10623+
addRelatedInfo(diagnostic, createDiagnosticForNode(
10624+
node,
10625+
signatures === constructSignatures ? Diagnostics.Did_you_mean_to_use_new_with_this_expression : Diagnostics.Did_you_mean_to_call_this_expression
10626+
));
10627+
return true;
10628+
}
1060510629
}
1060610630
return false;
1060710631
}
@@ -10612,15 +10636,15 @@ namespace ts {
1061210636
* If that element would issue an error, we first attempt to dive into that element's inner expression and issue a more specific error by recuring into `elaborateError`
1061310637
* Otherwise, we issue an error on _every_ element which fail the assignability check
1061410638
*/
10615-
function elaborateElementwise(iterator: ElaborationIterator, source: Type, target: Type) {
10639+
function elaborateElementwise(iterator: ElaborationIterator, source: Type, target: Type, relation: Map<RelationComparisonResult>) {
1061610640
// Assignability failure - check each prop individually, and if that fails, fall back on the bad error span
1061710641
let reportedError = false;
1061810642
for (let status = iterator.next(); !status.done; status = iterator.next()) {
1061910643
const { errorNode: prop, innerExpression: next, nameType, errorMessage } = status.value;
1062010644
const sourcePropType = getIndexedAccessType(source, nameType, /*accessNode*/ undefined, errorType);
1062110645
const targetPropType = getIndexedAccessType(target, nameType, /*accessNode*/ undefined, errorType);
1062210646
if (sourcePropType !== errorType && targetPropType !== errorType && !isTypeAssignableTo(sourcePropType, targetPropType)) {
10623-
const elaborated = next && elaborateError(next, sourcePropType, targetPropType);
10647+
const elaborated = next && elaborateError(next, sourcePropType, targetPropType, relation);
1062410648
if (elaborated) {
1062510649
reportedError = true;
1062610650
}
@@ -10629,10 +10653,10 @@ namespace ts {
1062910653
const resultObj: { error?: Diagnostic } = {};
1063010654
// Use the expression type, if available
1063110655
const specificSource = next ? checkExpressionForMutableLocation(next, CheckMode.Normal, sourcePropType) : sourcePropType;
10632-
const result = checkTypeAssignableTo(specificSource, targetPropType, prop, errorMessage, /*containingChain*/ undefined, resultObj);
10656+
const result = checkTypeRelatedTo(specificSource, targetPropType, relation, prop, errorMessage, /*containingChain*/ undefined, resultObj);
1063310657
if (result && specificSource !== sourcePropType) {
1063410658
// If for whatever reason the expression type doesn't yield an error, make sure we still issue an error on the sourcePropType
10635-
checkTypeAssignableTo(sourcePropType, targetPropType, prop, errorMessage, /*containingChain*/ undefined, resultObj);
10659+
checkTypeRelatedTo(sourcePropType, targetPropType, relation, prop, errorMessage, /*containingChain*/ undefined, resultObj);
1063610660
}
1063710661
if (resultObj.error) {
1063810662
const reportedDiag = resultObj.error;
@@ -10674,8 +10698,8 @@ namespace ts {
1067410698
}
1067510699
}
1067610700

10677-
function elaborateJsxAttributes(node: JsxAttributes, source: Type, target: Type) {
10678-
return elaborateElementwise(generateJsxAttributes(node), source, target);
10701+
function elaborateJsxAttributes(node: JsxAttributes, source: Type, target: Type, relation: Map<RelationComparisonResult>) {
10702+
return elaborateElementwise(generateJsxAttributes(node), source, target, relation);
1067910703
}
1068010704

1068110705
function *generateLimitedTupleElements(node: ArrayLiteralExpression, target: Type): ElaborationIterator {
@@ -10691,9 +10715,9 @@ namespace ts {
1069110715
}
1069210716
}
1069310717

10694-
function elaborateArrayLiteral(node: ArrayLiteralExpression, source: Type, target: Type) {
10718+
function elaborateArrayLiteral(node: ArrayLiteralExpression, source: Type, target: Type, relation: Map<RelationComparisonResult>) {
1069510719
if (isTupleLikeType(source)) {
10696-
return elaborateElementwise(generateLimitedTupleElements(node, target), source, target);
10720+
return elaborateElementwise(generateLimitedTupleElements(node, target), source, target, relation);
1069710721
}
1069810722
return false;
1069910723
}
@@ -10722,8 +10746,8 @@ namespace ts {
1072210746
}
1072310747
}
1072410748

10725-
function elaborateObjectLiteral(node: ObjectLiteralExpression, source: Type, target: Type) {
10726-
return elaborateElementwise(generateObjectLiteralElements(node), source, target);
10749+
function elaborateObjectLiteral(node: ObjectLiteralExpression, source: Type, target: Type, relation: Map<RelationComparisonResult>) {
10750+
return elaborateElementwise(generateObjectLiteralElements(node), source, target, relation);
1072710751
}
1072810752

1072910753
/**

src/compiler/diagnosticMessages.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3720,6 +3720,14 @@
37203720
"category": "Message",
37213721
"code": 6211
37223722
},
3723+
"Did you mean to call this expression?": {
3724+
"category": "Message",
3725+
"code": 6212
3726+
},
3727+
"Did you mean to use `new` with this expression?": {
3728+
"category": "Message",
3729+
"code": 6213
3730+
},
37233731

37243732
"Projects to reference": {
37253733
"category": "Message",
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
tests/cases/compiler/didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts(10,8): error TS2322: Type 'typeof Bar' is not assignable to type 'Bar'.
2+
Property 'x' is missing in type 'typeof Bar'.
3+
tests/cases/compiler/didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts(11,8): error TS2322: Type 'DateConstructor' is not assignable to type 'Date'.
4+
Property 'toDateString' is missing in type 'DateConstructor'.
5+
tests/cases/compiler/didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts(17,4): error TS2322: Type '() => number' is not assignable to type 'number'.
6+
tests/cases/compiler/didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts(26,5): error TS2322: Type '() => number' is not assignable to type 'number'.
7+
8+
9+
==== tests/cases/compiler/didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts (4 errors) ====
10+
class Bar {
11+
x!: string;
12+
}
13+
14+
declare function getNum(): number;
15+
16+
declare function foo(arg: { x: Bar, y: Date }, item: number, items?: [number, number, number]): void;
17+
18+
foo({
19+
x: Bar,
20+
~~~
21+
!!! error TS2322: Type 'typeof Bar' is not assignable to type 'Bar'.
22+
!!! error TS2322: Property 'x' is missing in type 'typeof Bar'.
23+
!!! related TS6213 tests/cases/compiler/didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts:10:8: Did you mean to use `new` with this expression?
24+
y: Date
25+
~~~~
26+
!!! error TS2322: Type 'DateConstructor' is not assignable to type 'Date'.
27+
!!! error TS2322: Property 'toDateString' is missing in type 'DateConstructor'.
28+
!!! related TS6213 tests/cases/compiler/didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts:11:8: Did you mean to use `new` with this expression?
29+
}, getNum());
30+
31+
foo({
32+
x: new Bar(),
33+
y: new Date()
34+
}, getNum);
35+
~~~~~~
36+
!!! error TS2322: Type '() => number' is not assignable to type 'number'.
37+
!!! related TS6212 tests/cases/compiler/didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts:17:4: Did you mean to call this expression?
38+
39+
40+
foo({
41+
x: new Bar(),
42+
y: new Date()
43+
}, getNum(), [
44+
1,
45+
2,
46+
getNum
47+
~~~~~~
48+
!!! error TS2322: Type '() => number' is not assignable to type 'number'.
49+
!!! related TS6212 tests/cases/compiler/didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts:26:5: Did you mean to call this expression?
50+
]);
51+
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//// [didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts]
2+
class Bar {
3+
x!: string;
4+
}
5+
6+
declare function getNum(): number;
7+
8+
declare function foo(arg: { x: Bar, y: Date }, item: number, items?: [number, number, number]): void;
9+
10+
foo({
11+
x: Bar,
12+
y: Date
13+
}, getNum());
14+
15+
foo({
16+
x: new Bar(),
17+
y: new Date()
18+
}, getNum);
19+
20+
21+
foo({
22+
x: new Bar(),
23+
y: new Date()
24+
}, getNum(), [
25+
1,
26+
2,
27+
getNum
28+
]);
29+
30+
31+
//// [didYouMeanElaborationsForExpressionsWhichCouldBeCalled.js]
32+
var Bar = /** @class */ (function () {
33+
function Bar() {
34+
}
35+
return Bar;
36+
}());
37+
foo({
38+
x: Bar,
39+
y: Date
40+
}, getNum());
41+
foo({
42+
x: new Bar(),
43+
y: new Date()
44+
}, getNum);
45+
foo({
46+
x: new Bar(),
47+
y: new Date()
48+
}, getNum(), [
49+
1,
50+
2,
51+
getNum
52+
]);
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
=== tests/cases/compiler/didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts ===
2+
class Bar {
3+
>Bar : Symbol(Bar, Decl(didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts, 0, 0))
4+
5+
x!: string;
6+
>x : Symbol(Bar.x, Decl(didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts, 0, 11))
7+
}
8+
9+
declare function getNum(): number;
10+
>getNum : Symbol(getNum, Decl(didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts, 2, 1))
11+
12+
declare function foo(arg: { x: Bar, y: Date }, item: number, items?: [number, number, number]): void;
13+
>foo : Symbol(foo, Decl(didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts, 4, 34))
14+
>arg : Symbol(arg, Decl(didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts, 6, 21))
15+
>x : Symbol(x, Decl(didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts, 6, 27))
16+
>Bar : Symbol(Bar, Decl(didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts, 0, 0))
17+
>y : Symbol(y, Decl(didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts, 6, 35))
18+
>Date : Symbol(Date, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.scripthost.d.ts, --, --))
19+
>item : Symbol(item, Decl(didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts, 6, 46))
20+
>items : Symbol(items, Decl(didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts, 6, 60))
21+
22+
foo({
23+
>foo : Symbol(foo, Decl(didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts, 4, 34))
24+
25+
x: Bar,
26+
>x : Symbol(x, Decl(didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts, 8, 5))
27+
>Bar : Symbol(Bar, Decl(didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts, 0, 0))
28+
29+
y: Date
30+
>y : Symbol(y, Decl(didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts, 9, 11))
31+
>Date : Symbol(Date, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.scripthost.d.ts, --, --))
32+
33+
}, getNum());
34+
>getNum : Symbol(getNum, Decl(didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts, 2, 1))
35+
36+
foo({
37+
>foo : Symbol(foo, Decl(didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts, 4, 34))
38+
39+
x: new Bar(),
40+
>x : Symbol(x, Decl(didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts, 13, 5))
41+
>Bar : Symbol(Bar, Decl(didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts, 0, 0))
42+
43+
y: new Date()
44+
>y : Symbol(y, Decl(didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts, 14, 17))
45+
>Date : Symbol(Date, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.scripthost.d.ts, --, --))
46+
47+
}, getNum);
48+
>getNum : Symbol(getNum, Decl(didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts, 2, 1))
49+
50+
51+
foo({
52+
>foo : Symbol(foo, Decl(didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts, 4, 34))
53+
54+
x: new Bar(),
55+
>x : Symbol(x, Decl(didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts, 19, 5))
56+
>Bar : Symbol(Bar, Decl(didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts, 0, 0))
57+
58+
y: new Date()
59+
>y : Symbol(y, Decl(didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts, 20, 17))
60+
>Date : Symbol(Date, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.scripthost.d.ts, --, --))
61+
62+
}, getNum(), [
63+
>getNum : Symbol(getNum, Decl(didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts, 2, 1))
64+
65+
1,
66+
2,
67+
getNum
68+
>getNum : Symbol(getNum, Decl(didYouMeanElaborationsForExpressionsWhichCouldBeCalled.ts, 2, 1))
69+
70+
]);
71+

0 commit comments

Comments
 (0)