0

I would like to extract a subtype from another type, but I don't know the most optimal/easiest way to do so.

I have many well-written (at least I think so) types in my project which could be generalized as:

interface Y {
  readonly y: string
  readonly type: 'y'
}

interface Z {
  readonly z: number
  readonly type: 'z'
}

interface X {
  readonly prop: Y | Z
}

So in current shape it's simply not possible for me to extract only a subtype assignable to "Z", eg.

// the type evaluates to "never", I would like it to evaluate to "{ prop: Z }" instead
type ZfromX = Extract<X, {readonly prop: { readonly type: 'z' }}>

I know one of the approaches is to make the type to be a union instead of being a type consisting of nested unions, eg.

type X = 
| {
  readonly prop: Y
}
| {
  readonly prop: Z
}

// now it works, the extracted type is "{ prop: Z }" as expected
type ZfromX = Extract<X, {readonly prop: { readonly type: 'z' }}>

But how to do so without rewriting every type which I have in my codebase into a union by hand? Maybe there is a way to transform each "branch" of my type into some kind of union of types made of cartesian product of branches (I could write that on my own, but maybe I'm not aware of an existing solution for that)?

roomcayz
  • 2,334
  • 4
  • 17
  • 26
  • 1
    I'm having a hard time understanding your requirements in terms of operations on the two types. You want something like `DeepExtract` which returns `T` if `T extends U`, but otherwise you want to start drilling down into properties of `T` and `U` and doing `DeepExtract` on them, but I don't quite see what operation is intended. I'd like to see a bunch of input/output examples that are more complicated than what you've shown here. – jcalz Jun 29 '22 at 14:11
  • 1
    And oof, `readonly` is just kind of wordy without much benefit to the question, given that `readonly` doesn't affect assignability... so `{a: string}` and `{readonly a: string}` are mutually assignable. Could you remove `readonly` from the question unless it has some kind of impact I'm not seeing? – jcalz Jun 29 '22 at 14:11
  • 1
    "I could write that on my own"... meaning the nested distribution of unions so that `{a: 0 | {b: 1 | 2}, c: 3 | 4}` becomes `{a: 0, c: 3} | {a: 0, c: 4} | {a: {b: 1}, c: 3} | {a: {b: 1}, c: 4} | {a: {b: 2}, c: 3} | {a: {b: 2}, c: 4}`? I tried this, which kind of works (see [this code](https://tsplay.dev/mLqrkW)), but it involves some serious [black magic](https://stackoverflow.com/a/72189271/2887218) and I don't know if it's worth it. Such multiply recursive types have the potential to be very taxing on the compiler. – jcalz Jun 29 '22 at 23:46
  • 1
    I could maybe write the above up as a potential answer, but I'd rather see if there's something a little less crazy-and-general you actually want. – jcalz Jun 29 '22 at 23:47
  • @jcalz I'm sorry for this suuuper delayed answer: yes, the nested distribution of unions is exactly what I need - don't hesitate to add it as an answer and I'll accept it, and please don't mind me using `readonly` because that's how I write my code – roomcayz Nov 17 '22 at 17:52

0 Answers0