From d1c1f14bc3baa4d9d68624497b617a71ab1d3117 Mon Sep 17 00:00:00 2001 From: kennysarnoski Date: Sat, 31 Oct 2026 10:34:00 -0700 Subject: [PATCH 1/2] Keep inferring from type arguments when the same generic type is nested inside them Since #64553, inference between the type arguments of two references to the same generic type (or alias) runs through invokeOnce, which pushes both types onto the recursion stacks and stops with a circularity when isDeeplyNestedType finds the same recursion identity twice on both sides. For a type nested in its own arguments, such as Eff<() => Eff, never>, the outer and inner references share an identity, so the inner pair is treated as a cycle at depth 2 and nothing is inferred for the inner type parameters. R then falls back to unknown. Before #64553 this path called inferFromTypeArguments directly and had no such guard, which is also what TypeScript 5.9 and 7.0 do. This keeps the cache from #64553 for the two type-argument paths but routes them through a variant of invokeOnce that leaves the recursion stacks alone and never reports a circularity. Inference from type arguments is bounded by the arguments themselves; deferred references were already excluded, and object types on the way down still go through the guarded invokeOnce. Fixes #64757. Co-Authored-By: Claude Mythos 5.1 --- tsc/internal/checker/inference.go | 55 +++-- ...omNestedTypeArgumentsOfSameGeneric.symbols | 233 ++++++++++++++++++ ...FromNestedTypeArgumentsOfSameGeneric.types | 176 +++++++++++++ ...ferFromNestedTypeArgumentsOfSameGeneric.ts | 58 +++++ 4 files changed, 503 insertions(+), 19 deletions(-) create mode 100644 tsc/testdata/baselines/reference/compiler/inferFromNestedTypeArgumentsOfSameGeneric.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/inferFromNestedTypeArgumentsOfSameGeneric.types create mode 100644 tsc/testdata/tests/cases/compiler/inferFromNestedTypeArgumentsOfSameGeneric.ts diff --git a/tsc/internal/checker/inference.go b/tsc/internal/checker/inference.go index 8b0cc87aedef4..524ebc932d326 100644 --- a/tsc/internal/checker/inference.go +++ b/tsc/internal/checker/inference.go @@ -81,7 +81,7 @@ func (c *Checker) inferFromTypes(n *InferenceState, source *Type, target *Type) } if source.alias != nil && target.alias != nil && source.alias.symbol == target.alias.symbol { if len(source.alias.typeArguments) != 0 || len(target.alias.typeArguments) != 0 { - c.invokeOnce(n, source, target, (*Checker).inferFromAliasTypeArguments) + c.invokeOnceWithoutRecursionCheck(n, source, target, (*Checker).inferFromAliasTypeArguments) } // And if there weren't any type arguments, there's no reason to run inference as the types must be the same. return @@ -227,7 +227,7 @@ func (c *Checker) inferFromTypes(n *InferenceState, source *Type, target *Type) switch { case source.objectFlags&ObjectFlagsReference != 0 && target.objectFlags&ObjectFlagsReference != 0 && (source.AsTypeReference().target == target.AsTypeReference().target || c.isArrayType(source) && c.isArrayType(target)) && !(source.AsTypeReference().node != nil && target.AsTypeReference().node != nil): // If source and target are references to the same generic type, infer from type arguments - c.invokeOnce(n, source, target, (*Checker).inferFromReferenceTypeArguments) + c.invokeOnceWithoutRecursionCheck(n, source, target, (*Checker).inferFromReferenceTypeArguments) case source.flags&TypeFlagsIndex != 0 && target.flags&TypeFlagsIndex != 0: c.inferFromContravariantTypes(n, source.AsIndexType().target, target.AsIndexType().target) case (isLiteralType(source) || source.flags&TypeFlagsString != 0) && target.flags&TypeFlagsIndex != 0: @@ -344,6 +344,19 @@ func (c *Checker) inferFromContravariantTypesIfStrictFunctionTypes(n *InferenceS // such that we would go on inferring forever, even though we would never infer // between the same pair of types. func (c *Checker) invokeOnce(n *InferenceState, source *Type, target *Type, action func(c *Checker, n *InferenceState, source *Type, target *Type)) { + c.invokeOnceWorker(n, source, target, action, true /*checkRecursion*/) +} + +// invokeOnceWithoutRecursionCheck caches like invokeOnce but leaves the recursion stacks alone and +// never reports a circularity. It serves inference between the type arguments of two references to +// the same generic type (or alias), which is bounded by the arguments themselves and needs no cycle +// guard. Pushing those references onto the stacks would make an outer `Box<() => Box>` look like +// a cycle at depth 2 and drop the inferences for the inner `T` (#64757). +func (c *Checker) invokeOnceWithoutRecursionCheck(n *InferenceState, source *Type, target *Type, action func(c *Checker, n *InferenceState, source *Type, target *Type)) { + c.invokeOnceWorker(n, source, target, action, false /*checkRecursion*/) +} + +func (c *Checker) invokeOnceWorker(n *InferenceState, source *Type, target *Type, action func(c *Checker, n *InferenceState, source *Type, target *Type), checkRecursion bool) { key := CacheHashKey{ Lo: uint64(source.id)<<32 | uint64(target.id), Hi: uint64(uint32(n.priority))<<2 | uint64(core.IfElse(n.contravariant, 2, 0)) | uint64(core.IfElse(n.bivariant, 1, 0)), @@ -355,25 +368,29 @@ func (c *Checker) invokeOnce(n *InferenceState, source *Type, target *Type, acti n.visited.set(key, InferencePriorityCircularity) saveInferencePriority := n.inferencePriority n.inferencePriority = InferencePriorityMaxValue - // We stop inferring and report a circularity if we encounter duplicate recursion identities on both - // the source side and the target side. - saveExpandingFlags := n.expandingFlags - n.sourceStack = append(n.sourceStack, source) - n.targetStack = append(n.targetStack, target) - if c.isDeeplyNestedType(source, n.sourceStack, 2) { - n.expandingFlags |= ExpandingFlagsSource - } - if c.isDeeplyNestedType(target, n.targetStack, 2) { - n.expandingFlags |= ExpandingFlagsTarget - } - if n.expandingFlags != ExpandingFlagsBoth { - action(c, n, source, target) + if checkRecursion { + // We stop inferring and report a circularity if we encounter duplicate recursion identities on both + // the source side and the target side. + saveExpandingFlags := n.expandingFlags + n.sourceStack = append(n.sourceStack, source) + n.targetStack = append(n.targetStack, target) + if c.isDeeplyNestedType(source, n.sourceStack, 2) { + n.expandingFlags |= ExpandingFlagsSource + } + if c.isDeeplyNestedType(target, n.targetStack, 2) { + n.expandingFlags |= ExpandingFlagsTarget + } + if n.expandingFlags != ExpandingFlagsBoth { + action(c, n, source, target) + } else { + n.inferencePriority = InferencePriorityCircularity + } + n.targetStack = n.targetStack[:len(n.targetStack)-1] + n.sourceStack = n.sourceStack[:len(n.sourceStack)-1] + n.expandingFlags = saveExpandingFlags } else { - n.inferencePriority = InferencePriorityCircularity + action(c, n, source, target) } - n.targetStack = n.targetStack[:len(n.targetStack)-1] - n.sourceStack = n.sourceStack[:len(n.sourceStack)-1] - n.expandingFlags = saveExpandingFlags n.visited.set(key, n.inferencePriority) n.inferencePriority = min(n.inferencePriority, saveInferencePriority) } diff --git a/tsc/testdata/baselines/reference/compiler/inferFromNestedTypeArgumentsOfSameGeneric.symbols b/tsc/testdata/baselines/reference/compiler/inferFromNestedTypeArgumentsOfSameGeneric.symbols new file mode 100644 index 0000000000000..fd92aac73164e --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/inferFromNestedTypeArgumentsOfSameGeneric.symbols @@ -0,0 +1,233 @@ +//// [tests/cases/compiler/inferFromNestedTypeArgumentsOfSameGeneric.ts] //// + +=== inferFromNestedTypeArgumentsOfSameGeneric.ts === +// Repro from #64757. Inference from the type arguments of two references to the +// same generic type must not stop when that type also appears nested inside +// one of its own arguments: `Box<() => Box>` is not a cycle, and the +// inner `R` must still be inferred. These shapes come from Effect's +// `HttpRouter.middleware`, Prisma's nested `select` results and +// `Saga.toLayer` of alchemy.run. + +interface Eff { +>Eff : Symbol(Eff, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 0, 0)) +>A : Symbol(A, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 7, 14)) +>R : Symbol(R, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 7, 20)) + + readonly _a: A; +>_a : Symbol(Eff._a, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 7, 29)) +>A : Symbol(A, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 7, 14)) + + readonly _r: R; +>_r : Symbol(Eff._r, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 8, 17)) +>R : Symbol(R, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 7, 20)) + + [Symbol.iterator](): Iterator, A>; +>[Symbol.iterator] : Symbol(Eff[Symbol.iterator], Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 9, 17)) +>Symbol.iterator : Symbol(SymbolConstructor.iterator, Decl(lib.es2015.iterable.d.ts, --, --)) +>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2019.symbol.d.ts, --, --)) +>iterator : Symbol(SymbolConstructor.iterator, Decl(lib.es2015.iterable.d.ts, --, --)) +>Iterator : Symbol(Iterator, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2025.iterator.d.ts, --, --)) +>Eff : Symbol(Eff, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 0, 0)) +>A : Symbol(A, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 7, 14)) +>R : Symbol(R, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 7, 20)) +>A : Symbol(A, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 7, 14)) +} +type ReqOf = [E] extends [never] ? never : [E] extends [Eff] ? R : never; +>ReqOf : Symbol(ReqOf, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 11, 1)) +>E : Symbol(E, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 12, 11)) +>E : Symbol(E, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 12, 11)) +>E : Symbol(E, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 12, 11)) +>Eff : Symbol(Eff, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 0, 0)) +>R : Symbol(R, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 12, 73)) +>R : Symbol(R, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 12, 73)) + +declare function fn, A, Args extends any[]>( +>fn : Symbol(fn, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 12, 90)) +>E : Symbol(E, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 13, 20)) +>Eff : Symbol(Eff, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 0, 0)) +>A : Symbol(A, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 13, 44)) +>Args : Symbol(Args, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 13, 47)) + + body: (...args: Args) => Generator, +>body : Symbol(body, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 13, 68)) +>args : Symbol(args, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 14, 9)) +>Args : Symbol(Args, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 13, 47)) +>Generator : Symbol(Generator, Decl(lib.es2015.generator.d.ts, --, --)) +>E : Symbol(E, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 13, 20)) +>A : Symbol(A, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 13, 44)) + +): (...args: Args) => Eff>; +>args : Symbol(args, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 15, 4)) +>Args : Symbol(Args, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 13, 47)) +>Eff : Symbol(Eff, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 0, 0)) +>A : Symbol(A, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 13, 44)) +>ReqOf : Symbol(ReqOf, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 11, 1)) +>E : Symbol(E, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 13, 20)) + +declare function succeed(a: A): Eff; +>succeed : Symbol(succeed, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 15, 39)) +>A : Symbol(A, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 16, 25)) +>a : Symbol(a, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 16, 28)) +>A : Symbol(A, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 16, 25)) +>Eff : Symbol(Eff, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 0, 0)) +>A : Symbol(A, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 16, 25)) + +interface Request { +>Request : Symbol(Request, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 16, 49)) + + readonly url: string; +>url : Symbol(Request.url, Decl(lib.dom.d.ts, --, --), Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 18, 19)) +} +declare const request: Eff; +>request : Symbol(request, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 21, 13)) +>Eff : Symbol(Eff, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 0, 0)) +>Request : Symbol(Request, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 16, 49)) +>Request : Symbol(Request, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 16, 49)) + +// Shape 1: a call signature from an instantiated generic type alias. +type Make

