Skip to content

Insufficient type narrowing of object with a union property #33467

@KevinWuWon

Description

@KevinWuWon

TypeScript Version: typescript@3.7.0-dev.20190917 (typescript@next)

Search Terms: union property type narrowing

Note: this is two bugs in one but they look related. "Issue 2" might actually be more damning.

Issue 1

Code

// Compile with `tsc --strictNullChecks` maybe1.ts
type Base = { bar: number };
type MaybeFoo = Base & { foo: string | null };
type HasFoo = MaybeFoo & { foo: string };

export function useMaybeFoo(maybeFoo: MaybeFoo) {
  if (maybeFoo.foo !== null) {
    const hasFoo: HasFoo = maybeFoo;
  }
}

Expected behavior:
Should compile because the conditional should have narrowed the MaybeFoo to a HasFoo.

Actual behavior:

maybe1.ts:7:11 - error TS2322: Type 'MaybeFoo' is not assignable to type 'HasFoo'.
  Type 'MaybeFoo' is not assignable to type '{ foo: string; }'.
    Types of property 'foo' are incompatible.
      Type 'string | null' is not assignable to type 'string'.
        Type 'null' is not assignable to type 'string'.

7     const hasFoo: HasFoo = maybeFoo;
            ~~~~~~

Playground Link: click

Related Issues: had a quick search but couldn't anything

Workaround

This code is exactly the same, but compiles

// Compile with `tsc --strictNullChecks` maybe2.ts
type Base = { bar: number };
type NoFoo = Base & { foo: null };
type HasFoo = Base & { foo: string };
type MaybeFoo = NoFoo | HasFoo;

export function useMaybeFoo(maybeFoo: MaybeFoo) {
  if (maybeFoo.foo !== null) {
    const hasFoo: HasFoo = maybeFoo;
  }
}

Playground Link click

Issue 2

Now let's try using the same type definitions as maybe2.ts, but try creating a MaybeFoo:

// Compile with `tsc --strictNullChecks` maybe3.ts
type Base = { bar: number };
type NoFoo = Base & { foo: null };
type HasFoo = Base & { foo: string };
type MaybeFoo = NoFoo | HasFoo;

export function makeMaybeFoo(foo: string | null): void {
  const maybeFoo: MaybeFoo = {foo: foo, bar: 3};
}

Expected behavior:
Should compile.

Actual behavior:

maybe3.ts:7:9 - error TS2322: Type '{ foo: string | null; bar: number; }' is not assignable to type 'MaybeFoo'.
  Type '{ foo: string | null; bar: number; }' is not assignable to type 'HasFoo'.
    Type '{ foo: string | null; bar: number; }' is not assignable to type '{ foo: string; }'.
      Types of property 'foo' are incompatible.
        Type 'string | null' is not assignable to type 'string'.
          Type 'null' is not assignable to type 'string'.

7   const maybeFoo: MaybeFoo = {foo: foo, bar: 3};

Playground Link click

Issue 2 Workaround

Changing back to the original type definitions fixes it:

// Compile with `tsc --strictNullChecks` maybe4.ts
type Base = { bar: number };
type MaybeFoo = Base & { foo: string | null };
type HasFoo = MaybeFoo & { foo: string };

export function makeMaybeFoo(foo: string | null): void {
  const maybeFoo: MaybeFoo = {foo: foo, bar: 3};
}

Playground Link: click

Metadata

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already created

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions