Skip to content

Commit f5ab714

Browse files
authored
Suppress error caused by intermediate types in getTypeOfExpression (#54380)
1 parent fb7efcf commit f5ab714

File tree

4 files changed

+163
-10
lines changed

4 files changed

+163
-10
lines changed

src/compiler/checker.ts

+16-10
Original file line numberDiff line numberDiff line change
@@ -1258,6 +1258,7 @@ export const enum CheckMode {
12581258
RestBindingElement = 1 << 6, // Checking a type that is going to be used to determine the type of a rest binding element
12591259
// e.g. in `const { a, ...rest } = foo`, when checking the type of `foo` to determine the type of `rest`,
12601260
// we need to preserve generic types instead of substituting them for constraints
1261+
TypeOnly = 1 << 7, // Called from getTypeOfExpression, diagnostics may be omitted
12611262
}
12621263

12631264
/** @internal */
@@ -36760,7 +36761,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3676036761
const rightType = getLastResult(state);
3676136762
Debug.assertIsDefined(rightType);
3676236763

36763-
result = checkBinaryLikeExpressionWorker(node.left, node.operatorToken, node.right, leftType, rightType, node);
36764+
result = checkBinaryLikeExpressionWorker(node.left, node.operatorToken, node.right, leftType, rightType, state.checkMode, node);
3676436765
}
3676536766

3676636767
state.skip = false;
@@ -36831,7 +36832,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3683136832
}
3683236833

3683336834
const rightType = checkExpression(right, checkMode);
36834-
return checkBinaryLikeExpressionWorker(left, operatorToken, right, leftType, rightType, errorNode);
36835+
return checkBinaryLikeExpressionWorker(left, operatorToken, right, leftType, rightType, checkMode, errorNode);
3683536836
}
3683636837

