diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 597a506f264ae..630c6f3accead 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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 */ @@ -36753,7 +36754,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; @@ -36824,7 +36825,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( @@ -36833,6 +36834,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { right: Expression, leftType: Type, rightType: Type, + checkMode?: CheckMode, errorNode?: Node ): Type { const operator = operatorToken.kind; @@ -36986,14 +36988,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: @@ -37348,7 +37354,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); @@ -37729,7 +37735,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 = []); diff --git a/tests/baselines/reference/controlFlowNoIntermediateErrors.symbols b/tests/baselines/reference/controlFlowNoIntermediateErrors.symbols new file mode 100644 index 0000000000000..86d26f7884a4e --- /dev/null +++ b/tests/baselines/reference/controlFlowNoIntermediateErrors.symbols @@ -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)) + } +} + diff --git a/tests/baselines/reference/controlFlowNoIntermediateErrors.types b/tests/baselines/reference/controlFlowNoIntermediateErrors.types new file mode 100644 index 0000000000000..2d6d9e0741861 --- /dev/null +++ b/tests/baselines/reference/controlFlowNoIntermediateErrors.types @@ -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 + } +} + diff --git a/tests/cases/conformance/controlFlow/controlFlowNoIntermediateErrors.ts b/tests/cases/conformance/controlFlow/controlFlowNoIntermediateErrors.ts new file mode 100644 index 0000000000000..670e2097090d4 --- /dev/null +++ b/tests/cases/conformance/controlFlow/controlFlowNoIntermediateErrors.ts @@ -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; + } +}