0

I'm trying to use this answer to exclude functions from when I call the set method with essentially the fields of the class, but I get the error Type instantiation is excessively deep and possibly infinite. I saw this question, but I can't use // @ts-ignore because I'll lose my type safety, and the accepted answer modifies it in such a way that I don't know how to apply in my example. I also saw this question, but once again, it's a little confusing how to apply here, probably due to my lack of understanding TS recursive generics. How do I recursively retrieve the field properties (while excluding functions and respecting if the fields are optional or not) and not get the excessively deep error?

Note: there might be some additional issues in the linked code, so I'm wishing for a seasoned TS expert to fix them.

Fiddle and code:

// types taken from https://stackoverflow.com/a/62362197/1253609
type IfEquals<X, Y, A=X, B=never> =
  (<T>() => T extends X ? 1 : 2) extends
  (<T>() => T extends Y ? 1 : 2) ? A : B;

type WritableKeys<T> = {
  [P in keyof T]: T[P] extends Function ? never : IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, P>
}[keyof T];

type DeepWritablePrimitive = undefined | null | boolean | string | number | Function;
type DeepWritable<T> =
    T extends DeepWritablePrimitive ? T :
    T extends Array<infer U> ? DeepWritableArray<U> :
    T extends Map<infer K, infer V> ? DeepWritableMap<K, V> :
    T extends Set<infer T> ? DeepWriableSet<T> : DeepWritableObject<T>;

type DeepWritableArray<T> = Array<DeepWritable<T>>;
type DeepWritableMap<K, V> = Map<K, DeepWritable<V>>;
type DeepWriableSet<T> = Set<DeepWritable<T>>;

type DeepWritableObject<T> = {
    [K in WritableKeys<T>]: DeepWritable<T[K]>
};

class Base {
  set(data?: Partial<DeepWritable<typeof this>>) {
    Object.assign(this, data);
  }
}

class Parent extends Base {
  name?: string;
  arr?: Parent[];
};

const record = new Parent();
record.set({
  // https://github.com/microsoft/TypeScript/issues/34933
  arr: [{
    name: '0'
  }]
})
console.log(record.arr);
incutonez
  • 3,241
  • 9
  • 43
  • 92
  • 2
    I think you should have more of the details of your question in this actual question. There's little to go on here without reading your links. Can it be reproduced in a few lines of code that you could add to this question? – Wyck Jun 08 '23 at 12:34
  • Oh my goodness, I forgot to include the Playground link. My bad, thanks @Wyck. Added to description. – incutonez Jun 08 '23 at 12:37
  • I suspect that the issue is that `arr` is referencing the `Parent` and it causes infinite depth error. I would suggest limiting the depth if you know the maximum possible depth. Would that work for you? – wonderflame Jun 08 '23 at 13:09
  • Unfortunately, I don't know the max depth, but I'd imagine nothing over 50. Is there a way to just set a max depth value without much overhaul? – incutonez Jun 08 '23 at 13:11
  • 1
    will come back to you in a bit – wonderflame Jun 08 '23 at 13:12
  • 1
    Recursive utility types acting on recursive data types are notoriously tricky. Here it looks like `Map` is the culprit for some reason (determined via trial and error); if I "shield" that check as shown in [in this playground link](https://tsplay.dev/we85dW) it starts to work. Does that meet your needs or does it explode elsewhere? If it works for you I could write up an answer; otherwise what goes wrong? – jcalz Jun 08 '23 at 14:12
  • @jcalz unfortunately, that looks like it requires a value for `arr`, but it's an optional property. – incutonez Jun 08 '23 at 14:20
  • But that's your own code that does that. (`{[K in WritableKeys]: ...` will "de-optionalize" the keys) Looks out of scope for the question as asked; the fact that this was exposed would be some sort of followup issue. What should we do here? – jcalz Jun 08 '23 at 14:21
  • Good question. I've updated the requirements in the question... does that help make it more clear? – incutonez Jun 08 '23 at 14:27
  • So instead of asking for someone to resolve the recursion problem, you're now asking for someone to resolve all possible problems with that code, even ones you haven't identified? That's not really focused enough for me to engage with. If you decide to edit to a more targeted question I'll check again. Good luck! – jcalz Jun 08 '23 at 14:32
  • @jcalz yes and no, the original question title is what I wanted, I just posed the final question incorrectly. That's my fault! You can absolutely post your answer as a partial answer, and I'll gladly upvote it though :) – incutonez Jun 08 '23 at 14:33

1 Answers1

1

Problem with recursion seems to be caused by Map, checking for Map<any, any> first seems to fix it.
You have missed keys losing optionality

type DeepWritable1<T> =
  | T extends DeepWritablePrimitive ? T
  : T extends (infer U)[] ? DeepWritable1<U>[]
  : T extends Map<any, any> ? (
    T extends Map<infer K, infer V> ? Map<K, DeepWritable1<V>> : never
  )
  : T extends Set<infer V> ? Set<DeepWritable1<V>>
  : DeepWritableRecord1<T>;

type DeepWritableRecord1<T> = {
  // need to keep optionality
  [K in keyof Pick<T, WritableKeys<T>>]: DeepWritable1<T[K]>
}

https://tsplay.dev/WGdl0w

Dimava
  • 7,654
  • 1
  • 9
  • 24