Suppress error caused by intermediate types in getTypeOfExpression (#54380)

This commit is contained in:
Anders Hejlsberg 2023-05-26 09:30:06 -07:00 committed by GitHub
parent fb7efcfe06
commit f5ab714d1f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 163 additions and 10 deletions

View File

@ -1258,6 +1258,7 @@ export const enum CheckMode {
RestBindingElement = 1 << 6, // Checking a type that is going to be used to determine the type of a rest binding element
// e.g. in `const { a, ...rest } = foo`, when checking the type of `foo` to determine the type of `rest`,
// we need to preserve generic types instead of substituting them for constraints
TypeOnly = 1 << 7, // Called from getTypeOfExpression, diagnostics may be omitted
}
/** @internal */
@ -36760,7 +36761,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const rightType = getLastResult(state);
Debug.assertIsDefined(rightType);
result = checkBinaryLikeExpressionWorker(node.left, node.operatorToken, node.right, leftType, rightType, node);
result = checkBinaryLikeExpressionWorker(node.left, node.operatorToken, node.right, leftType, rightType, state.checkMode, node);
}
state.skip = false;
@ -36831,7 +36832,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
const rightType = checkExpression(right, checkMode);
return checkBinaryLikeExpressionWorker(left, operatorToken, right, leftType, rightType, errorNode);
return checkBinaryLikeExpressionWorker(left, operatorToken, right, leftType, rightType, checkMode, errorNode);
}
function checkBinaryLikeExpressionWorker(
@ -36840,6 +36841,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
right: Expression,
leftType: Type,
rightType: Type,
checkMode?: CheckMode,
errorNode?: Node
): Type {
const operator = operatorToken.kind;
@ -36993,14 +36995,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
case SyntaxKind.ExclamationEqualsToken:
case SyntaxKind.EqualsEqualsEqualsToken:
case SyntaxKind.ExclamationEqualsEqualsToken:
if (isLiteralExpressionOfObject(left) || isLiteralExpressionOfObject(right)) {
const eqType = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.EqualsEqualsEqualsToken;
error(errorNode, Diagnostics.This_condition_will_always_return_0_since_JavaScript_compares_objects_by_reference_not_value, eqType ? "false" : "true");
// We suppress errors in CheckMode.TypeOnly (meaning the invocation came from getTypeOfExpression). During
// control flow analysis it is possible for operands to temporarily have narrower types, and those narrower
// types may cause the operands to not be comparable. We don't want such errors reported (see #46475).
if (!(checkMode && checkMode & CheckMode.TypeOnly)) {
if (isLiteralExpressionOfObject(left) || isLiteralExpressionOfObject(right)) {
const eqType = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.EqualsEqualsEqualsToken;
error(errorNode, Diagnostics.This_condition_will_always_return_0_since_JavaScript_compares_objects_by_reference_not_value, eqType ? "false" : "true");
}
checkNaNEquality(errorNode, operator, left, right);
reportOperatorErrorUnless((left, right) => isTypeEqualityComparableTo(left, right) || isTypeEqualityComparableTo(right, left));
}
checkNaNEquality(errorNode, operator, left, right);
reportOperatorErrorUnless((left, right) => isTypeEqualityComparableTo(left, right) || isTypeEqualityComparableTo(right, left));
return booleanType;
case SyntaxKind.InstanceOfKeyword:
return checkInstanceOfExpression(left, right, leftType, rightType);
case SyntaxKind.InKeyword:
@ -37355,7 +37361,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
function checkConditionalExpression(node: ConditionalExpression, checkMode?: CheckMode): Type {
const type = checkTruthinessExpression(node.condition);
const type = checkTruthinessExpression(node.condition, checkMode);
checkTestingKnownTruthyCallableOrAwaitableType(node.condition, type, node.whenTrue);
const type1 = checkExpression(node.whenTrue, checkMode);
const type2 = checkExpression(node.whenFalse, checkMode);
@ -37736,7 +37742,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
const startInvocationCount = flowInvocationCount;
const type = checkExpression(node);
const type = checkExpression(node, CheckMode.TypeOnly);
// If control flow analysis was required to determine the type, it is worth caching.
if (flowInvocationCount !== startInvocationCount) {
const cache = flowTypeCache || (flowTypeCache = []);

View File

@ -0,0 +1,43 @@
=== tests/cases/conformance/controlFlow/controlFlowNoIntermediateErrors.ts ===
// Repros from #46475
function f1() {
>f1 : Symbol(f1, Decl(controlFlowNoIntermediateErrors.ts, 0, 0))
let code: 0 | 1 | 2 = 0;
>code : Symbol(code, Decl(controlFlowNoIntermediateErrors.ts, 3, 7))
const otherCodes: (0 | 1 | 2)[] = [2, 0, 1, 0, 2, 2, 2, 0, 1, 0, 2, 1, 1, 0, 2, 1];
>otherCodes : Symbol(otherCodes, Decl(controlFlowNoIntermediateErrors.ts, 4, 9))
for (const code2 of otherCodes) {
>code2 : Symbol(code2, Decl(controlFlowNoIntermediateErrors.ts, 5, 14))
>otherCodes : Symbol(otherCodes, Decl(controlFlowNoIntermediateErrors.ts, 4, 9))
if (code2 === 0) {
>code2 : Symbol(code2, Decl(controlFlowNoIntermediateErrors.ts, 5, 14))
code = code === 2 ? 1 : 0;
>code : Symbol(code, Decl(controlFlowNoIntermediateErrors.ts, 3, 7))
>code : Symbol(code, Decl(controlFlowNoIntermediateErrors.ts, 3, 7))
}
else {
code = 2;
>code : Symbol(code, Decl(controlFlowNoIntermediateErrors.ts, 3, 7))
}
}
}
function f2() {
>f2 : Symbol(f2, Decl(controlFlowNoIntermediateErrors.ts, 13, 1))
let code: 0 | 1 = 0;
>code : Symbol(code, Decl(controlFlowNoIntermediateErrors.ts, 16, 7))
while (true) {
code = code === 1 ? 0 : 1;
>code : Symbol(code, Decl(controlFlowNoIntermediateErrors.ts, 16, 7))
>code : Symbol(code, Decl(controlFlowNoIntermediateErrors.ts, 16, 7))
}
}

View File

@ -0,0 +1,80 @@
=== tests/cases/conformance/controlFlow/controlFlowNoIntermediateErrors.ts ===
// Repros from #46475
function f1() {
>f1 : () => void
let code: 0 | 1 | 2 = 0;
>code : 0 | 1 | 2
>0 : 0
const otherCodes: (0 | 1 | 2)[] = [2, 0, 1, 0, 2, 2, 2, 0, 1, 0, 2, 1, 1, 0, 2, 1];
>otherCodes : (0 | 1 | 2)[]
>[2, 0, 1, 0, 2, 2, 2, 0, 1, 0, 2, 1, 1, 0, 2, 1] : (0 | 1 | 2)[]
>2 : 2
>0 : 0
>1 : 1
>0 : 0
>2 : 2
>2 : 2
>2 : 2
>0 : 0
>1 : 1
>0 : 0
>2 : 2
>1 : 1
>1 : 1
>0 : 0
>2 : 2
>1 : 1
for (const code2 of otherCodes) {
>code2 : 0 | 1 | 2
>otherCodes : (0 | 1 | 2)[]
if (code2 === 0) {
>code2 === 0 : boolean
>code2 : 0 | 1 | 2
>0 : 0
code = code === 2 ? 1 : 0;
>code = code === 2 ? 1 : 0 : 0 | 1
>code : 0 | 1 | 2
>code === 2 ? 1 : 0 : 0 | 1
>code === 2 : boolean
>code : 0 | 1 | 2
>2 : 2
>1 : 1
>0 : 0
}
else {
code = 2;
>code = 2 : 2
>code : 0 | 1 | 2
>2 : 2
}
}
}
function f2() {
>f2 : () => void
let code: 0 | 1 = 0;
>code : 0 | 1
>0 : 0
while (true) {
>true : true
code = code === 1 ? 0 : 1;
>code = code === 1 ? 0 : 1 : 0 | 1
>code : 0 | 1
>code === 1 ? 0 : 1 : 0 | 1
>code === 1 : boolean
>code : 0 | 1
>1 : 1
>0 : 0
>1 : 1
}
}

View File

@ -0,0 +1,24 @@
// @strict: true
// @noEmit: true
// Repros from #46475
function f1() {
let code: 0 | 1 | 2 = 0;
const otherCodes: (0 | 1 | 2)[] = [2, 0, 1, 0, 2, 2, 2, 0, 1, 0, 2, 1, 1, 0, 2, 1];
for (const code2 of otherCodes) {
if (code2 === 0) {
code = code === 2 ? 1 : 0;
}
else {
code = 2;
}
}
}
function f2() {
let code: 0 | 1 = 0;
while (true) {
code = code === 1 ? 0 : 1;
}
}