Skip to content

Commit 1c20aa0

Browse files
jack-williamsRyanCavanaugh
authored andcommitted
Narrow unknown under inequality when assumed false (#33488)
1 parent 344dba8 commit 1c20aa0

File tree

6 files changed

+361
-1
lines changed

6 files changed

+361
-1
lines changed

src/compiler/checker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17453,7 +17453,7 @@ namespace ts {
1745317453
assumeTrue = !assumeTrue;
1745417454
}
1745517455
const valueType = getTypeOfExpression(value);
17456-
if ((type.flags & TypeFlags.Unknown) && (operator === SyntaxKind.EqualsEqualsEqualsToken) && assumeTrue) {
17456+
if ((type.flags & TypeFlags.Unknown) && assumeTrue && (operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken)) {
1745717457
if (valueType.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive)) {
1745817458
return valueType;
1745917459
}

tests/baselines/reference/unknownType2.errors.txt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,4 +228,41 @@ tests/cases/conformance/types/unknown/unknownType2.ts(216,13): error TS2322: Typ
228228
// Arguably this should be never.
229229
type End = isTrue<isUnknown<typeof x>>
230230
}
231+
232+
// Repro from #33483
233+
234+
function f2(x: unknown): string | undefined {
235+
if (x !== undefined && typeof x !== 'string') {
236+
throw new Error();
237+
}
238+
return x;
239+
}
240+
241+
function notNotEquals(u: unknown) {
242+
if (u !== NumberEnum) { }
243+
else {
244+
const o: object = u;
245+
}
246+
247+
if (u !== NumberEnum.A) { }
248+
else {
249+
const a: NumberEnum.A = u;
250+
}
251+
252+
253+
if (u !== NumberEnum.A && u !== NumberEnum.B && u !== StringEnum.A) { }
254+
else {
255+
const aOrB: NumberEnum.A | NumberEnum.B | StringEnum.A = u;
256+
}
257+
258+
// equivalent to
259+
if (!(u === NumberEnum.A || u === NumberEnum.B || u === StringEnum.A)) { }
260+
else {
261+
const aOrB: NumberEnum.A | NumberEnum.B | StringEnum.A = u;
262+
}
263+
}
264+
265+
266+
267+
231268

tests/baselines/reference/unknownType2.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,43 @@ function switchResponseWrong(x: unknown): SomeResponse {
221221
// Arguably this should be never.
222222
type End = isTrue<isUnknown<typeof x>>
223223
}
224+
225+
// Repro from #33483
226+
227+
function f2(x: unknown): string | undefined {
228+
if (x !== undefined && typeof x !== 'string') {
229+
throw new Error();
230+
}
231+
return x;
232+
}
233+
234+
function notNotEquals(u: unknown) {
235+
if (u !== NumberEnum) { }
236+
else {
237+
const o: object = u;
238+
}
239+
240+
if (u !== NumberEnum.A) { }
241+
else {
242+
const a: NumberEnum.A = u;
243+
}
244+
245+
246+
if (u !== NumberEnum.A && u !== NumberEnum.B && u !== StringEnum.A) { }
247+
else {
248+
const aOrB: NumberEnum.A | NumberEnum.B | StringEnum.A = u;
249+
}
250+
251+
// equivalent to
252+
if (!(u === NumberEnum.A || u === NumberEnum.B || u === StringEnum.A)) { }
253+
else {
254+
const aOrB: NumberEnum.A | NumberEnum.B | StringEnum.A = u;
255+
}
256+
}
257+
258+
259+
260+
224261

225262

226263
//// [unknownType2.js]
@@ -388,3 +425,29 @@ function switchResponseWrong(x) {
388425
throw new Error('Can you repeat the question?');
389426
}
390427
}
428+
// Repro from #33483
429+
function f2(x) {
430+
if (x !== undefined && typeof x !== 'string') {
431+
throw new Error();
432+
}
433+
return x;
434+
}
435+
function notNotEquals(u) {
436+
if (u !== NumberEnum) { }
437+
else {
438+
var o = u;
439+
}
440+
if (u !== NumberEnum.A) { }
441+
else {
442+
var a = u;
443+
}
444+
if (u !== NumberEnum.A && u !== NumberEnum.B && u !== StringEnum.A) { }
445+
else {
446+
var aOrB = u;
447+
}
448+
// equivalent to
449+
if (!(u === NumberEnum.A || u === NumberEnum.B || u === StringEnum.A)) { }
450+
else {
451+
var aOrB = u;
452+
}
453+
}

