Skip to content

Commit

Permalink
Use bidirectional comparability in narrowing (#50592)
Browse files Browse the repository at this point in the history
* Use bidirectional comparability (aka comparability) in narrowing

* Rename test, check other CFA branch, test without strictNullChecks
  • Loading branch information
andrewbranch committed Sep 1, 2022
1 parent 891cdc5 commit 5df09a5
Show file tree
Hide file tree
Showing 8 changed files with 207 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/compiler/checker.ts
Expand Up @@ -25096,7 +25096,7 @@ namespace ts {
const narrowedPropType = narrowType(propType);
return filterType(type, t => {
const discriminantType = getTypeOfPropertyOrIndexSignature(t, propName);
return !(narrowedPropType.flags & TypeFlags.Never) && isTypeComparableTo(narrowedPropType, discriminantType);
return !(narrowedPropType.flags & TypeFlags.Never) && areTypesComparable(narrowedPropType, discriminantType);
});
}

Expand Down
@@ -0,0 +1,23 @@
//// [undefinedAsDiscriminantWithUnknown.ts]
type S =
| { type: 'string', value: string }
| { type: 'number', value: number }
| { type: 'unknown', value: unknown }
| { value: undefined };

declare var s: S

if (s.value !== undefined) {
s;
}
else {
s;
}

//// [undefinedAsDiscriminantWithUnknown.js]
if (s.value !== undefined) {
s;
}
else {
s;
}
@@ -0,0 +1,36 @@
=== tests/cases/compiler/undefinedAsDiscriminantWithUnknown.ts ===
type S =
>S : Symbol(S, Decl(undefinedAsDiscriminantWithUnknown.ts, 0, 0))

| { type: 'string', value: string }
>type : Symbol(type, Decl(undefinedAsDiscriminantWithUnknown.ts, 1, 3))
>value : Symbol(value, Decl(undefinedAsDiscriminantWithUnknown.ts, 1, 19))

| { type: 'number', value: number }
>type : Symbol(type, Decl(undefinedAsDiscriminantWithUnknown.ts, 2, 3))
>value : Symbol(value, Decl(undefinedAsDiscriminantWithUnknown.ts, 2, 19))

| { type: 'unknown', value: unknown }
>type : Symbol(type, Decl(undefinedAsDiscriminantWithUnknown.ts, 3, 3))
>value : Symbol(value, Decl(undefinedAsDiscriminantWithUnknown.ts, 3, 20))

| { value: undefined };
>value : Symbol(value, Decl(undefinedAsDiscriminantWithUnknown.ts, 4, 3))

declare var s: S
>s : Symbol(s, Decl(undefinedAsDiscriminantWithUnknown.ts, 6, 11))
>S : Symbol(S, Decl(undefinedAsDiscriminantWithUnknown.ts, 0, 0))

if (s.value !== undefined) {
>s.value : Symbol(value, Decl(undefinedAsDiscriminantWithUnknown.ts, 1, 19), Decl(undefinedAsDiscriminantWithUnknown.ts, 2, 19), Decl(undefinedAsDiscriminantWithUnknown.ts, 3, 20), Decl(undefinedAsDiscriminantWithUnknown.ts, 4, 3))
>s : Symbol(s, Decl(undefinedAsDiscriminantWithUnknown.ts, 6, 11))
>value : Symbol(value, Decl(undefinedAsDiscriminantWithUnknown.ts, 1, 19), Decl(undefinedAsDiscriminantWithUnknown.ts, 2, 19), Decl(undefinedAsDiscriminantWithUnknown.ts, 3, 20), Decl(undefinedAsDiscriminantWithUnknown.ts, 4, 3))
>undefined : Symbol(undefined)

s;
>s : Symbol(s, Decl(undefinedAsDiscriminantWithUnknown.ts, 6, 11))
}
else {
s;
>s : Symbol(s, Decl(undefinedAsDiscriminantWithUnknown.ts, 6, 11))
}
@@ -0,0 +1,36 @@
=== tests/cases/compiler/undefinedAsDiscriminantWithUnknown.ts ===
type S =
>S : { type: 'string'; value: string; } | { type: 'number'; value: number; } | { type: 'unknown'; value: unknown; } | { value: undefined; }

| { type: 'string', value: string }
>type : "string"
>value : string

| { type: 'number', value: number }
>type : "number"
>value : number

| { type: 'unknown', value: unknown }
>type : "unknown"
>value : unknown

| { value: undefined };
>value : undefined

declare var s: S
>s : S

if (s.value !== undefined) {
>s.value !== undefined : boolean
>s.value : unknown
>s : S
>value : unknown
>undefined : undefined

s;
>s : S
}
else {
s;
>s : S
}
@@ -0,0 +1,23 @@
//// [undefinedAsDiscriminantWithUnknown.ts]
type S =
| { type: 'string', value: string }
| { type: 'number', value: number }
| { type: 'unknown', value: unknown }
| { value: undefined };

declare var s: S

if (s.value !== undefined) {
s;
}
else {
s;
}

//// [undefinedAsDiscriminantWithUnknown.js]
if (s.value !== undefined) {
s;
}
else {
s;
}
@@ -0,0 +1,36 @@
=== tests/cases/compiler/undefinedAsDiscriminantWithUnknown.ts ===
type S =
>S : Symbol(S, Decl(undefinedAsDiscriminantWithUnknown.ts, 0, 0))

| { type: 'string', value: string }
>type : Symbol(type, Decl(undefinedAsDiscriminantWithUnknown.ts, 1, 3))
>value : Symbol(value, Decl(undefinedAsDiscriminantWithUnknown.ts, 1, 19))

| { type: 'number', value: number }
>type : Symbol(type, Decl(undefinedAsDiscriminantWithUnknown.ts, 2, 3))
>value : Symbol(value, Decl(undefinedAsDiscriminantWithUnknown.ts, 2, 19))

| { type: 'unknown', value: unknown }
>type : Symbol(type, Decl(undefinedAsDiscriminantWithUnknown.ts, 3, 3))
>value : Symbol(value, Decl(undefinedAsDiscriminantWithUnknown.ts, 3, 20))

| { value: undefined };
>value : Symbol(value, Decl(undefinedAsDiscriminantWithUnknown.ts, 4, 3))

declare var s: S
>s : Symbol(s, Decl(undefinedAsDiscriminantWithUnknown.ts, 6, 11))
>S : Symbol(S, Decl(undefinedAsDiscriminantWithUnknown.ts, 0, 0))

if (s.value !== undefined) {
>s.value : Symbol(value, Decl(undefinedAsDiscriminantWithUnknown.ts, 1, 19), Decl(undefinedAsDiscriminantWithUnknown.ts, 2, 19), Decl(undefinedAsDiscriminantWithUnknown.ts, 3, 20), Decl(undefinedAsDiscriminantWithUnknown.ts, 4, 3))
>s : Symbol(s, Decl(undefinedAsDiscriminantWithUnknown.ts, 6, 11))
>value : Symbol(value, Decl(undefinedAsDiscriminantWithUnknown.ts, 1, 19), Decl(undefinedAsDiscriminantWithUnknown.ts, 2, 19), Decl(undefinedAsDiscriminantWithUnknown.ts, 3, 20), Decl(undefinedAsDiscriminantWithUnknown.ts, 4, 3))
>undefined : Symbol(undefined)

s;
>s : Symbol(s, Decl(undefinedAsDiscriminantWithUnknown.ts, 6, 11))
}
else {
s;
>s : Symbol(s, Decl(undefinedAsDiscriminantWithUnknown.ts, 6, 11))
}
@@ -0,0 +1,36 @@
=== tests/cases/compiler/undefinedAsDiscriminantWithUnknown.ts ===
type S =
>S : { type: 'string'; value: string; } | { type: 'number'; value: number; } | { type: 'unknown'; value: unknown; } | { value: undefined; }

| { type: 'string', value: string }
>type : "string"
>value : string

| { type: 'number', value: number }
>type : "number"
>value : number

| { type: 'unknown', value: unknown }
>type : "unknown"
>value : unknown

| { value: undefined };
>value : undefined

declare var s: S
>s : S

if (s.value !== undefined) {
>s.value !== undefined : boolean
>s.value : unknown
>s : S
>value : unknown
>undefined : undefined

s;
>s : { type: "string"; value: string; } | { type: "number"; value: number; } | { type: "unknown"; value: unknown; }
}
else {
s;
>s : { type: "unknown"; value: unknown; } | { value: undefined; }
}
16 changes: 16 additions & 0 deletions tests/cases/compiler/undefinedAsDiscriminantWithUnknown.ts
@@ -0,0 +1,16 @@
// @strictNullChecks: true,false

type S =
| { type: 'string', value: string }
| { type: 'number', value: number }
| { type: 'unknown', value: unknown }
| { value: undefined };

declare var s: S

if (s.value !== undefined) {
s;
}
else {
s;
}

0 comments on commit 5df09a5

Please sign in to comment.