diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9a8cf2f8038..c5787c3b29d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17453,7 +17453,7 @@ namespace ts { assumeTrue = !assumeTrue; } const valueType = getTypeOfExpression(value); - if ((type.flags & TypeFlags.Unknown) && (operator === SyntaxKind.EqualsEqualsEqualsToken) && assumeTrue) { + if ((type.flags & TypeFlags.Unknown) && assumeTrue && (operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken)) { if (valueType.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive)) { return valueType; } diff --git a/tests/baselines/reference/unknownType2.errors.txt b/tests/baselines/reference/unknownType2.errors.txt index 5f08804b9e0..1469526182b 100644 --- a/tests/baselines/reference/unknownType2.errors.txt +++ b/tests/baselines/reference/unknownType2.errors.txt @@ -228,4 +228,41 @@ tests/cases/conformance/types/unknown/unknownType2.ts(216,13): error TS2322: Typ // Arguably this should be never. type End = isTrue> } + + // Repro from #33483 + + function f2(x: unknown): string | undefined { + if (x !== undefined && typeof x !== 'string') { + throw new Error(); + } + return x; + } + + function notNotEquals(u: unknown) { + if (u !== NumberEnum) { } + else { + const o: object = u; + } + + if (u !== NumberEnum.A) { } + else { + const a: NumberEnum.A = u; + } + + + if (u !== NumberEnum.A && u !== NumberEnum.B && u !== StringEnum.A) { } + else { + const aOrB: NumberEnum.A | NumberEnum.B | StringEnum.A = u; + } + + // equivalent to + if (!(u === NumberEnum.A || u === NumberEnum.B || u === StringEnum.A)) { } + else { + const aOrB: NumberEnum.A | NumberEnum.B | StringEnum.A = u; + } + } + + + + \ No newline at end of file diff --git a/tests/baselines/reference/unknownType2.js b/tests/baselines/reference/unknownType2.js index 2ec5b3ed205..cb1b0a58029 100644 --- a/tests/baselines/reference/unknownType2.js +++ b/tests/baselines/reference/unknownType2.js @@ -221,6 +221,43 @@ function switchResponseWrong(x: unknown): SomeResponse { // Arguably this should be never. type End = isTrue> } + +// Repro from #33483 + +function f2(x: unknown): string | undefined { + if (x !== undefined && typeof x !== 'string') { + throw new Error(); + } + return x; +} + +function notNotEquals(u: unknown) { + if (u !== NumberEnum) { } + else { + const o: object = u; + } + + if (u !== NumberEnum.A) { } + else { + const a: NumberEnum.A = u; + } + + + if (u !== NumberEnum.A && u !== NumberEnum.B && u !== StringEnum.A) { } + else { + const aOrB: NumberEnum.A | NumberEnum.B | StringEnum.A = u; + } + + // equivalent to + if (!(u === NumberEnum.A || u === NumberEnum.B || u === StringEnum.A)) { } + else { + const aOrB: NumberEnum.A | NumberEnum.B | StringEnum.A = u; + } +} + + + + //// [unknownType2.js] @@ -388,3 +425,29 @@ function switchResponseWrong(x) { throw new Error('Can you repeat the question?'); } } +// Repro from #33483 +function f2(x) { + if (x !== undefined && typeof x !== 'string') { + throw new Error(); + } + return x; +} +function notNotEquals(u) { + if (u !== NumberEnum) { } + else { + var o = u; + } + if (u !== NumberEnum.A) { } + else { + var a = u; + } + if (u !== NumberEnum.A && u !== NumberEnum.B && u !== StringEnum.A) { } + else { + var aOrB = u; + } + // equivalent to + if (!(u === NumberEnum.A || u === NumberEnum.B || u === StringEnum.A)) { } + else { + var aOrB = u; + } +} diff --git a/tests/baselines/reference/unknownType2.symbols b/tests/baselines/reference/unknownType2.symbols index 6e871bd4de4..19a1c7fca6e 100644 --- a/tests/baselines/reference/unknownType2.symbols +++ b/tests/baselines/reference/unknownType2.symbols @@ -582,3 +582,108 @@ function switchResponseWrong(x: unknown): SomeResponse { >x : Symbol(x, Decl(unknownType2.ts, 210, 29)) } +// Repro from #33483 + +function f2(x: unknown): string | undefined { +>f2 : Symbol(f2, Decl(unknownType2.ts, 221, 1)) +>x : Symbol(x, Decl(unknownType2.ts, 225, 12)) + + if (x !== undefined && typeof x !== 'string') { +>x : Symbol(x, Decl(unknownType2.ts, 225, 12)) +>undefined : Symbol(undefined) +>x : Symbol(x, Decl(unknownType2.ts, 225, 12)) + + throw new Error(); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + } + return x; +>x : Symbol(x, Decl(unknownType2.ts, 225, 12)) +} + +function notNotEquals(u: unknown) { +>notNotEquals : Symbol(notNotEquals, Decl(unknownType2.ts, 230, 1)) +>u : Symbol(u, Decl(unknownType2.ts, 232, 22)) + + if (u !== NumberEnum) { } +>u : Symbol(u, Decl(unknownType2.ts, 232, 22)) +>NumberEnum : Symbol(NumberEnum, Decl(unknownType2.ts, 90, 1)) + + else { + const o: object = u; +>o : Symbol(o, Decl(unknownType2.ts, 235, 13)) +>u : Symbol(u, Decl(unknownType2.ts, 232, 22)) + } + + if (u !== NumberEnum.A) { } +>u : Symbol(u, Decl(unknownType2.ts, 232, 22)) +>NumberEnum.A : Symbol(NumberEnum.A, Decl(unknownType2.ts, 92, 17)) +>NumberEnum : Symbol(NumberEnum, Decl(unknownType2.ts, 90, 1)) +>A : Symbol(NumberEnum.A, Decl(unknownType2.ts, 92, 17)) + + else { + const a: NumberEnum.A = u; +>a : Symbol(a, Decl(unknownType2.ts, 240, 13)) +>NumberEnum : Symbol(NumberEnum, Decl(unknownType2.ts, 90, 1)) +>A : Symbol(NumberEnum.A, Decl(unknownType2.ts, 92, 17)) +>u : Symbol(u, Decl(unknownType2.ts, 232, 22)) + } + + + if (u !== NumberEnum.A && u !== NumberEnum.B && u !== StringEnum.A) { } +>u : Symbol(u, Decl(unknownType2.ts, 232, 22)) +>NumberEnum.A : Symbol(NumberEnum.A, Decl(unknownType2.ts, 92, 17)) +>NumberEnum : Symbol(NumberEnum, Decl(unknownType2.ts, 90, 1)) +>A : Symbol(NumberEnum.A, Decl(unknownType2.ts, 92, 17)) +>u : Symbol(u, Decl(unknownType2.ts, 232, 22)) +>NumberEnum.B : Symbol(NumberEnum.B, Decl(unknownType2.ts, 93, 6)) +>NumberEnum : Symbol(NumberEnum, Decl(unknownType2.ts, 90, 1)) +>B : Symbol(NumberEnum.B, Decl(unknownType2.ts, 93, 6)) +>u : Symbol(u, Decl(unknownType2.ts, 232, 22)) +>StringEnum.A : Symbol(StringEnum.A, Decl(unknownType2.ts, 98, 17)) +>StringEnum : Symbol(StringEnum, Decl(unknownType2.ts, 96, 1)) +>A : Symbol(StringEnum.A, Decl(unknownType2.ts, 98, 17)) + + else { + const aOrB: NumberEnum.A | NumberEnum.B | StringEnum.A = u; +>aOrB : Symbol(aOrB, Decl(unknownType2.ts, 246, 13)) +>NumberEnum : Symbol(NumberEnum, Decl(unknownType2.ts, 90, 1)) +>A : Symbol(NumberEnum.A, Decl(unknownType2.ts, 92, 17)) +>NumberEnum : Symbol(NumberEnum, Decl(unknownType2.ts, 90, 1)) +>B : Symbol(NumberEnum.B, Decl(unknownType2.ts, 93, 6)) +>StringEnum : Symbol(StringEnum, Decl(unknownType2.ts, 96, 1)) +>A : Symbol(StringEnum.A, Decl(unknownType2.ts, 98, 17)) +>u : Symbol(u, Decl(unknownType2.ts, 232, 22)) + } + + // equivalent to + if (!(u === NumberEnum.A || u === NumberEnum.B || u === StringEnum.A)) { } +>u : Symbol(u, Decl(unknownType2.ts, 232, 22)) +>NumberEnum.A : Symbol(NumberEnum.A, Decl(unknownType2.ts, 92, 17)) +>NumberEnum : Symbol(NumberEnum, Decl(unknownType2.ts, 90, 1)) +>A : Symbol(NumberEnum.A, Decl(unknownType2.ts, 92, 17)) +>u : Symbol(u, Decl(unknownType2.ts, 232, 22)) +>NumberEnum.B : Symbol(NumberEnum.B, Decl(unknownType2.ts, 93, 6)) +>NumberEnum : Symbol(NumberEnum, Decl(unknownType2.ts, 90, 1)) +>B : Symbol(NumberEnum.B, Decl(unknownType2.ts, 93, 6)) +>u : Symbol(u, Decl(unknownType2.ts, 232, 22)) +>StringEnum.A : Symbol(StringEnum.A, Decl(unknownType2.ts, 98, 17)) +>StringEnum : Symbol(StringEnum, Decl(unknownType2.ts, 96, 1)) +>A : Symbol(StringEnum.A, Decl(unknownType2.ts, 98, 17)) + + else { + const aOrB: NumberEnum.A | NumberEnum.B | StringEnum.A = u; +>aOrB : Symbol(aOrB, Decl(unknownType2.ts, 252, 13)) +>NumberEnum : Symbol(NumberEnum, Decl(unknownType2.ts, 90, 1)) +>A : Symbol(NumberEnum.A, Decl(unknownType2.ts, 92, 17)) +>NumberEnum : Symbol(NumberEnum, Decl(unknownType2.ts, 90, 1)) +>B : Symbol(NumberEnum.B, Decl(unknownType2.ts, 93, 6)) +>StringEnum : Symbol(StringEnum, Decl(unknownType2.ts, 96, 1)) +>A : Symbol(StringEnum.A, Decl(unknownType2.ts, 98, 17)) +>u : Symbol(u, Decl(unknownType2.ts, 232, 22)) + } +} + + + + + diff --git a/tests/baselines/reference/unknownType2.types b/tests/baselines/reference/unknownType2.types index 7fc3c3d1038..ce3332138d5 100644 --- a/tests/baselines/reference/unknownType2.types +++ b/tests/baselines/reference/unknownType2.types @@ -627,3 +627,121 @@ function switchResponseWrong(x: unknown): SomeResponse { >x : unknown } +// Repro from #33483 + +function f2(x: unknown): string | undefined { +>f2 : (x: unknown) => string | undefined +>x : unknown + + if (x !== undefined && typeof x !== 'string') { +>x !== undefined && typeof x !== 'string' : boolean +>x !== undefined : boolean +>x : unknown +>undefined : undefined +>typeof x !== 'string' : boolean +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : unknown +>'string' : "string" + + throw new Error(); +>new Error() : Error +>Error : ErrorConstructor + } + return x; +>x : string | undefined +} + +function notNotEquals(u: unknown) { +>notNotEquals : (u: unknown) => void +>u : unknown + + if (u !== NumberEnum) { } +>u !== NumberEnum : boolean +>u : unknown +>NumberEnum : typeof NumberEnum + + else { + const o: object = u; +>o : object +>u : object + } + + if (u !== NumberEnum.A) { } +>u !== NumberEnum.A : boolean +>u : unknown +>NumberEnum.A : NumberEnum.A +>NumberEnum : typeof NumberEnum +>A : NumberEnum.A + + else { + const a: NumberEnum.A = u; +>a : NumberEnum.A +>NumberEnum : any +>u : NumberEnum.A + } + + + if (u !== NumberEnum.A && u !== NumberEnum.B && u !== StringEnum.A) { } +>u !== NumberEnum.A && u !== NumberEnum.B && u !== StringEnum.A : boolean +>u !== NumberEnum.A && u !== NumberEnum.B : boolean +>u !== NumberEnum.A : boolean +>u : unknown +>NumberEnum.A : NumberEnum.A +>NumberEnum : typeof NumberEnum +>A : NumberEnum.A +>u !== NumberEnum.B : boolean +>u : unknown +>NumberEnum.B : NumberEnum.B +>NumberEnum : typeof NumberEnum +>B : NumberEnum.B +>u !== StringEnum.A : boolean +>u : unknown +>StringEnum.A : StringEnum.A +>StringEnum : typeof StringEnum +>A : StringEnum.A + + else { + const aOrB: NumberEnum.A | NumberEnum.B | StringEnum.A = u; +>aOrB : NumberEnum.A | NumberEnum.B | StringEnum.A +>NumberEnum : any +>NumberEnum : any +>StringEnum : any +>u : NumberEnum.A | NumberEnum.B | StringEnum.A + } + + // equivalent to + if (!(u === NumberEnum.A || u === NumberEnum.B || u === StringEnum.A)) { } +>!(u === NumberEnum.A || u === NumberEnum.B || u === StringEnum.A) : boolean +>(u === NumberEnum.A || u === NumberEnum.B || u === StringEnum.A) : boolean +>u === NumberEnum.A || u === NumberEnum.B || u === StringEnum.A : boolean +>u === NumberEnum.A || u === NumberEnum.B : boolean +>u === NumberEnum.A : boolean +>u : unknown +>NumberEnum.A : NumberEnum.A +>NumberEnum : typeof NumberEnum +>A : NumberEnum.A +>u === NumberEnum.B : boolean +>u : unknown +>NumberEnum.B : NumberEnum.B +>NumberEnum : typeof NumberEnum +>B : NumberEnum.B +>u === StringEnum.A : boolean +>u : unknown +>StringEnum.A : StringEnum.A +>StringEnum : typeof StringEnum +>A : StringEnum.A + + else { + const aOrB: NumberEnum.A | NumberEnum.B | StringEnum.A = u; +>aOrB : NumberEnum.A | NumberEnum.B | StringEnum.A +>NumberEnum : any +>NumberEnum : any +>StringEnum : any +>u : NumberEnum.A | NumberEnum.B | StringEnum.A + } +} + + + + + diff --git a/tests/cases/conformance/types/unknown/unknownType2.ts b/tests/cases/conformance/types/unknown/unknownType2.ts index b3d16654b80..7da3093634e 100644 --- a/tests/cases/conformance/types/unknown/unknownType2.ts +++ b/tests/cases/conformance/types/unknown/unknownType2.ts @@ -222,3 +222,40 @@ function switchResponseWrong(x: unknown): SomeResponse { // Arguably this should be never. type End = isTrue> } + +// Repro from #33483 + +function f2(x: unknown): string | undefined { + if (x !== undefined && typeof x !== 'string') { + throw new Error(); + } + return x; +} + +function notNotEquals(u: unknown) { + if (u !== NumberEnum) { } + else { + const o: object = u; + } + + if (u !== NumberEnum.A) { } + else { + const a: NumberEnum.A = u; + } + + + if (u !== NumberEnum.A && u !== NumberEnum.B && u !== StringEnum.A) { } + else { + const aOrB: NumberEnum.A | NumberEnum.B | StringEnum.A = u; + } + + // equivalent to + if (!(u === NumberEnum.A || u === NumberEnum.B || u === StringEnum.A)) { } + else { + const aOrB: NumberEnum.A | NumberEnum.B | StringEnum.A = u; + } +} + + + +