I've got the following TS code
type Fruit = { kind: "apple" } | { kind: "grape"; color: "green" | "black" };
type FruitTaste<TFruit extends Fruit> = TFruit["kind"] extends "apple"
? "good"
: TFruit["color"] extends "green"
? "good"
: "bad";
It errors at TFruit["color"]
with
Type '"color"' cannot be used to index type 'TFruit'.
but it shouldn't, because we're in the ternary side where TFruit
should only be restricted to { kind: "grape"; color: "green" | "black" }
, and the color
key should exist.
Weirdly enough TS doesn't have to have any issue with the "runtime" version of it:
type Fruit = { kind: "apple" } | { kind: "grape"; color: "green" | "black" };
const fruitTaste = (fruit: Fruit) =>
fruit.kind === "apple" ? "good" : fruit.color === "green" ? "good" : "bad";
Why is that? How can I implement the FruitTaste
type?
CodePudding user response:
I think you can implement it by seperating the type { kind: "apple" }
and { kind: "grape"; color: "green" | "black" }
.
type Apple = { kind: "apple" };
type Grape = { kind: "grape"; color: "green" | "black" };
type Fruit = Apple | Grape;
type FruitTaste<TFruit extends Fruit> = TFruit extends Grape
? TFruit["color"] extends "green"
? "good"
: "bad"
: "good";
CodePudding user response:
To the "why" question: TypeScript does not discriminante unions in generic conditionals based on specific properties. TFruit
can not be indexed because at the point of this check, TFruit
is still a union with two elements of which one does not have a color
property.
My workaround would look like this:
type FruitTaste<TFruit extends Fruit> = TFruit extends { kind: "apple" }
? "good"
: (TFruit & { color: string })["color"] extends "green"
? "good"
: "bad";
Before accessing color
, we can use an intersection to remind TypeScript that this property should exist.