Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inconsistent type calculation results caused by caching mechanism #56813

Closed
NWYLZW opened this issue Dec 17, 2023 · 8 comments
Closed

Inconsistent type calculation results caused by caching mechanism #56813

NWYLZW opened this issue Dec 17, 2023 · 8 comments
Labels
Not a Defect This behavior is one of several equally-correct options

Comments

@NWYLZW
Copy link

NWYLZW commented Dec 17, 2023

🔎 Search Terms

  • cache, caching mechanism

🕗 Version & Regression Information

  • This changed between versions 4.6 and 5.3

⏯ Playground Link

https://www.typescriptlang.org/play?ts=4.6.4#code/KYDwDg9gTgLgBDAnmYcCqAmAkgHjQPjgF44AKNOUGYAOwBMBnOAQxsTgH4yBrALnQCUxQgDcIASzpx+NYCOBQhVWozKk+ccTQBmCuFiFFREukK5ZpcWfKgAoJCjgAZZgxhYaaGuIg08hEkxcWzh0ShBqeiYAVxpuGggAdxpOMhB+NENCAAZLawVbQmUotP4tXShnLLhskNSnOpk5AtBIWARkVEwAFTwAGmdXeBIXNw8vHz8CALgAbTQAXXDI1Vn8qAW6rlnN0P5ZgDojnpwAURAAYwAbaLpgfsG3fHwB0ZhN2wB6T47HbtzAhhelBgMw6L4ruwdnAAD5zGjRAC2ACMFAt8F8ft9QnAAHoceydODdACMxHQQJwsxJSzhswwtPhSNRGwx2NC+NsQA

💻 Code

export type U2I<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
type LastInUnion<U> = U2I<
  U extends unknown ? (x: U) => 0 : never
> extends (x: infer L) => 0
  ? L
  : never
export type U2T<U, Last = LastInUnion<U>> = [U] extends [never]
  ? []
  : [...U2T<Exclude<U, Last>>, Last]

// type T0 = U2T<readonly [] | [number]>
// //   ^?
type T1 = U2T<[1] | [2] | [number]>
//   ^? type [[number]]

🙁 Actual behavior

When the same value is passed into the type calculation function, the calculation result will be affected by the previous line of code.
Export-1702828320091

When we comment or uncomment, the calculation result of the next line type function will change accordingly.

// type T0 = U2T<readonly [] | [number]>
// //   ^?

🙂 Expected behavior

I think our cache data is being misused here, and I hope that the result of the calculation here should be uniquely determinable regardless of the rest of the code, instead of like it is now.

Additional information about the issue

No response

@NWYLZW
Copy link
Author

NWYLZW commented Dec 17, 2023

A magic solution.

We can override the Exclude function so that this works slightly correctly.

type Exclude1<T,U> = T extends U ? (<A>() => (A extends T ? 1 : 2)) extends (<A>() => (A extends U ? 1 : 2)) ? never : T : T

@jcalz
Copy link
Contributor

jcalz commented Dec 17, 2023

Such inconsistency is the price one pays for ducking beneath the caution tape and wandering around. The internal representation of a conceptually unordered structure like a union is supposed to be unobservable. If there’s a bug here it’s that you can observe it, not that the observation is inconsistent:

#13298 (comment)

Unless there’s an example that doesn’t involve an intentionally unsupported use case, I expect this to be closed as “won’t fix” or “not a defect” or “by design” or “skrt swerve” or whatever the lingo is nowadays.

@NWYLZW
Copy link
Author

NWYLZW commented Dec 17, 2023

#13298 (comment)

In fact, I don't care about the order of the union types here, I just want the caching mechanism to work properly.
Because it looks more like it's not finding the right cache target.

@jcalz
Copy link
Contributor

jcalz commented Dec 18, 2023

If you don't care about the order then your code shouldn't depend on being able to pull the "last" element out of a union type. If you are using Exclude to whittle down a union and one of the union elements is a supertype of another element, then the size of the result is order-dependent. I don't see anything happening here that I can imagine will be classified as a bug. If you can demonstrate an inconsistency that doesn't stem from trying to inspect union order, please do so. Otherwise I still imagine this will be a "won't fix".

@RyanCavanaugh RyanCavanaugh added the Not a Defect This behavior is one of several equally-correct options label Dec 18, 2023
@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Dec 18, 2023

Union order is not supposed to be observable. If you manage to build something that does observe union ordering, well, stop doing that (but congrats on finding something - it's not easy!).

Breaking things on purpose does not constitute a bug 🫠

@NWYLZW
Copy link
Author

NWYLZW commented Dec 20, 2023

sigh😮‍💨
In that case, I will use this self-defined Exclude to solve this problem.
Thank you for your answers.

@NWYLZW NWYLZW closed this as completed Dec 20, 2023
@fatcerberus
Copy link

fatcerberus commented Dec 20, 2023

@NWYLZW Take heed that your LastInUnion, even if it’s working for you now, is extremely fragile - unions are internally sorted by an ID number that largely correlates with the order the constituent types are first encountered by the compiler. As such changing other code in the program, even in another source file entirely, can change the result. Heck, it can even be affected by outside forces like the order the compiler processes the individual source files.

I personally wouldn’t touch anything that looks like UnionToTuple with a ten-foot pole.

@NWYLZW
Copy link
Author

NWYLZW commented Dec 20, 2023

Yes, indeed, this issue arises when converting a union type into an intersection type because using function overloading to obtain the last type in the union type leads to this uncertain problem. However, in my case, I only need to 'pop' a type out of the union type, so I mentioned that I don't really care about the order. I just want the types to be processed one after another.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Not a Defect This behavior is one of several equally-correct options
Projects
None yet
Development

No branches or pull requests

4 participants