= (m: Eff<() => Eff, never>) => [R]; +>Make : Symbol(Make, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 21, 45)) +>P : Symbol(P, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 24, 10)) +>R : Symbol(R, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 24, 16)) +>m : Symbol(m, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 24, 19)) +>Eff : Symbol(Eff, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 0, 0)) +>Eff : Symbol(Eff, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 0, 0)) +>R : Symbol(R, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 24, 16)) +>R : Symbol(R, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 24, 16)) + +declare const make: Make; +>make : Symbol(make, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 25, 13)) +>Make : Symbol(Make, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 21, 45)) + +const r1 = make(succeed(fn(function* () { +>r1 : Symbol(r1, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 26, 5)) +>make : Symbol(make, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 25, 13)) +>succeed : Symbol(succeed, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 15, 39)) +>fn : Symbol(fn, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 12, 90)) + + yield* request; +>request : Symbol(request, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 21, 13)) + + return ""; +}))); +const check1: [Request] = r1; +>check1 : Symbol(check1, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 30, 5)) +>Request : Symbol(Request, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 16, 49)) +>r1 : Symbol(r1, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 26, 5)) + +// Shape 2: a method of an instantiated generic interface, where the outer +// type argument appears inside the nested type. +interface S { +>S : Symbol(S, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 30, 29)) +>F : Symbol(F, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 34, 12)) + + toLayer(build: Eff<(p: { n: number }) => Eff, never>): Req; +>toLayer : Symbol(S.toLayer, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 34, 16)) +>Req : Symbol(Req, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 35, 10)) +>build : Symbol(build, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 35, 15)) +>Eff : Symbol(Eff, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 0, 0)) +>p : Symbol(p, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 35, 27)) +>n : Symbol(n, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 35, 31)) +>Eff : Symbol(Eff, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 0, 0)) +>F : Symbol(F, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 34, 12)) +>Req : Symbol(Req, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 35, 10)) +>Req : Symbol(Req, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 35, 10)) +} +declare const s: S<{ p: boolean }>; +>s : Symbol(s, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 37, 13)) +>S : Symbol(S, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 30, 29)) +>p : Symbol(p, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 37, 20)) + +const r2 = s.toLayer(succeed(fn(function* ({ n }) { return { p: n > 0 }; }))); +>r2 : Symbol(r2, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 38, 5)) +>s.toLayer : Symbol(S.toLayer, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 34, 16)) +>s : Symbol(s, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 37, 13)) +>toLayer : Symbol(S.toLayer, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 34, 16)) +>succeed : Symbol(succeed, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 15, 39)) +>fn : Symbol(fn, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 12, 90)) +>n : Symbol(n, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 38, 44)) +>p : Symbol(p, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 38, 60)) +>n : Symbol(n, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 38, 44)) + +const check2: never = r2; +>check2 : Symbol(check2, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 39, 5)) +>r2 : Symbol(r2, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 38, 5)) + +const r3 = s.toLayer(succeed(fn(function* ({ n }) { +>r3 : Symbol(r3, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 40, 5)) +>s.toLayer : Symbol(S.toLayer, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 34, 16)) +>s : Symbol(s, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 37, 13)) +>toLayer : Symbol(S.toLayer, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 34, 16)) +>succeed : Symbol(succeed, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 15, 39)) +>fn : Symbol(fn, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 12, 90)) +>n : Symbol(n, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 40, 44)) + + yield* request; +>request : Symbol(request, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 21, 13)) + + return { p: n > 0 }; +>p : Symbol(p, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 42, 10)) +>n : Symbol(n, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 40, 44)) + +}))); +const check3: Request = r3; +>check3 : Symbol(check3, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 44, 5)) +>Request : Symbol(Request, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 16, 49)) +>r3 : Symbol(r3, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 40, 5)) + +// Shape 3: a generic alias as a parameter type, with the result nested two +// levels deep (cut down from Prisma's nested `select` results). +type Payload = { readonly data: T }; +>Payload : Symbol(Payload, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 44, 27)) +>T : Symbol(T, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 48, 13)) +>data : Symbol(data, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 48, 19)) +>T : Symbol(T, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 48, 13)) + +type Selected = S extends { select: infer Sel } ? { [K in keyof Sel & keyof T]: T[K] } : T; +>Selected : Symbol(Selected, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 48, 39)) +>T : Symbol(T, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 49, 14)) +>S : Symbol(S, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 49, 16)) +>S : Symbol(S, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 49, 16)) +>select : Symbol(select, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 49, 33)) +>Sel : Symbol(Sel, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 49, 47)) +>K : Symbol(K, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 49, 59)) +>Sel : Symbol(Sel, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 49, 47)) +>T : Symbol(T, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 49, 14)) +>T : Symbol(T, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 49, 14)) +>K : Symbol(K, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 49, 59)) +>T : Symbol(T, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 49, 14)) + +declare function viaAlias(p: Payload>>, s: S): Selected; +>viaAlias : Symbol(viaAlias, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 49, 97)) +>T : Symbol(T, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 50, 26)) +>S : Symbol(S, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 50, 28)) +>p : Symbol(p, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 50, 32)) +>Payload : Symbol(Payload, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 44, 27)) +>Payload : Symbol(Payload, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 44, 27)) +>Selected : Symbol(Selected, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 48, 39)) +>T : Symbol(T, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 50, 26)) +>S : Symbol(S, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 50, 28)) +>s : Symbol(s, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 50, 68)) +>S : Symbol(S, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 50, 28)) +>Selected : Symbol(Selected, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 48, 39)) +>T : Symbol(T, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 50, 26)) +>S : Symbol(S, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 50, 28)) + +declare const p: Payload>; +>p : Symbol(p, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 51, 13)) +>Payload : Symbol(Payload, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 44, 27)) +>Payload : Symbol(Payload, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 44, 27)) +>id : Symbol(id, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 51, 34)) + +const r4 = viaAlias(p, { select: { id: true } }); +>r4 : Symbol(r4, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 52, 5)) +>viaAlias : Symbol(viaAlias, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 49, 97)) +>p : Symbol(p, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 51, 13)) +>select : Symbol(select, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 52, 24)) +>id : Symbol(id, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 52, 34)) + +const check4: { id: number } = r4; +>check4 : Symbol(check4, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 53, 5)) +>id : Symbol(id, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 53, 15)) +>r4 : Symbol(r4, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 52, 5)) + diff --git a/tsc/testdata/baselines/reference/compiler/inferFromNestedTypeArgumentsOfSameGeneric.types b/tsc/testdata/baselines/reference/compiler/inferFromNestedTypeArgumentsOfSameGeneric.types new file mode 100644 index 0000000000000..2a47f92819b41 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/inferFromNestedTypeArgumentsOfSameGeneric.types @@ -0,0 +1,176 @@ +//// [tests/cases/compiler/inferFromNestedTypeArgumentsOfSameGeneric.ts] //// + +=== inferFromNestedTypeArgumentsOfSameGeneric.ts === +// Repro from #64757. Inference from the type arguments of two references to the +// same generic type must not stop when that type also appears nested inside +// one of its own arguments: `Box<() => Box>` is not a cycle, and the +// inner `R` must still be inferred. These shapes come from Effect's +// `HttpRouter.middleware`, Prisma's nested `select` results and +// `Saga.toLayer` of alchemy.run. + +interface Eff { + readonly _a: A; +>_a : A + + readonly _r: R; +>_r : R + + [Symbol.iterator](): Iterator, A>; +>[Symbol.iterator] : () => Iterator, A> +>Symbol.iterator : unique symbol +>Symbol : SymbolConstructor +>iterator : unique symbol +} +type ReqOf = [E] extends [never] ? never : [E] extends [Eff] ? R : never; +>ReqOf : ReqOf + +declare function fn, A, Args extends any[]>( +>fn : , A, Args extends any[]>(body: (...args: Args) => Generator) => (...args: Args) => Eff> + + body: (...args: Args) => Generator, +>body : (...args: Args) => Generator +>args : Args + +): (...args: Args) => Eff>; +>args : Args + +declare function succeed(a: A): Eff; +>succeed : (a: A) => Eff +>a : A + +interface Request { + readonly url: string; +>url : string +} +declare const request: Eff; +>request : Eff + +// Shape 1: a call signature from an instantiated generic type alias. +type Make

