0

Zod is amazing at performing crazy type transformations and provide an inferred result that is completely devoid of generic types when previewed in your IDE.

const ObjSchema = z.object({
    t: z.enum(['A', 'B'])
}).transform(obj => {
    return obj.t === 'A' ? { bar: true } : { baz: 'bar' }
})

type ObjSchemaInput = z.input<typeof ObjSchema>
// { t: 'A' | 'B' }

type ObjSchemaOutput = z.infer<typeof ObjSchema>
/* {
    bar: boolean;
    baz?: undefined;
} | {
    baz: string;
    bar?: undefined;
} */

When trying to do my own complex type logic, I get much less useful previews.

Take this stupid example:

type Bar = { bar: true }
type Baz<T> = { baz: T extends 'A' ? 'foo' : 'bar' }

type Foo<T extends 'A' | 'B'> = {
    [K in 'A' | 'B']: T extends 'A' ? Bar : Baz<T>
}

type T1 = Foo<'B'> // { A: Baz<'B'>, B: Baz<'B'> }

There I expect T1 to be reported as { A: { baz: 'foo' }, B: { baz: 'foo' } }

While looking complex, these types are shallow and finite, and should be easily resolvable.

See Playground


But this nonsense works great:

type Obj<U extends string, V extends string> = {
  uv: U extends 'a' ? `${U}-${V}` : never
}

type Foo<T extends string> =
  T extends `${infer U}:${infer V}` ? Obj<U, V> : never

type A = Foo<'a:b'> // { uv: 'a-b' }

Based on the first example, I'd expect that to say like Obj<'a', 'b'>, but here it does the right thing and I'm not entirely sure why.


So how do I get type reporting more like Zod?


Motivating Example

I don't expect anyone to debug this, but it illustrates the problem better.

I'm trying to provide types for setter functions of GLSL shaders derived from only the source code.

For example:

const vertex = /* glsl */ `
  attribute vec3 position;
  attribute vec2 normal;
  
  uniform float value;
  uniform vec2 vec2Value;

  varying vec2 uv;

  void main() {
      gl_Position = vec4(position, 1.0);
      uv = position.xy;
  }
`

declare const shaderObject: ShaderObject<typeof vertex>
shaderObject.uniforms.vec2Value.set(1, 2)
//           ^?

My types so far return a type hint at that location that looks like this:

(property) uniforms: Settables<"uniform", SettableVariableDeclarations<[SingleDeclaration<"vec3", "attribute", "position">, SingleDeclaration<"vec2", "attribute", "normal">, SingleDeclaration<"float", "uniform", "value">, SingleDeclaration<...>, SingleDeclaration<...>, SingleDeclaration<...>, FunctionDefinition<...>], "uniform">>

Which is nearly useless.

I was hoping for something like:

{
  value: { set(n: number): void }
  vec2Value: { set(x: number, y: number): void }
}
  

Which does get more clear as you drill in, and at this location:

shaderObject.uniforms.vec2Value.set(1, 2)
//                              ^?

The reported type is:

(property) set: (x: number, y: number) => void

Which is great, but you have to discover the properties through autocomplete first, and can't easily see a list of the available props.

See Playground


So is there some sort of trick to writing your types in such a way that resolves them fully when reported back to you?

Alex Wayne
  • 178,991
  • 47
  • 309
  • 337
  • 2
    Does this answer your question? [How can I see the full expanded contract of a Typescript type?](https://stackoverflow.com/questions/57683303/how-can-i-see-the-full-expanded-contract-of-a-typescript-type). Wrapping your types into `ExpandRecursively` should do the trick. – Tobias S. Dec 30 '22 at 17:51

0 Answers0