diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0e45abf93737d..40bf99bb2cd94 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -38493,7 +38493,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { parent = parent.parent; } if (operator === SyntaxKind.AmpersandAmpersandToken || isIfStatement(parent)) { - checkTestingKnownTruthyCallableOrAwaitableType(node.left, leftType, isIfStatement(parent) ? parent.thenStatement : undefined); + checkTestingKnownTruthyCallableOrAwaitableOrEnumMemberType(node.left, leftType, isIfStatement(parent) ? parent.thenStatement : undefined); } checkTruthinessOfType(leftType, node.left); } @@ -39127,7 +39127,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function checkConditionalExpression(node: ConditionalExpression, checkMode?: CheckMode): Type { const type = checkTruthinessExpression(node.condition, checkMode); - checkTestingKnownTruthyCallableOrAwaitableType(node.condition, type, node.whenTrue); + checkTestingKnownTruthyCallableOrAwaitableOrEnumMemberType(node.condition, type, node.whenTrue); const type1 = checkExpression(node.whenTrue, checkMode); const type2 = checkExpression(node.whenFalse, checkMode); return getUnionType([type1, type2], UnionReduction.Subtype); @@ -43101,7 +43101,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // Grammar checking checkGrammarStatementInAmbientContext(node); const type = checkTruthinessExpression(node.expression); - checkTestingKnownTruthyCallableOrAwaitableType(node.expression, type, node.thenStatement); + checkTestingKnownTruthyCallableOrAwaitableOrEnumMemberType(node.expression, type, node.thenStatement); checkSourceElement(node.thenStatement); if (node.thenStatement.kind === SyntaxKind.EmptyStatement) { @@ -43111,7 +43111,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { checkSourceElement(node.elseStatement); } - function checkTestingKnownTruthyCallableOrAwaitableType(condExpr: Expression, condType: Type, body?: Statement | Expression) { + function checkTestingKnownTruthyCallableOrAwaitableOrEnumMemberType(condExpr: Expression, condType: Type, body?: Statement | Expression) { if (!strictNullChecks) return; bothHelper(condExpr, body); @@ -43136,6 +43136,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return; } const type = location === condExpr ? condType : checkTruthinessExpression(location); + if (type.flags & TypeFlags.EnumLiteral && isPropertyAccessExpression(location) && (getNodeLinks(location.expression).resolvedSymbol ?? unknownSymbol).flags & SymbolFlags.Enum) { + // EnumLiteral type at condition with known value is always truthy or always falsy, likely an error + error(location, Diagnostics.This_condition_will_always_return_0, !!(type as LiteralType).value ? "true" : "false"); + return; + } const isPropertyExpressionCast = isPropertyAccessExpression(location) && isTypeAssertion(location.expression); if (!hasTypeFacts(type, TypeFacts.Truthy) || isPropertyExpressionCast) return; diff --git a/tests/baselines/reference/errorOnEnumReferenceInCondition.errors.txt b/tests/baselines/reference/errorOnEnumReferenceInCondition.errors.txt new file mode 100644 index 0000000000000..eadbe3fb9f53f --- /dev/null +++ b/tests/baselines/reference/errorOnEnumReferenceInCondition.errors.txt @@ -0,0 +1,73 @@ +errorOnEnumReferenceInCondition.ts(6,11): error TS2845: This condition will always return 'false'. +errorOnEnumReferenceInCondition.ts(7,11): error TS2845: This condition will always return 'true'. +errorOnEnumReferenceInCondition.ts(9,5): error TS2845: This condition will always return 'false'. +errorOnEnumReferenceInCondition.ts(17,5): error TS2845: This condition will always return 'true'. +errorOnEnumReferenceInCondition.ts(30,11): error TS2845: This condition will always return 'false'. +errorOnEnumReferenceInCondition.ts(31,11): error TS2845: This condition will always return 'true'. +errorOnEnumReferenceInCondition.ts(33,5): error TS2845: This condition will always return 'false'. +errorOnEnumReferenceInCondition.ts(41,5): error TS2845: This condition will always return 'true'. + + +==== errorOnEnumReferenceInCondition.ts (8 errors) ==== + enum Nums { + Zero = 0, + One = 1, + } + + const a = Nums.Zero ? "a" : "b"; + ~~~~~~~~~ +!!! error TS2845: This condition will always return 'false'. + const b = Nums.One ? "a" : "b"; + ~~~~~~~~ +!!! error TS2845: This condition will always return 'true'. + + if (Nums.Zero) { + ~~~~~~~~~ +!!! error TS2845: This condition will always return 'false'. + Nums; + } + else { + Nums; + } + + + if (Nums.One) { + ~~~~~~~~ +!!! error TS2845: This condition will always return 'true'. + Nums; + } + else { + Nums; + } + + + enum Strs { + Empty = "", + A = "A", + } + + const c = Strs.Empty ? "a" : "b"; + ~~~~~~~~~~ +!!! error TS2845: This condition will always return 'false'. + const d = Strs.A ? "a" : "b"; + ~~~~~~ +!!! error TS2845: This condition will always return 'true'. + + if (Strs.Empty) { + ~~~~~~~~~~ +!!! error TS2845: This condition will always return 'false'. + Strs; + } + else { + Strs; + } + + + if (Strs.A) { + ~~~~~~ +!!! error TS2845: This condition will always return 'true'. + Strs; + } + else { + Strs; + } \ No newline at end of file diff --git a/tests/baselines/reference/errorOnEnumReferenceInCondition.js b/tests/baselines/reference/errorOnEnumReferenceInCondition.js new file mode 100644 index 0000000000000..f47d995e06be2 --- /dev/null +++ b/tests/baselines/reference/errorOnEnumReferenceInCondition.js @@ -0,0 +1,90 @@ +//// [tests/cases/compiler/errorOnEnumReferenceInCondition.ts] //// + +//// [errorOnEnumReferenceInCondition.ts] +enum Nums { + Zero = 0, + One = 1, +} + +const a = Nums.Zero ? "a" : "b"; +const b = Nums.One ? "a" : "b"; + +if (Nums.Zero) { + Nums; +} +else { + Nums; +} + + +if (Nums.One) { + Nums; +} +else { + Nums; +} + + +enum Strs { + Empty = "", + A = "A", +} + +const c = Strs.Empty ? "a" : "b"; +const d = Strs.A ? "a" : "b"; + +if (Strs.Empty) { + Strs; +} +else { + Strs; +} + + +if (Strs.A) { + Strs; +} +else { + Strs; +} + +//// [errorOnEnumReferenceInCondition.js] +"use strict"; +var Nums; +(function (Nums) { + Nums[Nums["Zero"] = 0] = "Zero"; + Nums[Nums["One"] = 1] = "One"; +})(Nums || (Nums = {})); +var a = Nums.Zero ? "a" : "b"; +var b = Nums.One ? "a" : "b"; +if (Nums.Zero) { + Nums; +} +else { + Nums; +} +if (Nums.One) { + Nums; +} +else { + Nums; +} +var Strs; +(function (Strs) { + Strs["Empty"] = ""; + Strs["A"] = "A"; +})(Strs || (Strs = {})); +var c = Strs.Empty ? "a" : "b"; +var d = Strs.A ? "a" : "b"; +if (Strs.Empty) { + Strs; +} +else { + Strs; +} +if (Strs.A) { + Strs; +} +else { + Strs; +} diff --git a/tests/baselines/reference/errorOnEnumReferenceInCondition.symbols b/tests/baselines/reference/errorOnEnumReferenceInCondition.symbols new file mode 100644 index 0000000000000..dd110a06cdd1c --- /dev/null +++ b/tests/baselines/reference/errorOnEnumReferenceInCondition.symbols @@ -0,0 +1,101 @@ +//// [tests/cases/compiler/errorOnEnumReferenceInCondition.ts] //// + +=== errorOnEnumReferenceInCondition.ts === +enum Nums { +>Nums : Symbol(Nums, Decl(errorOnEnumReferenceInCondition.ts, 0, 0)) + + Zero = 0, +>Zero : Symbol(Nums.Zero, Decl(errorOnEnumReferenceInCondition.ts, 0, 11)) + + One = 1, +>One : Symbol(Nums.One, Decl(errorOnEnumReferenceInCondition.ts, 1, 13)) +} + +const a = Nums.Zero ? "a" : "b"; +>a : Symbol(a, Decl(errorOnEnumReferenceInCondition.ts, 5, 5)) +>Nums.Zero : Symbol(Nums.Zero, Decl(errorOnEnumReferenceInCondition.ts, 0, 11)) +>Nums : Symbol(Nums, Decl(errorOnEnumReferenceInCondition.ts, 0, 0)) +>Zero : Symbol(Nums.Zero, Decl(errorOnEnumReferenceInCondition.ts, 0, 11)) + +const b = Nums.One ? "a" : "b"; +>b : Symbol(b, Decl(errorOnEnumReferenceInCondition.ts, 6, 5)) +>Nums.One : Symbol(Nums.One, Decl(errorOnEnumReferenceInCondition.ts, 1, 13)) +>Nums : Symbol(Nums, Decl(errorOnEnumReferenceInCondition.ts, 0, 0)) +>One : Symbol(Nums.One, Decl(errorOnEnumReferenceInCondition.ts, 1, 13)) + +if (Nums.Zero) { +>Nums.Zero : Symbol(Nums.Zero, Decl(errorOnEnumReferenceInCondition.ts, 0, 11)) +>Nums : Symbol(Nums, Decl(errorOnEnumReferenceInCondition.ts, 0, 0)) +>Zero : Symbol(Nums.Zero, Decl(errorOnEnumReferenceInCondition.ts, 0, 11)) + + Nums; +>Nums : Symbol(Nums, Decl(errorOnEnumReferenceInCondition.ts, 0, 0)) +} +else { + Nums; +>Nums : Symbol(Nums, Decl(errorOnEnumReferenceInCondition.ts, 0, 0)) +} + + +if (Nums.One) { +>Nums.One : Symbol(Nums.One, Decl(errorOnEnumReferenceInCondition.ts, 1, 13)) +>Nums : Symbol(Nums, Decl(errorOnEnumReferenceInCondition.ts, 0, 0)) +>One : Symbol(Nums.One, Decl(errorOnEnumReferenceInCondition.ts, 1, 13)) + + Nums; +>Nums : Symbol(Nums, Decl(errorOnEnumReferenceInCondition.ts, 0, 0)) +} +else { + Nums; +>Nums : Symbol(Nums, Decl(errorOnEnumReferenceInCondition.ts, 0, 0)) +} + + +enum Strs { +>Strs : Symbol(Strs, Decl(errorOnEnumReferenceInCondition.ts, 21, 1)) + + Empty = "", +>Empty : Symbol(Strs.Empty, Decl(errorOnEnumReferenceInCondition.ts, 24, 11)) + + A = "A", +>A : Symbol(Strs.A, Decl(errorOnEnumReferenceInCondition.ts, 25, 15)) +} + +const c = Strs.Empty ? "a" : "b"; +>c : Symbol(c, Decl(errorOnEnumReferenceInCondition.ts, 29, 5)) +>Strs.Empty : Symbol(Strs.Empty, Decl(errorOnEnumReferenceInCondition.ts, 24, 11)) +>Strs : Symbol(Strs, Decl(errorOnEnumReferenceInCondition.ts, 21, 1)) +>Empty : Symbol(Strs.Empty, Decl(errorOnEnumReferenceInCondition.ts, 24, 11)) + +const d = Strs.A ? "a" : "b"; +>d : Symbol(d, Decl(errorOnEnumReferenceInCondition.ts, 30, 5)) +>Strs.A : Symbol(Strs.A, Decl(errorOnEnumReferenceInCondition.ts, 25, 15)) +>Strs : Symbol(Strs, Decl(errorOnEnumReferenceInCondition.ts, 21, 1)) +>A : Symbol(Strs.A, Decl(errorOnEnumReferenceInCondition.ts, 25, 15)) + +if (Strs.Empty) { +>Strs.Empty : Symbol(Strs.Empty, Decl(errorOnEnumReferenceInCondition.ts, 24, 11)) +>Strs : Symbol(Strs, Decl(errorOnEnumReferenceInCondition.ts, 21, 1)) +>Empty : Symbol(Strs.Empty, Decl(errorOnEnumReferenceInCondition.ts, 24, 11)) + + Strs; +>Strs : Symbol(Strs, Decl(errorOnEnumReferenceInCondition.ts, 21, 1)) +} +else { + Strs; +>Strs : Symbol(Strs, Decl(errorOnEnumReferenceInCondition.ts, 21, 1)) +} + + +if (Strs.A) { +>Strs.A : Symbol(Strs.A, Decl(errorOnEnumReferenceInCondition.ts, 25, 15)) +>Strs : Symbol(Strs, Decl(errorOnEnumReferenceInCondition.ts, 21, 1)) +>A : Symbol(Strs.A, Decl(errorOnEnumReferenceInCondition.ts, 25, 15)) + + Strs; +>Strs : Symbol(Strs, Decl(errorOnEnumReferenceInCondition.ts, 21, 1)) +} +else { + Strs; +>Strs : Symbol(Strs, Decl(errorOnEnumReferenceInCondition.ts, 21, 1)) +} diff --git a/tests/baselines/reference/errorOnEnumReferenceInCondition.types b/tests/baselines/reference/errorOnEnumReferenceInCondition.types new file mode 100644 index 0000000000000..371385330605d --- /dev/null +++ b/tests/baselines/reference/errorOnEnumReferenceInCondition.types @@ -0,0 +1,175 @@ +//// [tests/cases/compiler/errorOnEnumReferenceInCondition.ts] //// + +=== errorOnEnumReferenceInCondition.ts === +enum Nums { +>Nums : Nums +> : ^^^^ + + Zero = 0, +>Zero : Nums.Zero +> : ^^^^^^^^^ +>0 : 0 +> : ^ + + One = 1, +>One : Nums.One +> : ^^^^^^^^ +>1 : 1 +> : ^ +} + +const a = Nums.Zero ? "a" : "b"; +>a : "a" | "b" +> : ^^^^^^^^^ +>Nums.Zero ? "a" : "b" : "a" | "b" +> : ^^^^^^^^^ +>Nums.Zero : Nums.Zero +> : ^^^^^^^^^ +>Nums : typeof Nums +> : ^^^^^^^^^^^ +>Zero : Nums.Zero +> : ^^^^^^^^^ +>"a" : "a" +> : ^^^ +>"b" : "b" +> : ^^^ + +const b = Nums.One ? "a" : "b"; +>b : "a" | "b" +> : ^^^^^^^^^ +>Nums.One ? "a" : "b" : "a" | "b" +> : ^^^^^^^^^ +>Nums.One : Nums.One +> : ^^^^^^^^ +>Nums : typeof Nums +> : ^^^^^^^^^^^ +>One : Nums.One +> : ^^^^^^^^ +>"a" : "a" +> : ^^^ +>"b" : "b" +> : ^^^ + +if (Nums.Zero) { +>Nums.Zero : Nums.Zero +> : ^^^^^^^^^ +>Nums : typeof Nums +> : ^^^^^^^^^^^ +>Zero : Nums.Zero +> : ^^^^^^^^^ + + Nums; +>Nums : typeof Nums +> : ^^^^^^^^^^^ +} +else { + Nums; +>Nums : typeof Nums +> : ^^^^^^^^^^^ +} + + +if (Nums.One) { +>Nums.One : Nums.One +> : ^^^^^^^^ +>Nums : typeof Nums +> : ^^^^^^^^^^^ +>One : Nums.One +> : ^^^^^^^^ + + Nums; +>Nums : typeof Nums +> : ^^^^^^^^^^^ +} +else { + Nums; +>Nums : typeof Nums +> : ^^^^^^^^^^^ +} + + +enum Strs { +>Strs : Strs +> : ^^^^ + + Empty = "", +>Empty : Strs.Empty +> : ^^^^^^^^^^ +>"" : "" +> : ^^ + + A = "A", +>A : Strs.A +> : ^^^^^^ +>"A" : "A" +> : ^^^ +} + +const c = Strs.Empty ? "a" : "b"; +>c : "a" | "b" +> : ^^^^^^^^^ +>Strs.Empty ? "a" : "b" : "a" | "b" +> : ^^^^^^^^^ +>Strs.Empty : Strs.Empty +> : ^^^^^^^^^^ +>Strs : typeof Strs +> : ^^^^^^^^^^^ +>Empty : Strs.Empty +> : ^^^^^^^^^^ +>"a" : "a" +> : ^^^ +>"b" : "b" +> : ^^^ + +const d = Strs.A ? "a" : "b"; +>d : "a" | "b" +> : ^^^^^^^^^ +>Strs.A ? "a" : "b" : "a" | "b" +> : ^^^^^^^^^ +>Strs.A : Strs.A +> : ^^^^^^ +>Strs : typeof Strs +> : ^^^^^^^^^^^ +>A : Strs.A +> : ^^^^^^ +>"a" : "a" +> : ^^^ +>"b" : "b" +> : ^^^ + +if (Strs.Empty) { +>Strs.Empty : Strs.Empty +> : ^^^^^^^^^^ +>Strs : typeof Strs +> : ^^^^^^^^^^^ +>Empty : Strs.Empty +> : ^^^^^^^^^^ + + Strs; +>Strs : typeof Strs +> : ^^^^^^^^^^^ +} +else { + Strs; +>Strs : typeof Strs +> : ^^^^^^^^^^^ +} + + +if (Strs.A) { +>Strs.A : Strs.A +> : ^^^^^^ +>Strs : typeof Strs +> : ^^^^^^^^^^^ +>A : Strs.A +> : ^^^^^^ + + Strs; +>Strs : typeof Strs +> : ^^^^^^^^^^^ +} +else { + Strs; +>Strs : typeof Strs +> : ^^^^^^^^^^^ +} diff --git a/tests/cases/compiler/errorOnEnumReferenceInCondition.ts b/tests/cases/compiler/errorOnEnumReferenceInCondition.ts new file mode 100644 index 0000000000000..22c1a1c3411ae --- /dev/null +++ b/tests/cases/compiler/errorOnEnumReferenceInCondition.ts @@ -0,0 +1,47 @@ +// @strict: true +enum Nums { + Zero = 0, + One = 1, +} + +const a = Nums.Zero ? "a" : "b"; +const b = Nums.One ? "a" : "b"; + +if (Nums.Zero) { + Nums; +} +else { + Nums; +} + + +if (Nums.One) { + Nums; +} +else { + Nums; +} + + +enum Strs { + Empty = "", + A = "A", +} + +const c = Strs.Empty ? "a" : "b"; +const d = Strs.A ? "a" : "b"; + +if (Strs.Empty) { + Strs; +} +else { + Strs; +} + + +if (Strs.A) { + Strs; +} +else { + Strs; +} \ No newline at end of file