= (m: Eff<() => Eff, never>) => [R]; +>Make : Make

+>m : Eff<() => Eff, never> + +declare const make: Make; +>make : Make + +const r1 = make(succeed(fn(function* () { +>r1 : [Request] +>make(succeed(fn(function* () { yield* request; return "";}))) : [Request] +>make : Make +>succeed(fn(function* () { yield* request; return "";})) : Eff<() => Eff, never> +>succeed : (a: A) => Eff +>fn(function* () { yield* request; return "";}) : () => Eff +>fn : , A, Args extends any[]>(body: (...args: Args) => Generator) => (...args: Args) => Eff> +>function* () { yield* request; return "";} : () => Generator, string, any> + + yield* request; +>yield* request : Request +>request : Eff + + return ""; +>"" : "" + +}))); +const check1: [Request] = r1; +>check1 : [Request] +>r1 : [Request] + +// Shape 2: a method of an instantiated generic interface, where the outer +// type argument appears inside the nested type. +interface S { + toLayer(build: Eff<(p: { n: number }) => Eff, never>): Req; +>toLayer : (build: Eff<(p: { n: number; }) => Eff, never>) => Req +>build : Eff<(p: { n: number; }) => Eff, never> +>p : { n: number; } +>n : number +} +declare const s: S<{ p: boolean }>; +>s : S<{ p: boolean; }> +>p : boolean + +const r2 = s.toLayer(succeed(fn(function* ({ n }) { return { p: n > 0 }; }))); +>r2 : never +>s.toLayer(succeed(fn(function* ({ n }) { return { p: n > 0 }; }))) : never +>s.toLayer : (build: Eff<(p: { n: number; }) => Eff<{ p: boolean; }, Req>, never>) => Req +>s : S<{ p: boolean; }> +>toLayer : (build: Eff<(p: { n: number; }) => Eff<{ p: boolean; }, Req>, never>) => Req +>succeed(fn(function* ({ n }) { return { p: n > 0 }; })) : Eff<(args_0: { n: number; }) => Eff<{ p: boolean; }, never>, never> +>succeed : (a: A) => Eff +>fn(function* ({ n }) { return { p: n > 0 }; }) : (args_0: { n: number; }) => Eff<{ p: boolean; }, never> +>fn : , A, Args extends any[]>(body: (...args: Args) => Generator) => (...args: Args) => Eff> +>function* ({ n }) { return { p: n > 0 }; } : ({ n }: { n: number; }) => Generator +>n : number +>{ p: n > 0 } : { p: boolean; } +>p : boolean +>n > 0 : boolean +>n : number +>0 : 0 + +const check2: never = r2; +>check2 : never +>r2 : never + +const r3 = s.toLayer(succeed(fn(function* ({ n }) { +>r3 : Request +>s.toLayer(succeed(fn(function* ({ n }) { yield* request; return { p: n > 0 };}))) : Request +>s.toLayer : (build: Eff<(p: { n: number; }) => Eff<{ p: boolean; }, Req>, never>) => Req +>s : S<{ p: boolean; }> +>toLayer : (build: Eff<(p: { n: number; }) => Eff<{ p: boolean; }, Req>, never>) => Req +>succeed(fn(function* ({ n }) { yield* request; return { p: n > 0 };})) : Eff<(args_0: { n: number; }) => Eff<{ p: boolean; }, Request>, never> +>succeed : (a: A) => Eff +>fn(function* ({ n }) { yield* request; return { p: n > 0 };}) : (args_0: { n: number; }) => Eff<{ p: boolean; }, Request> +>fn : , A, Args extends any[]>(body: (...args: Args) => Generator) => (...args: Args) => Eff> +>function* ({ n }) { yield* request; return { p: n > 0 };} : ({ n }: { n: number; }) => Generator, { p: boolean; }, any> +>n : number + + yield* request; +>yield* request : Request +>request : Eff + + return { p: n > 0 }; +>{ p: n > 0 } : { p: boolean; } +>p : boolean +>n > 0 : boolean +>n : number +>0 : 0 + +}))); +const check3: Request = r3; +>check3 : Request +>r3 : Request + +// Shape 3: a generic alias as a parameter type, with the result nested two +// levels deep (cut down from Prisma's nested `select` results). +type Payload = { readonly data: T }; +>Payload : Payload +>data : T + +type Selected = S extends { select: infer Sel } ? { [K in keyof Sel & keyof T]: T[K] } : T; +>Selected : Selected +>select : Sel + +declare function viaAlias(p: Payload>>, s: S): Selected; +>viaAlias : (p: Payload>>, s: S) => Selected +>p : Payload>> +>s : S + +declare const p: Payload>; +>p : Payload> +>id : number + +const r4 = viaAlias(p, { select: { id: true } }); +>r4 : { id: number; } +>viaAlias(p, { select: { id: true } }) : { id: number; } +>viaAlias : (p: Payload>>, s: S) => Selected +>p : Payload> +>{ select: { id: true } } : { select: { id: true; }; } +>select : { id: true; } +>{ id: true } : { id: true; } +>id : true +>true : true + +const check4: { id: number } = r4; +>check4 : { id: number; } +>id : number +>r4 : { id: number; } + diff --git a/tsc/testdata/tests/cases/compiler/inferFromNestedTypeArgumentsOfSameGeneric.ts b/tsc/testdata/tests/cases/compiler/inferFromNestedTypeArgumentsOfSameGeneric.ts new file mode 100644 index 0000000000000..49faf45cf8f9f --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/inferFromNestedTypeArgumentsOfSameGeneric.ts @@ -0,0 +1,58 @@ +// @strict: true +// @target: esnext +// @noEmit: true + +// Repro from #64757. Inference from the type arguments of two references to the +// same generic type must not stop when that type also appears nested inside +// one of its own arguments: `Box<() => Box>` is not a cycle, and the +// inner `R` must still be inferred. These shapes come from Effect's +// `HttpRouter.middleware`, Prisma's nested `select` results and +// `Saga.toLayer` of alchemy.run. + +interface Eff { + readonly _a: A; + readonly _r: R; + [Symbol.iterator](): Iterator, A>; +} +type ReqOf = [E] extends [never] ? never : [E] extends [Eff] ? R : never; +declare function fn, A, Args extends any[]>( + body: (...args: Args) => Generator, +): (...args: Args) => Eff>; +declare function succeed(a: A): Eff; + +interface Request { + readonly url: string; +} +declare const request: Eff; + +// Shape 1: a call signature from an instantiated generic type alias. +type Make

