Skip to content

Commit 3433434

Browse files
Fixed issue where missing method call went unreported if the call target symbol did no have an id assigned or if the called property was used inside the if block on a different target. (microsoft#35862)
1 parent 0e15b9f commit 3433434

6 files changed

+268
-5
lines changed

src/compiler/checker.ts

+25-2
Original file line numberDiff line numberDiff line change
@@ -31620,8 +31620,31 @@ namespace ts {
3162031620
const functionIsUsedInBody = forEachChild(body, function check(childNode): boolean | undefined {
3162131621
if (isIdentifier(childNode)) {
3162231622
const childSymbol = getSymbolAtLocation(childNode);
31623-
if (childSymbol && childSymbol.id === testedFunctionSymbol.id) {
31624-
return true;
31623+
if (childSymbol && childSymbol === testedFunctionSymbol) {
31624+
// If the test was a simple identifier, the above check is sufficient
31625+
if (isIdentifier(ifStatement.expression)) {
31626+
return true;
31627+
}
31628+
// Otherwise we need to ensure the symbol is called on the same target
31629+
let testedExpression = testedNode.parent;
31630+
let childExpression = childNode.parent;
31631+
while (testedExpression && childExpression) {
31632+
31633+
if (isIdentifier(testedExpression) && isIdentifier(childExpression)) {
31634+
return getSymbolAtLocation(testedExpression) === getSymbolAtLocation(childExpression);
31635+
}
31636+
31637+
if (isPropertyAccessExpression(testedExpression) && isPropertyAccessExpression(childExpression)) {
31638+
if (getSymbolAtLocation(testedExpression.name) !== getSymbolAtLocation(childExpression.name)) {
31639+
return false;
31640+
}
31641+
childExpression = childExpression.expression;
31642+
testedExpression = testedExpression.expression;
31643+
}
31644+
else {
31645+
return false;
31646+
}
31647+
}
3162531648
}
3162631649
}
3162731650

tests/baselines/reference/truthinessCallExpressionCoercion.errors.txt

+32-2
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ tests/cases/compiler/truthinessCallExpressionCoercion.ts(18,9): error TS2774: Th
33
tests/cases/compiler/truthinessCallExpressionCoercion.ts(36,9): error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead?
44
tests/cases/compiler/truthinessCallExpressionCoercion.ts(50,9): error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead?
55
tests/cases/compiler/truthinessCallExpressionCoercion.ts(66,13): error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead?
6+
tests/cases/compiler/truthinessCallExpressionCoercion.ts(76,9): error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead?
7+
tests/cases/compiler/truthinessCallExpressionCoercion.ts(82,9): error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead?
68

79

8-
==== tests/cases/compiler/truthinessCallExpressionCoercion.ts (5 errors) ====
10+
==== tests/cases/compiler/truthinessCallExpressionCoercion.ts (7 errors) ====
911
function onlyErrorsWhenTestingNonNullableFunctionType(required: () => boolean, optional?: () => boolean) {
1012
if (required) { // error
1113
~~~~~~~~
@@ -88,4 +90,32 @@ tests/cases/compiler/truthinessCallExpressionCoercion.ts(66,13): error TS2774: T
8890
}
8991
}
9092
}
91-
93+
94+
// Test for GH-35557 where ids were not assigned for a symbol.
95+
function A(stats: StatsBase<any>) {
96+
if (stats.isDirectory) { // err
97+
~~~~~~~~~~~~~~~~~
98+
!!! error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead?
99+
console.log(`[Directory] ${stats.ctime}`)
100+
}
101+
}
102+
103+
function B(a: Nested, b: Nested) {
104+
if (a.stats.isDirectory) { // err
105+
~~~~~~~~~~~~~~~~~~~
106+
!!! error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead?
107+
b.stats.isDirectory();
108+
}
109+
if (a.stats.isDirectory) { // ok
110+
a.stats.isDirectory();
111+
}
112+
}
113+
114+
interface StatsBase<T> {
115+
isDirectory(): boolean;
116+
ctime: number;
117+
}
118+
119+
interface Nested {
120+
stats: StatsBase<any>;
121+
}

tests/baselines/reference/truthinessCallExpressionCoercion.js

+39-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,31 @@ class Foo {
7171
}
7272
}
7373
}
74-
74+
75+
// Test for GH-35557 where ids were not assigned for a symbol.
76+
function A(stats: StatsBase<any>) {
77+
if (stats.isDirectory) { // err
78+
console.log(`[Directory] ${stats.ctime}`)
79+
}
80+
}
81+
82+
function B(a: Nested, b: Nested) {
83+
if (a.stats.isDirectory) { // err
84+
b.stats.isDirectory();
85+
}
86+
if (a.stats.isDirectory) { // ok
87+
a.stats.isDirectory();
88+
}
89+
}
90+
91+
interface StatsBase<T> {
92+
isDirectory(): boolean;
93+
ctime: number;
94+
}
95+
96+
interface Nested {
97+
stats: StatsBase<any>;
98+
}
7599

76100
//// [truthinessCallExpressionCoercion.js]
77101
function onlyErrorsWhenTestingNonNullableFunctionType(required, optional) {
@@ -132,3 +156,17 @@ var Foo = /** @class */ (function () {
132156
};
133157
return Foo;
134158
}());
159+
// Test for GH-35557 where ids were not assigned for a symbol.
160+
function A(stats) {
161+
if (stats.isDirectory) { // err
162+
console.log("[Directory] " + stats.ctime);
163+
}
164+
}
165+
function B(a, b) {
166+
if (a.stats.isDirectory) { // err
167+
b.stats.isDirectory();
168+
}
169+
if (a.stats.isDirectory) { // ok
170+
a.stats.isDirectory();
171+
}
172+
}

tests/baselines/reference/truthinessCallExpressionCoercion.symbols

+76
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,79 @@ class Foo {
151151
}
152152
}
153153

154+
// Test for GH-35557 where ids were not assigned for a symbol.
155+
function A(stats: StatsBase<any>) {
156+
>A : Symbol(A, Decl(truthinessCallExpressionCoercion.ts, 71, 1))
157+
>stats : Symbol(stats, Decl(truthinessCallExpressionCoercion.ts, 74, 11))
158+
>StatsBase : Symbol(StatsBase, Decl(truthinessCallExpressionCoercion.ts, 87, 1))
159+
160+
if (stats.isDirectory) { // err
161+
>stats.isDirectory : Symbol(StatsBase.isDirectory, Decl(truthinessCallExpressionCoercion.ts, 89, 24))
162+
>stats : Symbol(stats, Decl(truthinessCallExpressionCoercion.ts, 74, 11))
163+
>isDirectory : Symbol(StatsBase.isDirectory, Decl(truthinessCallExpressionCoercion.ts, 89, 24))
164+
165+
console.log(`[Directory] ${stats.ctime}`)
166+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
167+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
168+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
169+
>stats.ctime : Symbol(StatsBase.ctime, Decl(truthinessCallExpressionCoercion.ts, 90, 27))
170+
>stats : Symbol(stats, Decl(truthinessCallExpressionCoercion.ts, 74, 11))
171+
>ctime : Symbol(StatsBase.ctime, Decl(truthinessCallExpressionCoercion.ts, 90, 27))
172+
}
173+
}
174+
175+
function B(a: Nested, b: Nested) {
176+
>B : Symbol(B, Decl(truthinessCallExpressionCoercion.ts, 78, 1))
177+
>a : Symbol(a, Decl(truthinessCallExpressionCoercion.ts, 80, 11))
178+
>Nested : Symbol(Nested, Decl(truthinessCallExpressionCoercion.ts, 92, 1))
179+
>b : Symbol(b, Decl(truthinessCallExpressionCoercion.ts, 80, 21))
180+
>Nested : Symbol(Nested, Decl(truthinessCallExpressionCoercion.ts, 92, 1))
181+
182+
if (a.stats.isDirectory) { // err
183+
>a.stats.isDirectory : Symbol(StatsBase.isDirectory, Decl(truthinessCallExpressionCoercion.ts, 89, 24))
184+
>a.stats : Symbol(Nested.stats, Decl(truthinessCallExpressionCoercion.ts, 94, 18))
185+
>a : Symbol(a, Decl(truthinessCallExpressionCoercion.ts, 80, 11))
186+
>stats : Symbol(Nested.stats, Decl(truthinessCallExpressionCoercion.ts, 94, 18))
187+
>isDirectory : Symbol(StatsBase.isDirectory, Decl(truthinessCallExpressionCoercion.ts, 89, 24))
188+
189+
b.stats.isDirectory();
190+
>b.stats.isDirectory : Symbol(StatsBase.isDirectory, Decl(truthinessCallExpressionCoercion.ts, 89, 24))
191+
>b.stats : Symbol(Nested.stats, Decl(truthinessCallExpressionCoercion.ts, 94, 18))
192+
>b : Symbol(b, Decl(truthinessCallExpressionCoercion.ts, 80, 21))
193+
>stats : Symbol(Nested.stats, Decl(truthinessCallExpressionCoercion.ts, 94, 18))
194+
>isDirectory : Symbol(StatsBase.isDirectory, Decl(truthinessCallExpressionCoercion.ts, 89, 24))
195+
}
196+
if (a.stats.isDirectory) { // ok
197+
>a.stats.isDirectory : Symbol(StatsBase.isDirectory, Decl(truthinessCallExpressionCoercion.ts, 89, 24))
198+
>a.stats : Symbol(Nested.stats, Decl(truthinessCallExpressionCoercion.ts, 94, 18))
199+
>a : Symbol(a, Decl(truthinessCallExpressionCoercion.ts, 80, 11))
200+
>stats : Symbol(Nested.stats, Decl(truthinessCallExpressionCoercion.ts, 94, 18))
201+
>isDirectory : Symbol(StatsBase.isDirectory, Decl(truthinessCallExpressionCoercion.ts, 89, 24))
202+
203+
a.stats.isDirectory();
204+
>a.stats.isDirectory : Symbol(StatsBase.isDirectory, Decl(truthinessCallExpressionCoercion.ts, 89, 24))
205+
>a.stats : Symbol(Nested.stats, Decl(truthinessCallExpressionCoercion.ts, 94, 18))
206+
>a : Symbol(a, Decl(truthinessCallExpressionCoercion.ts, 80, 11))
207+
>stats : Symbol(Nested.stats, Decl(truthinessCallExpressionCoercion.ts, 94, 18))
208+
>isDirectory : Symbol(StatsBase.isDirectory, Decl(truthinessCallExpressionCoercion.ts, 89, 24))
209+
}
210+
}
211+
212+
interface StatsBase<T> {
213+
>StatsBase : Symbol(StatsBase, Decl(truthinessCallExpressionCoercion.ts, 87, 1))
214+
>T : Symbol(T, Decl(truthinessCallExpressionCoercion.ts, 89, 20))
215+
216+
isDirectory(): boolean;
217+
>isDirectory : Symbol(StatsBase.isDirectory, Decl(truthinessCallExpressionCoercion.ts, 89, 24))
218+
219+
ctime: number;
220+
>ctime : Symbol(StatsBase.ctime, Decl(truthinessCallExpressionCoercion.ts, 90, 27))
221+
}
222+
223+
interface Nested {
224+
>Nested : Symbol(Nested, Decl(truthinessCallExpressionCoercion.ts, 92, 1))
225+
226+
stats: StatsBase<any>;
227+
>stats : Symbol(Nested.stats, Decl(truthinessCallExpressionCoercion.ts, 94, 18))
228+
>StatsBase : Symbol(StatsBase, Decl(truthinessCallExpressionCoercion.ts, 87, 1))
229+
}

tests/baselines/reference/truthinessCallExpressionCoercion.types

+71
Original file line numberDiff line numberDiff line change
@@ -177,3 +177,74 @@ class Foo {
177177
}
178178
}
179179