3683736838
function checkBinaryLikeExpressionWorker(
@@ -36840,6 +36841,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3684036841
right: Expression,
3684136842
leftType: Type,
3684236843
rightType: Type,
36844+
checkMode?: CheckMode,
3684336845
errorNode?: Node
3684436846
): Type {
3684536847
const operator = operatorToken.kind;
@@ -36993,14 +36995,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3699336995
case SyntaxKind.ExclamationEqualsToken:
3699436996
case SyntaxKind.EqualsEqualsEqualsToken:
3699536997
case SyntaxKind.ExclamationEqualsEqualsToken:
36996-
if (isLiteralExpressionOfObject(left) || isLiteralExpressionOfObject(right)) {
36997-
const eqType = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.EqualsEqualsEqualsToken;
36998-
error(errorNode, Diagnostics.This_condition_will_always_return_0_since_JavaScript_compares_objects_by_reference_not_value, eqType ? "false" : "true");
36998+
// We suppress errors in CheckMode.TypeOnly (meaning the invocation came from getTypeOfExpression). During
36999+
// control flow analysis it is possible for operands to temporarily have narrower types, and those narrower
37000+
// types may cause the operands to not be comparable. We don't want such errors reported (see #46475).
37001+
if (!(checkMode && checkMode & CheckMode.TypeOnly)) {
37002+
if (isLiteralExpressionOfObject(left) || isLiteralExpressionOfObject(right)) {
37003+
const eqType = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.EqualsEqualsEqualsToken;
37004+
error(errorNode, Diagnostics.This_condition_will_always_return_0_since_JavaScript_compares_objects_by_reference_not_value, eqType ? "false" : "true");
37005+
}
37006+
checkNaNEquality(errorNode, operator, left, right);
37007+
reportOperatorErrorUnless((left, right) => isTypeEqualityComparableTo(left, right) || isTypeEqualityComparableTo(right, left));
3699937008
}
37000-
checkNaNEquality(errorNode, operator, left, right);
37001-
reportOperatorErrorUnless((left, right) => isTypeEqualityComparableTo(left, right) || isTypeEqualityComparableTo(right, left));
3700237009
return booleanType;
37003-
3700437010
case SyntaxKind.InstanceOfKeyword:
3700537011
return checkInstanceOfExpression(left, right, leftType, rightType);
3700637012
case SyntaxKind.InKeyword:
@@ -37355,7 +37361,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3735537361
}
3735637362

3735737363
function checkConditionalExpression(node: ConditionalExpression, checkMode?: CheckMode): Type {
37358-
const type = checkTruthinessExpression(node.condition);
37364+
const type = checkTruthinessExpression(node.condition, checkMode);
3735937365
checkTestingKnownTruthyCallableOrAwaitableType(node.condition, type, node.whenTrue);
3736037366
const type1 = checkExpression(node.whenTrue, checkMode);
3736137367
const type2 = checkExpression(node.whenFalse, checkMode);
@@ -37736,7 +37742,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3773637742
}
3773737743
}
3773837744
const startInvocationCount = flowInvocationCount;
37739-
const type = checkExpression(node);
37745+
const type = checkExpression(node, CheckMode.TypeOnly);
3774037746
// If control flow analysis was required to determine the type, it is worth caching.
3774137747
if (flowInvocationCount !== startInvocationCount) {
3774237748
const cache = flowTypeCache || (flowTypeCache = []);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
=== tests/cases/conformance/controlFlow/controlFlowNoIntermediateErrors.ts ===
2+
// Repros from #46475
3+
4+
function f1() {
5+
>f1 : Symbol(f1, Decl(controlFlowNoIntermediateErrors.ts, 0, 0))
6+
7+
let code: 0 | 1 | 2 = 0;
8+
>code : Symbol(code, Decl(controlFlowNoIntermediateErrors.ts, 3, 7))
9+
10+
const otherCodes: (0 | 1 | 2)[] = [2, 0, 1, 0, 2, 2, 2, 0, 1, 0, 2, 1, 1, 0, 2, 1];
11+
>otherCodes : Symbol(otherCodes, Decl(controlFlowNoIntermediateErrors.ts, 4, 9))
12+
13+
for (const code2 of otherCodes) {
14+
>code2 : Symbol(code2, Decl(controlFlowNoIntermediateErrors.ts, 5, 14))
15+
>otherCodes : Symbol(otherCodes, Decl(controlFlowNoIntermediateErrors.ts, 4, 9))
16+
17+
if (code2 === 0) {
18+
>code2 : Symbol(code2, Decl(controlFlowNoIntermediateErrors.ts, 5, 14))
19+
20+
code = code === 2 ? 1 : 0;
21+
>code : Symbol(code, Decl(controlFlowNoIntermediateErrors.ts, 3, 7))
22+
>code : Symbol(code, Decl(controlFlowNoIntermediateErrors.ts, 3, 7))
23+
}
24+
else {
25+
code = 2;
26+
>code : Symbol(code, Decl(controlFlowNoIntermediateErrors.ts, 3, 7))
27+
}
28+
}
29+
}
30+
31+
function f2() {
32+
>f2 : Symbol(f2, Decl(controlFlowNoIntermediateErrors.ts, 13, 1))
33+
34+
let code: 0 | 1 = 0;
35+
>code : Symbol(code, Decl(controlFlowNoIntermediateErrors.ts, 16, 7))
36+
37+
while (true) {
38+
code = code === 1 ? 0 : 1;
39+
>code : Symbol(code, Decl(controlFlowNoIntermediateErrors.ts, 16, 7))
40+
>code : Symbol(code, Decl(controlFlowNoIntermediateErrors.ts, 16, 7))
41+
}
42+
}
43+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
=== tests/cases/conformance/controlFlow/controlFlowNoIntermediateErrors.ts ===
2+
// Repros from #46475
3+
4+
function f1() {
5+
>f1 : () => void
6+
7+
let code: 0 | 1 | 2 = 0;
8+
>code : 0 | 1 | 2
9+
>0 : 0
10+
11+
const otherCodes: (0 | 1 | 2)[] = [2, 0, 1, 0, 2, 2, 2, 0, 1, 0, 2, 1, 1, 0, 2, 1];
12+
>otherCodes : (0 | 1 | 2)[]
13+
>[2, 0, 1, 0, 2, 2, 2, 0, 1, 0, 2, 1, 1, 0, 2, 1] : (0 | 1 | 2)[]
14+
>2 : 2
15+
>0 : 0
16+
>1 : 1
17+
>0 : 0
18+
>2 : 2
19+
>2 : 2
20+
>2 : 2
21+
>0 : 0
22+
>1 : 1
23+
>0 : 0
24+
>2 : 2
25+
>1 : 1
26+
>1 : 1
27+
>0 : 0
28+
>2 : 2
29+
>1 : 1
30+
31+
for (const code2 of otherCodes) {
32+
>code2 : 0 | 1 | 2
33+
>otherCodes : (0 | 1 | 2)[]
34+
35+
if (code2 === 0) {
36+
>code2 === 0 : boolean
37+
>code2 : 0 | 1 | 2
38+
>0 : 0
39+
40+
code = code === 2 ? 1 : 0;
41+
>code = code === 2 ? 1 : 0 : 0 | 1
42+
>code : 0 | 1 | 2
43+
>code === 2 ? 1 : 0 : 0 | 1
44+
>code === 2 : boolean
45+
>code : 0 | 1 | 2
46+
>2 : 2
47+
>1 : 1
48+
>0 : 0
49+
}
50+
else {
51+
code = 2;
52+
>code = 2 : 2
53+
>code : 0 | 1 | 2
54+
>2 : 2
55+
}
56+
}
57+
}
58+
59+
function f2() {
60+
>f2 : () => void
61+
62+
let code: 0 | 1 = 0;
63+
>code : 0 | 1
64+
>0 : 0
65+
66+
while (true) {
67+
>true : true
68+
69+
code = code === 1 ? 0 : 1;
70+
>code = code === 1 ? 0 : 1 : 0 | 1
71+
>code : 0 | 1
72+
>code === 1 ? 0 : 1 : 0 | 1
73+
>code === 1 : boolean
74+
>code : 0 | 1
75+
>1 : 1
76+
>0 : 0
77+
>1 : 1
78+
}
79+
}
80+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// @strict: true
2+
// @noEmit: true
3+
4+
// Repros from #46475
5+
6+
function f1() {
7+
let code: 0 | 1 | 2 = 0;
8+
const otherCodes: (0 | 1 | 2)[] = [2, 0, 1, 0, 2, 2, 2, 0, 1, 0, 2, 1, 1, 0, 2, 1];
9+
for (const code2 of otherCodes) {
10+
if (code2 === 0) {
11+
code = code === 2 ? 1 : 0;
12+
}
13+
else {
14+
code = 2;
15+
}
16+
}
17+
}
18+
19+
function f2() {
20+
let code: 0 | 1 = 0;
21+
while (true) {
22+
code = code === 1 ? 0 : 1;
23+
}
24+
}

0 commit comments

Comments
 (0)