= (m: Eff<() => Eff, never>) => [R]; +declare const make: Make; +const r1 = make(succeed(fn(function* () { + yield* request; + return ""; +}))); +const check1: [Request] = r1; + +// Shape 2: a method of an instantiated generic interface, where the outer +// type argument appears inside the nested type. +interface S { + toLayer(build: Eff<(p: { n: number }) => Eff, never>): Req; +} +declare const s: S<{ p: boolean }>; +const r2 = s.toLayer(succeed(fn(function* ({ n }) { return { p: n > 0 }; }))); +const check2: never = r2; +const r3 = s.toLayer(succeed(fn(function* ({ n }) { + yield* request; + return { p: n > 0 }; +}))); +const check3: Request = r3; + +// Shape 3: a generic alias as a parameter type, with the result nested two +// levels deep (cut down from Prisma's nested `select` results). +type Payload = { readonly data: T }; +type Selected = S extends { select: infer Sel } ? { [K in keyof Sel & keyof T]: T[K] } : T; +declare function viaAlias(p: Payload>>, s: S): Selected; +declare const p: Payload>; +const r4 = viaAlias(p, { select: { id: true } }); +const check4: { id: number } = r4; From 132d27c391f8fb34d83df83295398abe1c7cfe75 Mon Sep 17 00:00:00 2001 From: kennysarnoski Date: Sat, 31 Oct 2026 10:49:37 -0700 Subject: [PATCH 2/2] Cover the Prisma shape of #64757 with the reported repro The previous Shape 3 compared the same alias on both sides and did not fail before the fix. This is the reduction from the issue: a generic alias as a parameter type, with inference descending through nested arrays of a recursively computed type. It fails with TS2571 before the fix. Co-Authored-By: Claude Mythos 5.1 --- ...omNestedTypeArgumentsOfSameGeneric.symbols | 180 +++++++++++++----- ...FromNestedTypeArgumentsOfSameGeneric.types | 128 +++++++++---- ...ferFromNestedTypeArgumentsOfSameGeneric.ts | 41 +++- 3 files changed, 257 insertions(+), 92 deletions(-) diff --git a/tsc/testdata/baselines/reference/compiler/inferFromNestedTypeArgumentsOfSameGeneric.symbols b/tsc/testdata/baselines/reference/compiler/inferFromNestedTypeArgumentsOfSameGeneric.symbols index fd92aac73164e..aed64c4c9f043 100644 --- a/tsc/testdata/baselines/reference/compiler/inferFromNestedTypeArgumentsOfSameGeneric.symbols +++ b/tsc/testdata/baselines/reference/compiler/inferFromNestedTypeArgumentsOfSameGeneric.symbols @@ -175,59 +175,135 @@ const check3: Request = r3; >Request : Symbol(Request, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 16, 49)) >r3 : Symbol(r3, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 40, 5)) -// Shape 3: a generic alias as a parameter type, with the result nested two -// levels deep (cut down from Prisma's nested `select` results). -type Payload = { readonly data: T }; +// Shape 3: a generic alias as a parameter type, with the inference descending +// through nested arrays of a type computed by a recursive conditional type +// (cut down from Prisma's nested `select` results). The same type written +// inline still infers on every version. +type Payload = { scalars: object; objects: object }; >Payload : Symbol(Payload, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 44, 27)) ->T : Symbol(T, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 48, 13)) ->data : Symbol(data, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 48, 19)) ->T : Symbol(T, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 48, 13)) - -type Selected = S extends { select: infer Sel } ? { [K in keyof Sel & keyof T]: T[K] } : T; ->Selected : Symbol(Selected, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 48, 39)) ->T : Symbol(T, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 49, 14)) ->S : Symbol(S, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 49, 16)) ->S : Symbol(S, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 49, 16)) ->select : Symbol(select, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 49, 33)) ->Sel : Symbol(Sel, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 49, 47)) ->K : Symbol(K, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 49, 59)) ->Sel : Symbol(Sel, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 49, 47)) ->T : Symbol(T, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 49, 14)) ->T : Symbol(T, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 49, 14)) ->K : Symbol(K, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 49, 59)) ->T : Symbol(T, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 49, 14)) - -declare function viaAlias(p: Payload>>, s: S): Selected; ->viaAlias : Symbol(viaAlias, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 49, 97)) ->T : Symbol(T, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 50, 26)) ->S : Symbol(S, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 50, 28)) ->p : Symbol(p, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 50, 32)) ->Payload : Symbol(Payload, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 44, 27)) ->Payload : Symbol(Payload, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 44, 27)) ->Selected : Symbol(Selected, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 48, 39)) ->T : Symbol(T, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 50, 26)) ->S : Symbol(S, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 50, 28)) ->s : Symbol(s, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 50, 68)) ->S : Symbol(S, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 50, 28)) ->Selected : Symbol(Selected, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 48, 39)) ->T : Symbol(T, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 50, 26)) ->S : Symbol(S, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 50, 28)) - -declare const p: Payload>; ->p : Symbol(p, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 51, 13)) +>scalars : Symbol(scalars, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 50, 16)) +>objects : Symbol(objects, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 50, 33)) + +type FindResult