180+
// Test for GH-35557 where ids were not assigned for a symbol.
181+
function A(stats: StatsBase<any>) {
182+
>A : (stats: StatsBase<any>) => void
183+
>stats : StatsBase<any>
184+
185+
if (stats.isDirectory) { // err
186+
>stats.isDirectory : () => boolean
187+
>stats : StatsBase<any>
188+
>isDirectory : () => boolean
189+
190+
console.log(`[Directory] ${stats.ctime}`)
191+
>console.log(`[Directory] ${stats.ctime}`) : void
192+
>console.log : (message?: any, ...optionalParams: any[]) => void
193+
>console : Console
194+
>log : (message?: any, ...optionalParams: any[]) => void
195+
>`[Directory] ${stats.ctime}` : string
196+
>stats.ctime : number
197+
>stats : StatsBase<any>
198+
>ctime : number
199+
}
200+
}
201+
202+
function B(a: Nested, b: Nested) {
203+
>B : (a: Nested, b: Nested) => void
204+
>a : Nested
205+
>b : Nested
206+
207+
if (a.stats.isDirectory) { // err
208+
>a.stats.isDirectory : () => boolean
209+
>a.stats : StatsBase<any>
210+
>a : Nested
211+
>stats : StatsBase<any>
212+
>isDirectory : () => boolean
213+
214+
b.stats.isDirectory();
215+
>b.stats.isDirectory() : boolean
216+
>b.stats.isDirectory : () => boolean
217+
>b.stats : StatsBase<any>
218+
>b : Nested
219+
>stats : StatsBase<any>
220+
>isDirectory : () => boolean
221+
}
222+
if (a.stats.isDirectory) { // ok
223+
>a.stats.isDirectory : () => boolean
224+
>a.stats : StatsBase<any>
225+
>a : Nested
226+
>stats : StatsBase<any>
227+
>isDirectory : () => boolean
228+
229+
a.stats.isDirectory();
230+
>a.stats.isDirectory() : boolean
231+
>a.stats.isDirectory : () => boolean
232+
>a.stats : StatsBase<any>
233+
>a : Nested
234+
>stats : StatsBase<any>
235+
>isDirectory : () => boolean
236+
}
237+
}
238+
239+
interface StatsBase<T> {
240+
isDirectory(): boolean;
241+
>isDirectory : () => boolean
242+
243+
ctime: number;
244+
>ctime : number
245+
}
246+
247+
interface Nested {
248+
stats: StatsBase<any>;
249+
>stats : StatsBase<any>
250+
}

tests/cases/compiler/truthinessCallExpressionCoercion.ts

+25
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,28 @@ class Foo {
7272
}
7373
}
7474
}
75+
76+
// Test for GH-35557 where ids were not assigned for a symbol.
77+
function A(stats: StatsBase<any>) {
78+
if (stats.isDirectory) { // err
79+
console.log(`[Directory] ${stats.ctime}`)
80+
}
81+
}
82+
83+
function B(a: Nested, b: Nested) {
84+
if (a.stats.isDirectory) { // err
85+
b.stats.isDirectory();
86+
}
87+
if (a.stats.isDirectory) { // ok
88+
a.stats.isDirectory();
89+
}
90+
}
91+
92+
interface StatsBase<T> {
93+
isDirectory(): boolean;
94+
ctime: number;
95+
}
96+
97+
interface Nested {
98+
stats: StatsBase<any>;
99+
}

0 commit comments

Comments
 (0)