#Expression produces a union type that is too complex to represent.

21 messages · Page 1 of 1 (latest)

left spire
#
[Q in `${Prefix}${string & K}`]?: XOR<
 [arraySize, elemMatch<U>, DeepPartial<U | U[]>, ComparisonOperators<U>]
>;

I have this XOR utility which lets you choose only of the given types and I got it generated through claude. When i pass 4 or more arguments to this utility type the typescript seems to give up and complains "Expression produces a union type that is too complex to represent." why is this behaving this way and how can i resolve this ?

type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };

export type XOR<T extends any[]> = Prettify<
  T extends [infer Only]
    ? Only
    : T extends [infer First, ...infer Rest]
    ?
        | (Without<First, XOR<Rest>> & XOR<Rest>)
        | (Without<XOR<Rest>, First> & First)
    : never
>;
visual oyster
#

i'm not getting that behavior, it's probably because your inputs are somewhat complex to begin with

#

but this kind of utility isn't unsafe, if you need to actually discriminate between branches, use a DU

cosmic craterBOT
#
that_guy977#0

Preview:ts ... const x = { a: "", b: "" } const y: { a: string } = x; const z: X = y; // ambiguous at runtime

long river
#

It's probably the eager evaluation that Prettify requires

#

The definitions of these are usually something like type Prettify<T> = T extends infer O ? { [K in keyof O]: O[K] } : never. Which btw means they usually have a bug when it comes to functions...

#

In contexts like this the infer O means that you're effectively creating a new type instantiation to newly evaluate it. This is what makes the Prettify type work, it reduces the laziness that goes on but it's also what's going to kill performance here

#

Each call of XOR recurses into 3 calls of XOR until the base case of 1 element.

Because Prettify is likely to defeat any type identify caching you're basically going to get at least:
n = 1 1 instantiation of XOR
n = 2 4 instantiations of XOR (remember there's 3 sub instantiations for n = 1 because you defeated caching)
n = 3 13 instantiations of XOR
n = 4 40 instantiations of XOR

the general formula being f(n) = {
1 if n = 1
1 + f(n - 1) * 3 if n > 1
}

Now 40 shouldn't actually be high enough to cause issues in of itself. So I'm guessing you have more distribution going on.

#

My short term advice would be to write:

export type XOR<T> = Prettify<_XOR<T>>;

type _XOR<T extends any[]> = T extends [infer Only]
  ? Only
  : T extends [infer First, ...infer Rest]
  ?
      | (Without<First, _XOR<Rest>> & _XOR<Rest>)
      | (Without<_XOR<Rest>, First> & First)
  : never;
#

Even if this doesn't help once you reach n = 4 (at which point I'd probably need traces and a full reproduction with the input) it'll still help performance so it's a good change to make regardless

visual oyster
long river
#

Depends on how deeply you want it

visual oyster
#

no, not really

long river
#

I'd appreciate if you did research instead of assuming. It may be unintuitive that there's a difference but there are tests in tsc itself for this

#

you can check by searching for type Evaluate in the TypeScript repository

#

The example in deeplyNestedMappedType is the primary one

#

but you can find the pattern elsewhere

#

I already gave an explanation above that extends infer creates instantiations in a different way than simply extends unknown. I will not be shut down by just a simple "no". There are many things in TypeScript that are not purely type theorematic and hover information is one of those.

#

you have to create a type complex enough for the difference to be relevant. Hence why I'm referring you to a test case.

left spire