Skip to content

Commit 5fb24d1

Browse files
committed
feat(47698): handle uncalled function statements
1 parent 9b0f01a commit 5fb24d1

26 files changed

+1763
-8
lines changed

src/compiler/checker.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -37156,7 +37156,7 @@ namespace ts {
3715637156
function checkExpressionStatement(node: ExpressionStatement) {
3715737157
// Grammar checking
3715837158
checkGrammarStatementInAmbientContext(node);
37159-
37159+
checkUncalledFunction(node);
3716037160
checkExpression(node.expression);
3716137161
}
3716237162

@@ -39944,6 +39944,21 @@ namespace ts {
3994439944
return targetSymbol;
3994539945
}
3994639946

39947+
function checkUncalledFunction(node: ExpressionStatement) {
39948+
if (!strictNullChecks) return;
39949+
39950+
const expression = node.expression;
39951+
switch (expression.kind) {
39952+
case SyntaxKind.Identifier:
39953+
case SyntaxKind.PropertyAccessExpression:
39954+
case SyntaxKind.ElementAccessExpression: {
39955+
if (isFunctionType(getTypeOfNode(expression))) {
39956+
error(expression, Diagnostics.This_expression_refers_to_function_Did_you_mean_to_call_it_instead);
39957+
}
39958+
}
39959+
}
39960+
}
39961+
3994739962
function checkImportBinding(node: ImportEqualsDeclaration | ImportClause | NamespaceImport | ImportSpecifier) {
3994839963
checkCollisionsForDeclarationName(node, node.name);
3994939964
checkAliasSymbol(node);

src/compiler/diagnosticMessages.json

+4
Original file line numberDiff line numberDiff line change
@@ -3353,6 +3353,10 @@
33533353
"category": "Error",
33543354
"code": 2822
33553355
},
3356+
"This expression refers to function. Did you mean to call it instead?": {
3357+
"category": "Error",
3358+
"code": 2823
3359+
},
33563360
"Cannot find namespace '{0}'. Did you mean '{1}'?": {
33573361
"category": "Error",
33583362
"code": 2833

src/services/codefixes/fixMissingCallParentheses.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ namespace ts.codefix {
33
const fixId = "fixMissingCallParentheses";
44
const errorCodes = [
55
Diagnostics.This_condition_will_always_return_true_since_this_function_is_always_defined_Did_you_mean_to_call_it_instead.code,
6+
Diagnostics.This_expression_refers_to_function_Did_you_mean_to_call_it_instead.code,
67
];
78

89
registerCodeFix({

tests/baselines/reference/controlFlowOptionalChain.errors.txt

+70-1
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
tests/cases/compiler/discriminantPropertyCheck.ts(94,17): error TS2823: This expression refers to function. Did you mean to call it instead?
2+
3+
4+
==== tests/cases/compiler/discriminantPropertyCheck.ts (1 errors) ====
5+
type Item = Item1 | Item2;
6+
7+
interface Base {
8+
bar: boolean;
9+
}
10+
11+
interface Item1 extends Base {
12+
kind: "A";
13+
foo: string | undefined;
14+
baz: boolean;
15+
qux: true;
16+
}
17+
18+
interface Item2 extends Base {
19+
kind: "B";
20+
foo: string | undefined;
21+
baz: boolean;
22+
qux: false;
23+
}
24+
25+
function goo1(x: Item) {
26+
if (x.kind === "A" && x.foo !== undefined) {
27+
x.foo.length;
28+
}
29+
}
30+
31+
function goo2(x: Item) {
32+
if (x.foo !== undefined && x.kind === "A") {
33+
x.foo.length; // Error, intervening discriminant guard
34+
}
35+
}
36+
37+
function foo1(x: Item) {
38+
if (x.bar && x.foo !== undefined) {
39+
x.foo.length;
40+
}
41+
}
42+
43+
function foo2(x: Item) {
44+
if (x.foo !== undefined && x.bar) {
45+
x.foo.length;
46+
}
47+
}
48+
49+
function foo3(x: Item) {
50+
if (x.baz && x.foo !== undefined) {
51+
x.foo.length;
52+
}
53+
}
54+
55+
function foo4(x: Item) {
56+
if (x.foo !== undefined && x.baz) {
57+
x.foo.length;
58+
}
59+
}
60+
61+
function foo5(x: Item) {
62+
if (x.qux && x.foo !== undefined) {
63+
x.foo.length;
64+
}
65+
}
66+
67+
function foo6(x: Item) {
68+
if (x.foo !== undefined && x.qux) {
69+
x.foo.length; // Error, intervening discriminant guard
70+
}
71+
}
72+
73+
// Repro from #27493
74+
75+
enum Types { Str = 1, Num = 2 }
76+
77+
type Instance = StrType | NumType;
78+
79+
interface StrType {
80+
type: Types.Str;
81+
value: string;
82+
length: number;
83+
}
84+
85+
interface NumType {
86+
type: Types.Num;
87+
value: number;
88+
}
89+
90+
function func2(inst: Instance) {
91+
while (true) {
92+
switch (inst.type) {
93+
case Types.Str: {
94+
inst.value.length;
95+
break;
96+
}
97+
case Types.Num: {
98+
inst.value.toExponential;
99+
~~~~~~~~~~~~~~~~~~~~~~~~
100+
!!! error TS2823: This expression refers to function. Did you mean to call it instead?
101+
break;
102+
}
103+
}
104+
}
105+
}
106+
107+
// Repro from #29106
108+
109+
const f = (_a: string, _b: string): void => {};
110+
111+
interface A {
112+
a?: string;
113+
b?: string;
114+
}
115+
116+
interface B {
117+
a: string;
118+
b: string;
119+
}
120+
121+
type U = A | B;
122+
123+
const u: U = {} as any;
124+
125+
u.a && u.b && f(u.a, u.b);
126+
127+
u.b && u.a && f(u.a, u.b);
128+
129+
// Repro from #29012
130+
131+
type Additive = '+' | '-';
132+
type Multiplicative = '*' | '/';
133+
134+
interface AdditiveObj {
135+
key: Additive
136+
}
137+
138+
interface MultiplicativeObj {
139+
key: Multiplicative
140+
}
141+
142+
type Obj = AdditiveObj | MultiplicativeObj
143+
144+
export function foo(obj: Obj) {
145+
switch (obj.key) {
146+
case '+': {
147+
onlyPlus(obj.key);
148+
return;
149+
}
150+
}
151+
}
152+
153+
function onlyPlus(arg: '+') {
154+
return arg;
155+
}
156+
157+
// Repro from #29496
158+
159+
declare function never(value: never): never;
160+
161+
const enum BarEnum {
162+
bar1 = 1,
163+
bar2 = 2,
164+
}
165+
166+
type UnionOfBar = TypeBar1 | TypeBar2;
167+
type TypeBar1 = { type: BarEnum.bar1 };
168+
type TypeBar2 = { type: BarEnum.bar2 };
169+
170+
function func3(value: Partial<UnionOfBar>) {
171+
if (value.type !== undefined) {
172+
switch (value.type) {
173+
case BarEnum.bar1:
174+
break;
175+
case BarEnum.bar2:
176+
break;
177+
default:
178+
never(value.type);
179+
}
180+
}
181+
}
182+
183+
// Repro from #30557
184+
185+
interface TypeA {
186+
Name: "TypeA";
187+
Value1: "Cool stuff!";
188+
}
189+
190+
interface TypeB {
191+
Name: "TypeB";
192+
Value2: 0;
193+
}
194+
195+
type Type = TypeA | TypeB;
196+
197+
declare function isType(x: unknown): x is Type;
198+
199+
function WorksProperly(data: Type) {
200+
if (data.Name === "TypeA") {
201+
const value1 = data.Value1;
202+
}
203+
}
204+
205+
function DoesNotWork(data: unknown) {
206+
if (isType(data)) {
207+
if (data.Name === "TypeA") {
208+
const value1 = data.Value1;
209+
}
210+
}
211+
}
212+
213+
// Repro from #36777
214+
215+
type TestA = {
216+
type: 'testA';
217+
bananas: 3;
218+
}
219+
220+
type TestB = {
221+
type: 'testB';
222+
apples: 5;
223+
}
224+
225+
type AllTests = TestA | TestB;
226+
227+
type MapOfAllTests = Record<string, AllTests>;
228+
229+
const doTestingStuff = (mapOfTests: MapOfAllTests, ids: string[]) => {
230+
ids.forEach(id => {
231+
let test;
232+
test = mapOfTests[id];
233+
if (test.type === 'testA') {
234+
console.log(test.bananas);
235+
}
236+
switch (test.type) {
237+
case 'testA': {
238+
console.log(test.bananas);
239+
}
240+
}
241+
});
242+
};
243+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
tests/cases/compiler/iteratorsAndStrictNullChecks.ts(3,5): error TS2823: This expression refers to function. Did you mean to call it instead?
2+
3+
4+
==== tests/cases/compiler/iteratorsAndStrictNullChecks.ts (1 errors) ====
5+
// for..of
6+
for (const x of ["a", "b"]) {
7+
x.substring;
8+
~~~~~~~~~~~
9+
!!! error TS2823: This expression refers to function. Did you mean to call it instead?
10+
}
11+
12+
// Spread
13+
const xs = [1, 2, 3];
14+
const ys = [4, 5];
15+
xs.push(...ys);
16+

0 commit comments

Comments
 (0)