= A extends { select: infer S extends object } +>FindResult : Symbol(FindResult, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 50, 52)) +>P : Symbol(P, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 52, 16)) >Payload : Symbol(Payload, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 44, 27)) +>A : Symbol(A, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 52, 34)) +>A : Symbol(A, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 52, 34)) +>select : Symbol(select, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 52, 51)) +>S : Symbol(S, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 52, 65)) + + ? { + [K in keyof S]: S[K] extends object +>K : Symbol(K, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 54, 7)) +>S : Symbol(S, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 52, 65)) +>S : Symbol(S, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 52, 65)) +>K : Symbol(K, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 54, 7)) + + ? P extends { objects: { [k in K]: (infer O)[] } } +>P : Symbol(P, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 52, 16)) +>objects : Symbol(objects, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 55, 21)) +>k : Symbol(k, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 55, 34)) +>K : Symbol(K, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 54, 7)) +>O : Symbol(O, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 55, 49)) + + ? FindResult[] +>FindResult : Symbol(FindResult, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 50, 52)) +>O : Symbol(O, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 55, 49)) >Payload : Symbol(Payload, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 44, 27)) ->id : Symbol(id, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 51, 34)) - -const r4 = viaAlias(p, { select: { id: true } }); ->r4 : Symbol(r4, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 52, 5)) ->viaAlias : Symbol(viaAlias, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 49, 97)) ->p : Symbol(p, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 51, 13)) ->select : Symbol(select, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 52, 24)) ->id : Symbol(id, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 52, 34)) - -const check4: { id: number } = r4; ->check4 : Symbol(check4, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 53, 5)) ->id : Symbol(id, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 53, 15)) ->r4 : Symbol(r4, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 52, 5)) +>S : Symbol(S, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 52, 65)) +>K : Symbol(K, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 54, 7)) + + : never + : P['scalars'][K & keyof P['scalars']]; +>P : Symbol(P, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 52, 16)) +>K : Symbol(K, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 54, 7)) +>P : Symbol(P, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 52, 16)) + } + : never; + +type MessagePayload = { objects: {}; scalars: { body: string } }; +>MessagePayload : Symbol(MessagePayload, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 60, 10)) +>objects : Symbol(objects, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 62, 23)) +>scalars : Symbol(scalars, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 62, 36)) +>body : Symbol(body, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 62, 47)) + +type ConversationPayload = { objects: { messages: MessagePayload[] }; scalars: {} }; +>ConversationPayload : Symbol(ConversationPayload, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 62, 65)) +>objects : Symbol(objects, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 63, 28)) +>messages : Symbol(messages, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 63, 39)) +>MessagePayload : Symbol(MessagePayload, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 60, 10)) +>scalars : Symbol(scalars, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 63, 69)) + +type ThreadPayload = { objects: { conversations: ConversationPayload[] }; scalars: {} }; +>ThreadPayload : Symbol(ThreadPayload, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 63, 84)) +>objects : Symbol(objects, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 64, 22)) +>conversations : Symbol(conversations, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 64, 33)) +>ConversationPayload : Symbol(ConversationPayload, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 62, 65)) +>scalars : Symbol(scalars, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 64, 73)) + +declare function findThread(args: A): FindResult; +>findThread : Symbol(findThread, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 64, 88)) +>A : Symbol(A, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 66, 28)) +>args : Symbol(args, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 66, 31)) +>A : Symbol(A, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 66, 28)) +>FindResult : Symbol(FindResult, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 50, 52)) +>ThreadPayload : Symbol(ThreadPayload, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 63, 84)) +>A : Symbol(A, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 66, 28)) + +const thread = findThread({ +>thread : Symbol(thread, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 68, 5)) +>findThread : Symbol(findThread, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 64, 88)) + + select: { conversations: { select: { messages: { select: { body: true } } } } }, +>select : Symbol(select, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 68, 27)) +>conversations : Symbol(conversations, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 69, 11)) +>select : Symbol(select, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 69, 28)) +>messages : Symbol(messages, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 69, 38)) +>select : Symbol(select, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 69, 50)) +>body : Symbol(body, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 69, 60)) + +}); + +type ThreadRow = { conversations: Array<{ messages: Array }> }; +>ThreadRow : Symbol(ThreadRow, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 70, 3)) +>M : Symbol(M, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 72, 15)) +>conversations : Symbol(conversations, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 72, 21)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --) ... and 4 more) +>messages : Symbol(messages, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 72, 44)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --) ... and 4 more) +>M : Symbol(M, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 72, 15)) + +declare function firstMessageViaAlias(thread: ThreadRow): M; +>firstMessageViaAlias : Symbol(firstMessageViaAlias, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 72, 69)) +>M : Symbol(M, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 74, 38)) +>thread : Symbol(thread, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 74, 41)) +>ThreadRow : Symbol(ThreadRow, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 70, 3)) +>M : Symbol(M, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 74, 38)) +>M : Symbol(M, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 74, 38)) + +declare function firstMessageInline(thread: { conversations: Array<{ messages: Array }> }): M; +>firstMessageInline : Symbol(firstMessageInline, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 74, 66)) +>M : Symbol(M, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 75, 36)) +>thread : Symbol(thread, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 75, 39)) +>conversations : Symbol(conversations, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 75, 48)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --) ... and 4 more) +>messages : Symbol(messages, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 75, 71)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --) ... and 4 more) +>M : Symbol(M, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 75, 36)) +>M : Symbol(M, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 75, 36)) + +const inline: string = firstMessageInline(thread).body; +>inline : Symbol(inline, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 77, 5)) +>firstMessageInline(thread).body : Symbol(body, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 69, 60)) +>firstMessageInline : Symbol(firstMessageInline, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 74, 66)) +>thread : Symbol(thread, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 68, 5)) +>body : Symbol(body, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 69, 60)) + +const viaAlias: string = firstMessageViaAlias(thread).body; +>viaAlias : Symbol(viaAlias, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 78, 5)) +>firstMessageViaAlias(thread).body : Symbol(body, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 69, 60)) +>firstMessageViaAlias : Symbol(firstMessageViaAlias, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 72, 69)) +>thread : Symbol(thread, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 68, 5)) +>body : Symbol(body, Decl(inferFromNestedTypeArgumentsOfSameGeneric.ts, 69, 60)) diff --git a/tsc/testdata/baselines/reference/compiler/inferFromNestedTypeArgumentsOfSameGeneric.types b/tsc/testdata/baselines/reference/compiler/inferFromNestedTypeArgumentsOfSameGeneric.types index 2a47f92819b41..e341afee57d40 100644 --- a/tsc/testdata/baselines/reference/compiler/inferFromNestedTypeArgumentsOfSameGeneric.types +++ b/tsc/testdata/baselines/reference/compiler/inferFromNestedTypeArgumentsOfSameGeneric.types @@ -139,38 +139,102 @@ const check3: Request = r3; >check3 : Request >r3 : Request -// Shape 3: a generic alias as a parameter type, with the result nested two -// levels deep (cut down from Prisma's nested `select` results). -type Payload = { readonly data: T }; ->Payload : Payload ->data : T - -type Selected = S extends { select: infer Sel } ? { [K in keyof Sel & keyof T]: T[K] } : T; ->Selected : Selected ->select : Sel - -declare function viaAlias(p: Payload>>, s: S): Selected; ->viaAlias : (p: Payload>>, s: S) => Selected ->p : Payload>> ->s : S - -declare const p: Payload>; ->p : Payload> ->id : number - -const r4 = viaAlias(p, { select: { id: true } }); ->r4 : { id: number; } ->viaAlias(p, { select: { id: true } }) : { id: number; } ->viaAlias : (p: Payload>>, s: S) => Selected ->p : Payload> ->{ select: { id: true } } : { select: { id: true; }; } ->select : { id: true; } ->{ id: true } : { id: true; } ->id : true +// Shape 3: a generic alias as a parameter type, with the inference descending +// through nested arrays of a type computed by a recursive conditional type +// (cut down from Prisma's nested `select` results). The same type written +// inline still infers on every version. +type Payload = { scalars: object; objects: object }; +>Payload : Payload +>scalars : object +>objects : object + +type FindResult

