From 267e5989cd7fdd3b6a34661e269c1c8634b835d9 Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Thu, 6 Sep 2018 16:56:31 +0100 Subject: [PATCH] Add narrowing for unknown with strict equal --- src/compiler/checker.ts | 3 + tests/baselines/reference/unknownType2.js | 82 ++++++++++ .../baselines/reference/unknownType2.symbols | 124 +++++++++++++++ tests/baselines/reference/unknownType2.types | 148 ++++++++++++++++++ .../conformance/types/unknown/unknownType2.ts | 51 ++++++ 5 files changed, 408 insertions(+) create mode 100644 tests/baselines/reference/unknownType2.js create mode 100644 tests/baselines/reference/unknownType2.symbols create mode 100644 tests/baselines/reference/unknownType2.types create mode 100644 tests/cases/conformance/types/unknown/unknownType2.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8947f98f86..96d6437e62 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16018,6 +16018,9 @@ namespace ts { assumeTrue = !assumeTrue; } const valueType = getTypeOfExpression(value); + if ((type.flags & TypeFlags.Unknown) && (operator === SyntaxKind.EqualsEqualsEqualsToken) && (valueType.flags & TypeFlags.Unit)) { + return assumeTrue ? valueType : type; + } if (valueType.flags & TypeFlags.Nullable) { if (!strictNullChecks) { return type; diff --git a/tests/baselines/reference/unknownType2.js b/tests/baselines/reference/unknownType2.js new file mode 100644 index 0000000000..59a63b1bd9 --- /dev/null +++ b/tests/baselines/reference/unknownType2.js @@ -0,0 +1,82 @@ +//// [unknownType2.ts] +type isUnknown = unknown extends T ? true : false; +type isTrue = T; + +type SomeResponse = 'yes' | 'no' | 'idk'; +let validate: (x: unknown) => SomeResponse = x => (x === 'yes' || x === 'no') ? x : 'idk'; // No error + +const u: unknown = undefined; + +declare const symb: unique symbol; + +if (u === 5) { + const y = u.toString(10); +} + +if (u === true || u === false) { + const someBool: boolean = u; +} + +if (u === undefined) { + const undef: undefined = u; +} + +if (u === null) { + const someNull: null = u; +} + +if(u === symb) { + const symbolAlias: typeof symb = u; +} + +if (!(u === 42)) { + u // u should still be `unknown` here +} + +if (u !== 42) { + type A = isTrue> +} + +if (u == 42) { + type B = isTrue> +} + +if (u == true) { + type C = isTrue> +} + +if (u == Object) { + type D = isTrue> +} + + +//// [unknownType2.js] +"use strict"; +var validate = function (x) { return (x === 'yes' || x === 'no') ? x : 'idk'; }; // No error +var u = undefined; +if (u === 5) { + var y = u.toString(10); +} +if (u === true || u === false) { + var someBool = u; +} +if (u === undefined) { + var undef = u; +} +if (u === null) { + var someNull = u; +} +if (u === symb) { + var symbolAlias = u; +} +if (!(u === 42)) { + u; // u should still be `unknown` here +} +if (u !== 42) { +} +if (u == 42) { +} +if (u == true) { +} +if (u == Object) { +} diff --git a/tests/baselines/reference/unknownType2.symbols b/tests/baselines/reference/unknownType2.symbols new file mode 100644 index 0000000000..0cd1ded5d8 --- /dev/null +++ b/tests/baselines/reference/unknownType2.symbols @@ -0,0 +1,124 @@ +=== tests/cases/conformance/types/unknown/unknownType2.ts === +type isUnknown = unknown extends T ? true : false; +>isUnknown : Symbol(isUnknown, Decl(unknownType2.ts, 0, 0)) +>T : Symbol(T, Decl(unknownType2.ts, 0, 15)) +>T : Symbol(T, Decl(unknownType2.ts, 0, 15)) + +type isTrue = T; +>isTrue : Symbol(isTrue, Decl(unknownType2.ts, 0, 53)) +>T : Symbol(T, Decl(unknownType2.ts, 1, 12)) +>T : Symbol(T, Decl(unknownType2.ts, 1, 12)) + +type SomeResponse = 'yes' | 'no' | 'idk'; +>SomeResponse : Symbol(SomeResponse, Decl(unknownType2.ts, 1, 32)) + +let validate: (x: unknown) => SomeResponse = x => (x === 'yes' || x === 'no') ? x : 'idk'; // No error +>validate : Symbol(validate, Decl(unknownType2.ts, 4, 3)) +>x : Symbol(x, Decl(unknownType2.ts, 4, 15)) +>SomeResponse : Symbol(SomeResponse, Decl(unknownType2.ts, 1, 32)) +>x : Symbol(x, Decl(unknownType2.ts, 4, 44)) +>x : Symbol(x, Decl(unknownType2.ts, 4, 44)) +>x : Symbol(x, Decl(unknownType2.ts, 4, 44)) +>x : Symbol(x, Decl(unknownType2.ts, 4, 44)) + +const u: unknown = undefined; +>u : Symbol(u, Decl(unknownType2.ts, 6, 5)) +>undefined : Symbol(undefined) + +declare const symb: unique symbol; +>symb : Symbol(symb, Decl(unknownType2.ts, 8, 13)) + +if (u === 5) { +>u : Symbol(u, Decl(unknownType2.ts, 6, 5)) + + const y = u.toString(10); +>y : Symbol(y, Decl(unknownType2.ts, 11, 9)) +>u.toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +>u : Symbol(u, Decl(unknownType2.ts, 6, 5)) +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +} + +if (u === true || u === false) { +>u : Symbol(u, Decl(unknownType2.ts, 6, 5)) +>u : Symbol(u, Decl(unknownType2.ts, 6, 5)) + + const someBool: boolean = u; +>someBool : Symbol(someBool, Decl(unknownType2.ts, 15, 9)) +>u : Symbol(u, Decl(unknownType2.ts, 6, 5)) +} + +if (u === undefined) { +>u : Symbol(u, Decl(unknownType2.ts, 6, 5)) +>undefined : Symbol(undefined) + + const undef: undefined = u; +>undef : Symbol(undef, Decl(unknownType2.ts, 19, 9)) +>u : Symbol(u, Decl(unknownType2.ts, 6, 5)) +} + +if (u === null) { +>u : Symbol(u, Decl(unknownType2.ts, 6, 5)) + + const someNull: null = u; +>someNull : Symbol(someNull, Decl(unknownType2.ts, 23, 9)) +>u : Symbol(u, Decl(unknownType2.ts, 6, 5)) +} + +if(u === symb) { +>u : Symbol(u, Decl(unknownType2.ts, 6, 5)) +>symb : Symbol(symb, Decl(unknownType2.ts, 8, 13)) + + const symbolAlias: typeof symb = u; +>symbolAlias : Symbol(symbolAlias, Decl(unknownType2.ts, 27, 9)) +>symb : Symbol(symb, Decl(unknownType2.ts, 8, 13)) +>u : Symbol(u, Decl(unknownType2.ts, 6, 5)) +} + +if (!(u === 42)) { +>u : Symbol(u, Decl(unknownType2.ts, 6, 5)) + + u // u should still be `unknown` here +>u : Symbol(u, Decl(unknownType2.ts, 6, 5)) +} + +if (u !== 42) { +>u : Symbol(u, Decl(unknownType2.ts, 6, 5)) + + type A = isTrue> +>A : Symbol(A, Decl(unknownType2.ts, 34, 15)) +>isTrue : Symbol(isTrue, Decl(unknownType2.ts, 0, 53)) +>isUnknown : Symbol(isUnknown, Decl(unknownType2.ts, 0, 0)) +>u : Symbol(u, Decl(unknownType2.ts, 6, 5)) +} + +if (u == 42) { +>u : Symbol(u, Decl(unknownType2.ts, 6, 5)) + + type B = isTrue> +>B : Symbol(B, Decl(unknownType2.ts, 38, 14)) +>isTrue : Symbol(isTrue, Decl(unknownType2.ts, 0, 53)) +>isUnknown : Symbol(isUnknown, Decl(unknownType2.ts, 0, 0)) +>u : Symbol(u, Decl(unknownType2.ts, 6, 5)) +} + +if (u == true) { +>u : Symbol(u, Decl(unknownType2.ts, 6, 5)) + + type C = isTrue> +>C : Symbol(C, Decl(unknownType2.ts, 42, 16)) +>isTrue : Symbol(isTrue, Decl(unknownType2.ts, 0, 53)) +>isUnknown : Symbol(isUnknown, Decl(unknownType2.ts, 0, 0)) +>u : Symbol(u, Decl(unknownType2.ts, 6, 5)) +} + +if (u == Object) { +>u : Symbol(u, Decl(unknownType2.ts, 6, 5)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + + type D = isTrue> +>D : Symbol(D, Decl(unknownType2.ts, 46, 18)) +>isTrue : Symbol(isTrue, Decl(unknownType2.ts, 0, 53)) +>isUnknown : Symbol(isUnknown, Decl(unknownType2.ts, 0, 0)) +>u : Symbol(u, Decl(unknownType2.ts, 6, 5)) +} + diff --git a/tests/baselines/reference/unknownType2.types b/tests/baselines/reference/unknownType2.types new file mode 100644 index 0000000000..41743b0c49 --- /dev/null +++ b/tests/baselines/reference/unknownType2.types @@ -0,0 +1,148 @@ +=== tests/cases/conformance/types/unknown/unknownType2.ts === +type isUnknown = unknown extends T ? true : false; +>isUnknown : isUnknown +>true : true +>false : false + +type isTrue = T; +>isTrue : T +>true : true + +type SomeResponse = 'yes' | 'no' | 'idk'; +>SomeResponse : SomeResponse + +let validate: (x: unknown) => SomeResponse = x => (x === 'yes' || x === 'no') ? x : 'idk'; // No error +>validate : (x: unknown) => SomeResponse +>x : unknown +>x => (x === 'yes' || x === 'no') ? x : 'idk' : (x: unknown) => "yes" | "no" | "idk" +>x : unknown +>(x === 'yes' || x === 'no') ? x : 'idk' : SomeResponse +>(x === 'yes' || x === 'no') : boolean +>x === 'yes' || x === 'no' : boolean +>x === 'yes' : boolean +>x : unknown +>'yes' : "yes" +>x === 'no' : boolean +>x : unknown +>'no' : "no" +>x : "yes" | "no" +>'idk' : "idk" + +const u: unknown = undefined; +>u : unknown +>undefined : undefined + +declare const symb: unique symbol; +>symb : unique symbol + +if (u === 5) { +>u === 5 : boolean +>u : unknown +>5 : 5 + + const y = u.toString(10); +>y : string +>u.toString(10) : string +>u.toString : (radix?: number | undefined) => string +>u : 5 +>toString : (radix?: number | undefined) => string +>10 : 10 +} + +if (u === true || u === false) { +>u === true || u === false : boolean +>u === true : boolean +>u : unknown +>true : true +>u === false : boolean +>u : unknown +>false : false + + const someBool: boolean = u; +>someBool : boolean +>u : boolean +} + +if (u === undefined) { +>u === undefined : boolean +>u : unknown +>undefined : undefined + + const undef: undefined = u; +>undef : undefined +>u : undefined +} + +if (u === null) { +>u === null : boolean +>u : unknown +>null : null + + const someNull: null = u; +>someNull : null +>null : null +>u : null +} + +if(u === symb) { +>u === symb : boolean +>u : unknown +>symb : unique symbol + + const symbolAlias: typeof symb = u; +>symbolAlias : unique symbol +>symb : unique symbol +>u : unique symbol +} + +if (!(u === 42)) { +>!(u === 42) : boolean +>(u === 42) : boolean +>u === 42 : boolean +>u : unknown +>42 : 42 + + u // u should still be `unknown` here +>u : unknown +} + +if (u !== 42) { +>u !== 42 : boolean +>u : unknown +>42 : 42 + + type A = isTrue> +>A : true +>u : unknown +} + +if (u == 42) { +>u == 42 : boolean +>u : unknown +>42 : 42 + + type B = isTrue> +>B : true +>u : unknown +} + +if (u == true) { +>u == true : boolean +>u : unknown +>true : true + + type C = isTrue> +>C : true +>u : unknown +} + +if (u == Object) { +>u == Object : boolean +>u : unknown +>Object : ObjectConstructor + + type D = isTrue> +>D : true +>u : unknown +} + diff --git a/tests/cases/conformance/types/unknown/unknownType2.ts b/tests/cases/conformance/types/unknown/unknownType2.ts new file mode 100644 index 0000000000..b644700044 --- /dev/null +++ b/tests/cases/conformance/types/unknown/unknownType2.ts @@ -0,0 +1,51 @@ +// @strict: true + +type isUnknown = unknown extends T ? true : false; +type isTrue = T; + +type SomeResponse = 'yes' | 'no' | 'idk'; +let validate: (x: unknown) => SomeResponse = x => (x === 'yes' || x === 'no') ? x : 'idk'; // No error + +const u: unknown = undefined; + +declare const symb: unique symbol; + +if (u === 5) { + const y = u.toString(10); +} + +if (u === true || u === false) { + const someBool: boolean = u; +} + +if (u === undefined) { + const undef: undefined = u; +} + +if (u === null) { + const someNull: null = u; +} + +if(u === symb) { + const symbolAlias: typeof symb = u; +} + +if (!(u === 42)) { + u // u should still be `unknown` here +} + +if (u !== 42) { + type A = isTrue> +} + +if (u == 42) { + type B = isTrue> +} + +if (u == true) { + type C = isTrue> +} + +if (u == Object) { + type D = isTrue> +}