Narrow unknown under inequality when assumed false (#33488)

This commit is contained in:
Jack Williams
2019-09-18 18:54:42 +01:00
committed by Ryan Cavanaugh
parent 344dba8809
commit 1c20aa0b1a
6 changed files with 361 additions and 1 deletions

View File

@@ -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;
}

View File

@@ -228,4 +228,41 @@ tests/cases/conformance/types/unknown/unknownType2.ts(216,13): error TS2322: Typ
// Arguably this should be never.
type End = isTrue<isUnknown<typeof x>>
}
// 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;
}
}

View File

@@ -221,6 +221,43 @@ function switchResponseWrong(x: unknown): SomeResponse {
// Arguably this should be never.
type End = isTrue<isUnknown<typeof x>>
}
// 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;
}
}

View File

@@ -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))
}
}

View File

@@ -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
}
}

View File

@@ -222,3 +222,40 @@ function switchResponseWrong(x: unknown): SomeResponse {
// Arguably this should be never.
type End = isTrue<isUnknown<typeof x>>
}
// 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;
}
}