-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Cache specific utility types #41263
Comments
If this were possible to implement tractably, we would have done it a really long time ago. The problem is that you can't just say "re-use this type", because a type can depend on any number of other types which are changing as the program is edited. If you write expensive type T = U extends V ? Z : W; then anything about I speak from experience because this is how TypeScript 0.8 worked, and it was incredibly, incredibly buggy, because any single bug here effectively corrupts the editing session because types get out of date. We literally rewrote the compiler from scratch instead of using a cached approach. A plausible path forward would be to automatically compute when a type was somehow provably isolated from the rest of the world (maybe it's entirely contained within a sourcefile, or something) and somehow make that thing stick around, but we if we did that, we'd do it automatically for everything, not ask for a user opt-in. |
Thanks for the detailed response; I didn't know typescript 0.8 cached. I recognize the issues/difficulties with a reverse dependency graph and/or things closer to incremental semantic analysis. Instead, I was suggesting to memoize specific generic types based on the hash, to benefit from previous computations without restructuring the existing type instantiation process; i.e. given type A = /* ... */;
expensive type X<T> = /* ... */;
type B = X<A>; before instantiating the Complex recursive utility types would benefit greatly, as for each analysis you only need to traverse them once to hash it, rather than multiple times to reinstantiate it. I suspect that a hashing approach could be easier to test, as existing unit tests can be utilized without individual modification by running them with different initial memoization caches. If an approach like this could work, giving it to users behind a keyword would be great, but if it could be done automatically, that would be even better. 🙂 |
Would it make a difference if the user manually specified cache invalidation with the utility type (similar to certain React hooks, eg. useMemo, useEffect, useCallback)? Considering the use case of #41254, the
import {UnsafeParse} from "./internal";
export type Parse<Src extends string> = Memo<
UnsafeParse<Src>,
[Src] // <-- only recompute when `Src` changes
> If a library developer wants to create a particularly-complex type––and they are certain that the computation is finite––they have no way to bypass the recursion limiter. Yes, enabling such bypassing could be dangerous, as type library consumers might accidentally allow types to eat up resources. Maybe a new compiler option could require users to be deliberate about usage.
An The crux of this issue is that the recursion limiter prevents the creation of would-be-valuable type-safe experiences. I won't go on about the value of single-env interop of type systems of embedded languages... there are too many other experiences that would also benefit. |
I think it would be bad if editor session "corruption" could occur because of user error. Additionally, I think a challenge with it would be identifying that old Parse is the same as new Parse; as RyanCavanaugh mentioned, "there's no old copy of U or V to compare with". While I think discussing a change to the recursion limiter has merit, I do not think that is within the scope of this issue. Slight asideHowever, I do like the concept of the type MyVar<NewVal = never, Set extends true | false = false> = Memo<
true extends Set ? MyVar<NewVal, false> : NewVal,
[Set],
>; But that would just be evil. |
Search Terms
cache types
Suggestion
(WIP; syntax bikeshed-able)
A new keyword
expensive
, used as belowWhen a generic type uses the
expensive
keyword, it marks it to be cached by the compiler.The compiler will hash the type alias (generics unresolved) and the passed type parameters, and use that to memoize the resulting type.
Suggestions for hashing the types:
unique symbol
types and classes with private properties, it also hashes the relevant name & source file pathinfer
s, it labels themT0
,T1
, etc. (as they appear), and hashes them as suchDocumented
Utility Type #41165)Use Cases
With complex utility types, perf can often be sluggish as "semantic information produced from type computation is recomputed fresh on each edit"
Suggestion adapted from a comment on #41254
Examples
Checklist
My suggestion meets these guidelines:
The text was updated successfully, but these errors were encountered: