typescript-go icon indicating copy to clipboard operation
typescript-go copied to clipboard

`UnionToTuple` type returns "inverted" tuple in TSGO compared to TypeScript 5.8.3 and gives type error

Open alexwork1611 opened this issue 7 months ago • 7 comments

Hello,

This will need a little investigation (learning the type-fest package types), but I tried running TSGO in my current project and got some errors. Basically, type-fest has a UnionToTuple type and TSGO inverts the tuple values and gives an error. TSGO successfully extracts the union values and makes them into a tuple, but reversed compared to TypeScript 5.8.3.

Testable repo: https://github.com/alexwork1611/tsgo-union-to-tuple.

Hope it helps.

Thanks!

alexwork1611 avatar Apr 10 '25 06:04 alexwork1611

Union ordering is undefined, determined only by the implementation details of the checker. The Go code isn't the same as the previous compiler (in order to handle concurrency) and uses a different but deterministic sort order, intentionally. Anything like "union to tuple" is going to behave differently.

jakebailey avatar Apr 10 '25 06:04 jakebailey

Union ordering is undefined, determined only by the implementation details of the checker. The Go code isn't the same as the previous compiler (in order to handle concurrency) and uses a different but deterministic sort order, intentionally. Anything like "union to tuple" is going to behave differently.

Thanks for your answer. Shouldn't this be something that's standardized, though?

alexwork1611 avatar Apr 10 '25 16:04 alexwork1611

What does "this" refer to, and what do you mean by "standardized"?

jakebailey avatar Apr 10 '25 16:04 jakebailey

Good clarifying question. "This" refers to the way unions are sorted. "Standardized" refers to the idea that the two compilers should be intentionally designed to have matching union element sorting.

alexwork1611 avatar Apr 10 '25 16:04 alexwork1611

They can't be; the old method was dependent on the order of files and the order that the checker walks everything. There was no "design", per se.

In the new compiler, we check files concurrently, which means we are going to be doing everything in entirely different orders, have errors that change each time the code is checked, etc, if we aren't careful. Hence, we have more strictly defined ordering to not be in terms of type IDs.

The ordering calculation is not free; it's not good for perf in the old codebase: https://github.com/microsoft/TypeScript/pull/61399

jakebailey avatar Apr 10 '25 16:04 jakebailey

I'll note that it's always been dubious to depend on union order for anything; simple changes like swapping the order of two declarations in the same file, sorting your imports, etc, could all affect the behavior, which isn't great.

jakebailey avatar Apr 10 '25 16:04 jakebailey

We tried our best to tell people not to do this, but some folks are very determined to do dangerous things.

See also

https://github.com/microsoft/TypeScript/issues/13298#issuecomment-468481350

https://github.com/microsoft/TypeScript/issues/13298#issuecomment-493583406

RyanCavanaugh avatar Apr 10 '25 17:04 RyanCavanaugh

Thank you so much, @RyanCavanaugh and @jakebailey. I refactored my code and I am not using the UnionToTuple type anymore.

The reason why I went for that is because I wanted to enforce the my types into my runtime, but I was able to come up with an alternative. Here is the before and after (for one of the places that were giving me errors because of the tuple elements order):

Before:

Image

After:

Image

Here are the UserRole definitions:

Image

I hope the images above provide insight into my thinking and I hope you can empathize with me not knowing the things discussed in the awesome PR mentioned by @jakebailey and @RyanCavanaugh's reply above.

Thanks again!

alexwork1611 avatar Apr 11 '25 09:04 alexwork1611

For what it's worth the reason why it was never a good idea is to look at a snippet of code like this:

import { UnionToTuple } from "type-fest";

// Try commenting and uncommenting this line:
declare const b: "b";

type Test = UnionToTuple<"a" | "b">;

When it's uncommented you'll get ["b", "a"]. When it's commented you'll get ["a", "b"]. Imagine just happening to add "Guest" anywhere else in your code (maybe even in a random dependency!) and suddenly it jumps to the front. It might not even be "consistent" because in editor it'll walk files differently than with a full rebuild.

LukeAbby avatar May 01 '25 07:05 LukeAbby