tests/baselines/reference/unknownType2.symbols

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,3 +582,108 @@ function switchResponseWrong(x: unknown): SomeResponse {
582582
>x : Symbol(x, Decl(unknownType2.ts, 210, 29))
583583
}
584584

585+
// Repro from #33483
586+
587+
function f2(x: unknown): string | undefined {
588+
>f2 : Symbol(f2, Decl(unknownType2.ts, 221, 1))
589+
>x : Symbol(x, Decl(unknownType2.ts, 225, 12))
590+
591+
if (x !== undefined && typeof x !== 'string') {
592+
>x : Symbol(x, Decl(unknownType2.ts, 225, 12))
593+
>undefined : Symbol(undefined)
594+
>x : Symbol(x, Decl(unknownType2.ts, 225, 12))
595+
596+
throw new Error();
597+
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
598+
}
599+
return x;
600+
>x : Symbol(x, Decl(unknownType2.ts, 225, 12))
601+
}
602+
603+
function notNotEquals(u: unknown) {
604+
>notNotEquals : Symbol(notNotEquals, Decl(unknownType2.ts, 230, 1))
605+
>u : Symbol(u, Decl(unknownType2.ts, 232, 22))
606+
607+
if (u !== NumberEnum) { }
608+
>u : Symbol(u, Decl(unknownType2.ts, 232, 22))
609+
>NumberEnum : Symbol(NumberEnum, Decl(unknownType2.ts, 90, 1))
610+
611+
else {
612+
const o: object = u;
613+
>o : Symbol(o, Decl(unknownType2.ts, 235, 13))
614+
>u : Symbol(u, Decl(unknownType2.ts, 232, 22))
615+
}
616+
617+
if (u !== NumberEnum.A) { }
618+
>u : Symbol(u, Decl(unknownType2.ts, 232, 22))
619+
>NumberEnum.A : Symbol(NumberEnum.A, Decl(unknownType2.ts, 92, 17))
620+
>NumberEnum : Symbol(NumberEnum, Decl(unknownType2.ts, 90, 1))
621+
>A : Symbol(NumberEnum.A, Decl(unknownType2.ts, 92, 17))
622+
623+
else {
624+
const a: NumberEnum.A = u;
625+
>a : Symbol(a, Decl(unknownType2.ts, 240, 13))
626+
>NumberEnum : Symbol(NumberEnum, Decl(unknownType2.ts, 90, 1))
627+
>A : Symbol(NumberEnum.A, Decl(unknownType2.ts, 92, 17))
628+
>u : Symbol(u, Decl(unknownType2.ts, 232, 22))
629+
}
630+
631+
632+
if (u !== NumberEnum.A && u !== NumberEnum.B && u !== StringEnum.A) { }
633+
>u : Symbol(u, Decl(unknownType2.ts, 232, 22))
634+
>NumberEnum.A : Symbol(NumberEnum.A, Decl(unknownType2.ts, 92, 17))
635+
>NumberEnum : Symbol(NumberEnum, Decl(unknownType2.ts, 90, 1))
636+
>A : Symbol(NumberEnum.A, Decl(unknownType2.ts, 92, 17))
637+
>u : Symbol(u, Decl(unknownType2.ts, 232, 22))
638+
>NumberEnum.B : Symbol(NumberEnum.B, Decl(unknownType2.ts, 93, 6))
639+
>NumberEnum : Symbol(NumberEnum, Decl(unknownType2.ts, 90, 1))
640+
>B : Symbol(NumberEnum.B, Decl(unknownType2.ts, 93, 6))
641+
>u : Symbol(u, Decl(unknownType2.ts, 232, 22))
642+
>StringEnum.A : Symbol(StringEnum.A, Decl(unknownType2.ts, 98, 17))
643+
>StringEnum : Symbol(StringEnum, Decl(unknownType2.ts, 96, 1))
644+
>A : Symbol(StringEnum.A, Decl(unknownType2.ts, 98, 17))
645+
646+
else {
647+
const aOrB: NumberEnum.A | NumberEnum.B | StringEnum.A = u;
648+
>aOrB : Symbol(aOrB, Decl(unknownType2.ts, 246, 13))
649+
>NumberEnum : Symbol(NumberEnum, Decl(unknownType2.ts, 90, 1))
650+
>A : Symbol(NumberEnum.A, Decl(unknownType2.ts, 92, 17))
651+
>NumberEnum : Symbol(NumberEnum, Decl(unknownType2.ts, 90, 1))
652+
>B : Symbol(NumberEnum.B, Decl(unknownType2.ts, 93, 6))
653+
>StringEnum : Symbol(StringEnum, Decl(unknownType2.ts, 96, 1))
654+
>A : Symbol(StringEnum.A, Decl(unknownType2.ts, 98, 17))
655+
>u : Symbol(u, Decl(unknownType2.ts, 232, 22))
656+
}
657+
658+
// equivalent to
659+
if (!(u === NumberEnum.A || u === NumberEnum.B || u === StringEnum.A)) { }
660+
>u : Symbol(u, Decl(unknownType2.ts, 232, 22))
661+
>NumberEnum.A : Symbol(NumberEnum.A, Decl(unknownType2.ts, 92, 17))
662+
>NumberEnum : Symbol(NumberEnum, Decl(unknownType2.ts, 90, 1))
663+
>A : Symbol(NumberEnum.A, Decl(unknownType2.ts, 92, 17))
664+
>u : Symbol(u, Decl(unknownType2.ts, 232, 22))
665+
>NumberEnum.B : Symbol(NumberEnum.B, Decl(unknownType2.ts, 93, 6))
666+
>NumberEnum : Symbol(NumberEnum, Decl(unknownType2.ts, 90, 1))
667+
>B : Symbol(NumberEnum.B, Decl(unknownType2.ts, 93, 6))
668+
>u : Symbol(u, Decl(unknownType2.ts, 232, 22))
669+
>StringEnum.A : Symbol(StringEnum.A, Decl(unknownType2.ts, 98, 17))
670+
>StringEnum : Symbol(StringEnum, Decl(unknownType2.ts, 96, 1))
671+
>A : Symbol(StringEnum.A, Decl(unknownType2.ts, 98, 17))
672+
673+
else {
674+
const aOrB: NumberEnum.A | NumberEnum.B | StringEnum.A = u;
675+
>aOrB : Symbol(aOrB, Decl(unknownType2.ts, 252, 13))
676+
>NumberEnum : Symbol(NumberEnum, Decl(unknownType2.ts, 90, 1))
677+
>A : Symbol(NumberEnum.A, Decl(unknownType2.ts, 92, 17))
678+
>NumberEnum : Symbol(NumberEnum, Decl(unknownType2.ts, 90, 1))
679+
>B : Symbol(NumberEnum.B, Decl(unknownType2.ts, 93, 6))
680+
>StringEnum : Symbol(StringEnum, Decl(unknownType2.ts, 96, 1))
681+
>A : Symbol(StringEnum.A, Decl(unknownType2.ts, 98, 17))
682+
>u : Symbol(u, Decl(unknownType2.ts, 232, 22))
683+
}
684+
}
685+
686+
687+
688+
689+

