sinclairzx81 / typebox

Json Schema Type Builder with Static Type Resolution for TypeScript
Other
4.77k stars 152 forks source link

Max call stack size exceeded #929

Closed damodharanj closed 1 month ago

damodharanj commented 1 month ago

type Zero = 0

type Inc<T> = { len: T }

type Dec<T> = 
    T extends Zero ? Zero :
    T extends {len: any} ? T['len'] : T

type Add<X, Y> =
  X extends Zero ? Y :
  Inc<Add<Dec<X>, Y>>

This above ts when translated to typebox throws max call stack size exceeded

const Zero = Type.Literal(0);

const Inc = (T: any) =>
  Type.Extends(
    T,
    Zero,
    Type.Object({
      len: T,
    }),
    Type.Object({
      len: T,
    })
  );

  const Dec = (T: any) =>
  Type.Extends(T, Zero, Zero, Type.Index(T, Type.Literal('len')));

const Add = (X, Y) => Type.Recursive((This => {
  return Type.Extends(
    X,
    Zero,
    Y,
    Add(Dec(X), Y)
  )
}))

Is there a way to recursively point to the type where the case is a generic and not structurally recursive ?

sinclairzx81 commented 1 month ago

@damodharanj Hi, Apoligies for the delay in reply.

The issue here is that Add(Dec(X), Y) is called immediately inside the Add function (which is causing the stackoverflow). TypeBox types are eagerly evaluated, don't support deferred/lazy evaluation and only support a subset of TypeScript's programmable feature set (although I've been working towards full programmability)

You can implement simple stuff like increment, but usually it's better to work through the TypeScript programming model and generate the TypeBox equivalent. You can use the link below to experiment.

Workbench Link Here

export type Table = Static<typeof Table>
export const Table = Type.Object({
  '0': Type.Literal('1'),
  '1': Type.Literal('2'),
  '2': Type.Literal('3'),
  '3': Type.Literal('4'),
  '4': Type.Literal('5'),
  '5': Type.Literal('6'),
  '6': Type.Literal('7')
})

export type Increment<A extends TSchema> = Static<
  ReturnType<typeof Increment<A>>
>
export const Increment = <A extends TSchema>(A: A) =>
  Type.Extends(A, Type.KeyOf(Table), Type.Index(Table, A), Type.Never())

export type A = Static<typeof A>
export const A = Increment(Type.Literal('2'))

Will close off this issue for now, but if you have any questions, feel free to ping on this thread. Cheers S