Description
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.