@@ -32,25 +32,62 @@ import { Memory } from '../../system/memory/index.ts'
3232import { type StaticType , type StaticDirection } from './static.ts'
3333import { type TSchema , type TSchemaOptions , IsKind } from './schema.ts'
3434import { type TProperties } from './properties.ts'
35+ import { type TObject } from './object.ts'
36+ import { type TUnknown } from './unknown.ts'
3537
38+ // ------------------------------------------------------------------
39+ //
40+ // CyclicGuard
41+ //
42+ // This Guard checks if a given Ref already exists in the Stack,
43+ // indicating recursion. If recursion is found, it ensures the Stack
44+ // length remains below a safe threshold.
45+ //
46+ // The purpose is to allow recursive types to instantiate up to a
47+ // reasonable depth (for user feedback) while preventing unbounded
48+ // recursion.
49+ //
50+ // Note: This Guard is only needed for non-Object types, since
51+ // TypeScript automatically defers inference for referential
52+ // mapped property types.
53+ //
54+ // ------------------------------------------------------------------
55+ type CyclicStackLength < Stack extends unknown [ ] , MaxLength extends number , Buffer extends unknown [ ] = [ ] > = (
56+ Stack extends [ infer Left , ...infer Right ]
57+ ? Buffer [ 'length' ] extends MaxLength
58+ ? false
59+ : CyclicStackLength < Right , MaxLength , [ ...Buffer , Left ] >
60+ : true
61+ )
62+ type CyclicGuard < Stack extends unknown [ ] , Ref extends string > = (
63+ Ref extends Stack [ number ] ? CyclicStackLength < Stack , 4 > : true
64+ )
3665
3766// ------------------------------------------------------------------
38- // Static
67+ // StaticRef
68+ //
69+ // The inference Stack is appended only when encountering a Ref. If the
70+ // referenced target is a TObject, the Stack is reset, and TypeScript's
71+ // built-in inference deferral for referential property types applies.
72+ //
73+ // In all other cases, the Ref is pushed onto the Stack and checked
74+ // with CyclicGuard to ensure recursion is terminated based on the
75+ // CyclicGuard heuristic. Terminated recursion defaults to Any as an
76+ // approximation of the expansive type.
77+ //
3978// ------------------------------------------------------------------
40- type StaticTerminate < Stack extends unknown [ ] , Ref extends string > = (
41- Stack [ 'length' ] extends 6
42- ? true
43- : false
79+ type StaticGuardedRef < Stack extends string [ ] , Direction extends StaticDirection , Context extends TProperties , This extends TProperties , Ref extends string , Type extends TSchema > = (
80+ CyclicGuard < Stack , Ref > extends true
81+ ? StaticType < [ ... Stack , Ref ] , Direction , Context , This , Type >
82+ : any
4483)
45- export type StaticRef < Stack extends string [ ] , Direction extends StaticDirection , Context extends TProperties , This extends TProperties , Ref extends string ,
46- Result extends unknown = (
47- Ref extends keyof Context
48- ? StaticTerminate < Stack , Ref > extends false
49- ? StaticType < [ ...Stack , Ref ] , Direction , Context , This , Context [ Ref ] >
50- : any
51- : unknown
52- )
84+ export type StaticRef < Stack extends string [ ] , Direction extends StaticDirection , Context extends TProperties , This extends TProperties , Ref extends string ,
85+ Target extends TSchema = Ref extends keyof Context ? Context [ Ref ] : TUnknown ,
86+ Result extends unknown = Target extends TObject
87+ ? StaticType < [ /* Reset */ ] , Direction , Context , This , Target >
88+ : StaticGuardedRef < Stack , Direction , Context , This , Ref , Target >
5389> = Result
90+
5491// ------------------------------------------------------------------
5592// Type
5693// ------------------------------------------------------------------
0 commit comments