= A extends { select: infer S extends object } +>FindResult : FindResult +>select : S + + ? { + [K in keyof S]: S[K] extends object + ? P extends { objects: { [k in K]: (infer O)[] } } +>objects : { [k in K]: O[]; } + + ? FindResult[] + : never + : P['scalars'][K & keyof P['scalars']]; + } + : never; + +type MessagePayload = { objects: {}; scalars: { body: string } }; +>MessagePayload : MessagePayload +>objects : {} +>scalars : { body: string; } +>body : string + +type ConversationPayload = { objects: { messages: MessagePayload[] }; scalars: {} }; +>ConversationPayload : ConversationPayload +>objects : { messages: MessagePayload[]; } +>messages : MessagePayload[] +>scalars : {} + +type ThreadPayload = { objects: { conversations: ConversationPayload[] }; scalars: {} }; +>ThreadPayload : ThreadPayload +>objects : { conversations: ConversationPayload[]; } +>conversations : ConversationPayload[] +>scalars : {} + +declare function findThread(args: A): FindResult; +>findThread : (args: A) => FindResult +>args : A + +const thread = findThread({ +>thread : { conversations: { messages: { body: string; }[]; }[]; } +>findThread({ select: { conversations: { select: { messages: { select: { body: true } } } } },}) : { conversations: { messages: { body: string; }[]; }[]; } +>findThread : (args: A) => FindResult +>{ select: { conversations: { select: { messages: { select: { body: true } } } } },} : { select: { conversations: { select: { messages: { select: { body: true; }; }; }; }; }; } + + select: { conversations: { select: { messages: { select: { body: true } } } } }, +>select : { conversations: { select: { messages: { select: { body: true; }; }; }; }; } +>{ conversations: { select: { messages: { select: { body: true } } } } } : { conversations: { select: { messages: { select: { body: true; }; }; }; }; } +>conversations : { select: { messages: { select: { body: true; }; }; }; } +>{ select: { messages: { select: { body: true } } } } : { select: { messages: { select: { body: true; }; }; }; } +>select : { messages: { select: { body: true; }; }; } +>{ messages: { select: { body: true } } } : { messages: { select: { body: true; }; }; } +>messages : { select: { body: true; }; } +>{ select: { body: true } } : { select: { body: true; }; } +>select : { body: true; } +>{ body: true } : { body: true; } +>body : true >true : true -const check4: { id: number } = r4; ->check4 : { id: number; } ->id : number ->r4 : { id: number; } +}); + +type ThreadRow = { conversations: Array<{ messages: Array }> }; +>ThreadRow : ThreadRow +>conversations : { messages: Array; }[] +>messages : M[] + +declare function firstMessageViaAlias(thread: ThreadRow): M; +>firstMessageViaAlias : (thread: ThreadRow) => M +>thread : ThreadRow + +declare function firstMessageInline(thread: { conversations: Array<{ messages: Array }> }): M; +>firstMessageInline : (thread: { conversations: Array<{ messages: Array; }>; }) => M +>thread : { conversations: Array<{ messages: Array; }>; } +>conversations : { messages: Array; }[] +>messages : M[] + +const inline: string = firstMessageInline(thread).body; +>inline : string +>firstMessageInline(thread).body : string +>firstMessageInline(thread) : { body: string; } +>firstMessageInline : (thread: { conversations: Array<{ messages: Array; }>; }) => M +>thread : { conversations: { messages: { body: string; }[]; }[]; } +>body : string + +const viaAlias: string = firstMessageViaAlias(thread).body; +>viaAlias : string +>firstMessageViaAlias(thread).body : string +>firstMessageViaAlias(thread) : { body: string; } +>firstMessageViaAlias : (thread: ThreadRow) => M +>thread : { conversations: { messages: { body: string; }[]; }[]; } +>body : string diff --git a/tsc/testdata/tests/cases/compiler/inferFromNestedTypeArgumentsOfSameGeneric.ts b/tsc/testdata/tests/cases/compiler/inferFromNestedTypeArgumentsOfSameGeneric.ts index 49faf45cf8f9f..1564d8c7b656c 100644 --- a/tsc/testdata/tests/cases/compiler/inferFromNestedTypeArgumentsOfSameGeneric.ts +++ b/tsc/testdata/tests/cases/compiler/inferFromNestedTypeArgumentsOfSameGeneric.ts @@ -48,11 +48,36 @@ const r3 = s.toLayer(succeed(fn(function* ({ n }) { }))); const check3: Request = r3; -// Shape 3: a generic alias as a parameter type, with the result nested two -// levels deep (cut down from Prisma's nested `select` results). -type Payload = { readonly data: T }; -type Selected = S extends { select: infer Sel } ? { [K in keyof Sel & keyof T]: T[K] } : T; -declare function viaAlias(p: Payload>>, s: S): Selected; -declare const p: Payload>; -const r4 = viaAlias(p, { select: { id: true } }); -const check4: { id: number } = r4; +// Shape 3: a generic alias as a parameter type, with the inference descending +// through nested arrays of a type computed by a recursive conditional type +// (cut down from Prisma's nested `select` results). The same type written +// inline still infers on every version. +type Payload = { scalars: object; objects: object }; + +type FindResult

= A extends { select: infer S extends object } + ? { + [K in keyof S]: S[K] extends object + ? P extends { objects: { [k in K]: (infer O)[] } } + ? FindResult[] + : never + : P['scalars'][K & keyof P['scalars']]; + } + : never; + +type MessagePayload = { objects: {}; scalars: { body: string } }; +type ConversationPayload = { objects: { messages: MessagePayload[] }; scalars: {} }; +type ThreadPayload = { objects: { conversations: ConversationPayload[] }; scalars: {} }; + +declare function findThread(args: A): FindResult; + +const thread = findThread({ + select: { conversations: { select: { messages: { select: { body: true } } } } }, +}); + +type ThreadRow = { conversations: Array<{ messages: Array }> }; + +declare function firstMessageViaAlias(thread: ThreadRow): M; +declare function firstMessageInline(thread: { conversations: Array<{ messages: Array }> }): M; + +const inline: string = firstMessageInline(thread).body; +const viaAlias: string = firstMessageViaAlias(thread).body;