tests/baselines/reference/unknownType2.types

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,3 +627,121 @@ function switchResponseWrong(x: unknown): SomeResponse {
627627
>x : unknown
628628
}
629629

630+
// Repro from #33483
631+
632+
function f2(x: unknown): string | undefined {
633+
>f2 : (x: unknown) => string | undefined
634+
>x : unknown
635+
636+
if (x !== undefined && typeof x !== 'string') {
637+
>x !== undefined && typeof x !== 'string' : boolean
638+
>x !== undefined : boolean
639+
>x : unknown
640+
>undefined : undefined
641+
>typeof x !== 'string' : boolean
642+
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
643+
>x : unknown
644+
>'string' : "string"
645+
646+
throw new Error();
647+
>new Error() : Error
648+
>Error : ErrorConstructor
649+
}
650+
return x;
651+
>x : string | undefined
652+
}
653+
654+
function notNotEquals(u: unknown) {
655+
>notNotEquals : (u: unknown) => void
656+
>u : unknown
657+
658+
if (u !== NumberEnum) { }
659+
>u !== NumberEnum : boolean
660+
>u : unknown
661+
>NumberEnum : typeof NumberEnum
662+
663+
else {
664+
const o: object = u;
665+
>o : object
666+
>u : object
667+
}
668+
669+
if (u !== NumberEnum.A) { }
670+
>u !== NumberEnum.A : boolean
671+
>u : unknown
672+
>NumberEnum.A : NumberEnum.A
673+
>NumberEnum : typeof NumberEnum
674+
>A : NumberEnum.A
675+
676+
else {
677+
const a: NumberEnum.A = u;
678+
>a : NumberEnum.A
679+
>NumberEnum : any
680+
>u : NumberEnum.A
681+
}
682+
683+
684+
if (u !== NumberEnum.A && u !== NumberEnum.B && u !== StringEnum.A) { }
685+
>u !== NumberEnum.A && u !== NumberEnum.B && u !== StringEnum.A : boolean
686+
>u !== NumberEnum.A && u !== NumberEnum.B : boolean
687+
>u !== NumberEnum.A : boolean
688+
>u : unknown
689+
>NumberEnum.A : NumberEnum.A
690+
>NumberEnum : typeof NumberEnum
691+
>A : NumberEnum.A
692+
>u !== NumberEnum.B : boolean
693+
>u : unknown
694+
>NumberEnum.B : NumberEnum.B
695+
>NumberEnum : typeof NumberEnum
696+
>B : NumberEnum.B
697+
>u !== StringEnum.A : boolean
698+
>u : unknown
699+
>StringEnum.A : StringEnum.A
700+
>StringEnum : typeof StringEnum
701+
>A : StringEnum.A
702+
703+
else {
704+
const aOrB: NumberEnum.A | NumberEnum.B | StringEnum.A = u;
705+
>aOrB : NumberEnum.A | NumberEnum.B | StringEnum.A
706+
>NumberEnum : any
707+
>NumberEnum : any
708+
>StringEnum : any
709+
>u : NumberEnum.A | NumberEnum.B | StringEnum.A
710+
}
711+
712+
// equivalent to
713+
if (!(u === NumberEnum.A || u === NumberEnum.B || u === StringEnum.A)) { }
714+
>!(u === NumberEnum.A || u === NumberEnum.B || u === StringEnum.A) : boolean
715+
>(u === NumberEnum.A || u === NumberEnum.B || u === StringEnum.A) : boolean
716+
>u === NumberEnum.A || u === NumberEnum.B || u === StringEnum.A : boolean
717+
>u === NumberEnum.A || u === NumberEnum.B : boolean
718+
>u === NumberEnum.A : boolean
719+
>u : unknown
720+
>NumberEnum.A : NumberEnum.A
721+
>NumberEnum : typeof NumberEnum
722+
>A : NumberEnum.A
723+
>u === NumberEnum.B : boolean
724+
>u : unknown
725+
>NumberEnum.B : NumberEnum.B
726+
>NumberEnum : typeof NumberEnum
727+
>B : NumberEnum.B
728+
>u === StringEnum.A : boolean
729+
>u : unknown
730+
>StringEnum.A : StringEnum.A
731+
>StringEnum : typeof StringEnum
732+
>A : StringEnum.A
733+
734+
else {
735+
const aOrB: NumberEnum.A | NumberEnum.B | StringEnum.A = u;
736+
>aOrB : NumberEnum.A | NumberEnum.B | StringEnum.A
737+
>NumberEnum : any
738+
>NumberEnum : any
739+
>StringEnum : any
740+
>u : NumberEnum.A | NumberEnum.B | StringEnum.A
741+
}
742+
}
743+
744+
745+
746+
747+

