-
Notifications
You must be signed in to change notification settings - Fork 12.8k
[TS 4.1] Type-level String Accumulation Error In Recursive Type #41111
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
Comments
It might be helpful to pare this down to a more minimal reproduction so that extraneous details don't distract from the underlying issue. For example: type IterateNoOp<T extends string> =
T extends `${infer C0}${infer CRest}` ? `${C0}${IterateNoOp<CRest>}` : "";
type Wrapped<T extends string> = { wrapped: IterateNoOp<T> };
type NotTooLong = "abcdefghij";
type Fine = IterateNoOp<NotTooLong>; // type Fine = "abcdefghij"
type AlsoFine = Wrapped<NotTooLong> // type AlsoFine = { wrapped: "abcdefghij"; }
type TooLong = "abcdefghijklmnopqrstuvwxyz";
type Err = IterateNoOp<TooLong>; // error!
// ------> ~~~~~~~~~~~~~~~~~~~~
// Type instantiation is excessively deep and possibly infinite
// type Err = `abcdefghijklmnopqrstuvwx${any}${any}`
type NoErr = Wrapped<TooLong>; // no error, but still:
// type NoErr = { wrapped: `abcdefghijklmnopqrstuvwx${any}${any}`; } Here all The TS4.1 template literal stuff does tend to slam into recursion limits really easily, so hopefully there will be some way to address this soon. Relevant issues:
|
@jcalz thank you for this spectacular explanation and references. You're always incredibly helpful & I appreciate you! |
You can circumvent this by using HKTs. type Access<T, U> = T[Extract<U, keyof T>]
interface GeneratorHKT<I, O> {
_I: I,
_O: O,
input: I,
_next: IteratorResult<I, O>
next: IteratorResult<I, O>,
}
type CallGenHKT<G extends GeneratorHKT<any, any>, I extends G["_I"]> = (G & {input: I})["next"];
type IterateGenHKT<G extends GeneratorHKT<any, any>, N extends any> = N extends { done: true } ? N : CallGenHKT<G, Access<N, "value">>;
// Does 10 iterations
interface CompileGeneratorHKT10<G extends GeneratorHKT<any, any>> extends GeneratorHKT<G["_I"], G["_O"]> {
next: IterateGenHKT<G, IterateGenHKT<G, IterateGenHKT<G, IterateGenHKT<G, IterateGenHKT<G, IterateGenHKT<G, IterateGenHKT<G, IterateGenHKT<G, IterateGenHKT<G, IterateGenHKT<G, IteratorYieldResult<this["input"]>>>>>>>>>>>,
}
// Does 1000 iterations
interface CompileGeneratorHKT1000<G extends GeneratorHKT<any, any>> extends CompileGeneratorHKT10<CompileGeneratorHKT10<CompileGeneratorHKT10<G>>> {
}
interface IterateNoOpGenHKT extends GeneratorHKT<[acc: string, i: string], string> {
next: (
this["input"][1] extends `${infer C0}${infer CRest}`
? IteratorYieldResult<[`${this["input"][0]}${C0}`, CRest]>
: IteratorReturnResult<this["input"][0]>
)
}
// If we use normal property accessing (`A[B]`), it needs to check that `B` is assignable to `keyof A`,
// which causes excessively deep. Instead, we use `Access<A, B>`, which circumvents this.
// Strangely, if we make this alias generic intead of assuming `IterateNoOpGenHKT`, it
// throws excessively deep. I am unsure what the cause of this is, but I assume it is related.
type IterateNoOp<T extends string> = Access<IterateGenHKT<CompileGeneratorHKT1000<IterateNoOpGenHKT>, { done: false, value: ["", T] }>, "value">;
type NotTooLong = "abcdefghij";
type Fine = IterateNoOp<NotTooLong>;
// type Fine = "abcdefghij"
type LongerButStillFine = "abcdefghijklmnopqrstuvwxyz";
type StillFine = IterateNoOp<LongerButStillFine>;
// type StillFine = "abcdefghijklmnopqrstuvwxyz"
type SuperLong = "999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars 999 chars"
type StllStillFine = IterateNoOp<SuperLong>;
// type StillStillFine = "999 chars 999 chars..." I was unable to get above 1000 with simple tweaks. |
TypeScript Version: 4.1.0-dev.20201014
Search Terms: recursive, type, string, literal, accumulation, reducer, token, split
I'm trying to create a mini type-level lexer, that deals solely with a sequence of GraphQL directives. Aka., the following are all valid:
@aaa
Taking the last example, here's the type-level structure that I'm trying to compute.
The generic type should be able to build such a structure––without provoking the excessive depth error––via tail calls.
Code
First we define the token type, along with a widened type,
Token.Any
(for the constraint ofNext
).Next we define our recursive type.
This
Tokenize
type checks whether the character at position0
ofSrc
is a subtype of a delimiting character (keyof KindByDelimiter
). If so, then we've hit the start of the next token. Therefore we create a token with the kind name and accumulator, and then tail-callTokenize
withC0
as the newLastDelimiter
. In the case thatC0
is not a delimiting char, we progress down the string (addingC0
to the accumulator (Acc
), passingCRest
in asSrc
, and leavingLastDelimiter
as is). If we've hit an empty string, we return undefined.Result:
The final argument sequence does not evaluate to the expected token. Instead, we get
any
. I could well be missing something (in which case I apologies for an issue better suited for StackOverflow)... although I believe this to be a bug.Here is the playground Link
Any suggestions as to a better approach would be greatly appreciated, although I understand you likely have higher priorities. Thank you!
The text was updated successfully, but these errors were encountered: