Skip to content

Narrowing unions of interfaces with intradependent property types #51341

Closed
@david-mancuso

Description

@david-mancuso

Bug Report

Type narrowing doesn't behave as I expect it would for a union of interfaces where one property type is mapped directly to another property type. If this is not a bug, I would love to understand how I can better type this use case because it appears valid to me.

🔎 Search Terms

enum, union, type, interface, property, never, error, validation, gating, narrowing, 4.7.4, strict

🕗 Version & Regression Information

Bug appears in v4.7.4 (used in my context) through latest next version today.

⏯ Playground Link

Playground link with relevant code

💻 Code

enum FooKeys {
  BAR = 'bar',
  BAZ = 'baz'
}

interface Bar {
  key: FooKeys.BAR;
  value: string;
}

interface Baz {
  key: FooKeys.BAZ;
  value: number;
}

type Foo = Bar | Baz;

interface FooBar {
  bar: Bar['value'],
  baz: Baz['value'],
  opts: Foo[]
}

class FooBar implements FooBar {
  bar = 'abc';
  baz = 123;
  opts: Foo[] = [{
    key: FooKeys.BAR,
    value: 'def'
  },
  {
    key: FooKeys.BAZ,
    value: 456
  }
  ];

  constructor(){ } 

  foobar(): void {
    this.opts.forEach(({ key, value }) => {
      // Throws error:
      // Type 'string | number' is not assignable to type 'never'.
      // Type 'string' is not assignable to type 'never'.
      this[key] = value;
    });
  }
}

🙁 Actual behavior

The Foo properties of value and key are not narrowed relative to each other despite being constituents of discrete types where they are directly related.

🙂 Expected behavior

I would expect that typing an object as Foo would enforce the relationship between the key and value properties. This is because the Foo union is not an any to any relationship of its constituent interfaces' properties. Each interface has a type for the key which maps directly to a type for the value. These relationships are enforced at the time of initializing opts: Foo[] but that same reasoning seems to not apply when narrowing and gating the Foo objects.

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