tests/cases/conformance/types/unknown/unknownType2.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,3 +222,40 @@ function switchResponseWrong(x: unknown): SomeResponse {
222222
// Arguably this should be never.
223223
type End = isTrue<isUnknown<typeof x>>
224224
}
225+
226+
// Repro from #33483
227+
228+
function f2(x: unknown): string | undefined {
229+
if (x !== undefined && typeof x !== 'string') {
230+
throw new Error();
231+
}
232+
return x;
233+
}
234+
235+
function notNotEquals(u: unknown) {
236+
if (u !== NumberEnum) { }
237+
else {
238+
const o: object = u;
239+
}
240+
241+
if (u !== NumberEnum.A) { }
242+
else {
243+
const a: NumberEnum.A = u;
244+
}
245+
246+
247+
if (u !== NumberEnum.A && u !== NumberEnum.B && u !== StringEnum.A) { }
248+
else {
249+
const aOrB: NumberEnum.A | NumberEnum.B | StringEnum.A = u;
250+
}
251+
252+
// equivalent to
253+
if (!(u === NumberEnum.A || u === NumberEnum.B || u === StringEnum.A)) { }
254+
else {
255+
const aOrB: NumberEnum.A | NumberEnum.B | StringEnum.A = u;
256+
}
257+
}
258+
259+
260+
261+

0 commit comments

Comments
 (0)