I want to define a function f such that arguments and return types are union types but they are tied, i.e. when you call f(anInputType) you get aCorrespondingOutputType as a result. I tried the following at it seems to work on TypeScript 4.1.3.
class Input1 {
type1Input: string = "I am type 1!";
}
class Input2 {
type2Input: string = "I am type 2!";
}
interface Output1 {
type1Output: string;
}
interface Output2 {
type2Output: string;
}
export type Input = Input1 | Input2;
export type Output = Output1 | Output2;
function f(_input: Input1): Output1;
function f(_input: Input2): Output2;
function f(_input: Input): Output {
return null as any as Output;
}
const input = new Input2();
const output = f(input);
output.type2Output // Compiles because the compiler can figure out that output is of type Output2
However, in (my) real life the inputs should be WebGL objects. The same thing, in which I replaced Input1 with WebGLTexture and Input2 with WebGLBuffer, does not compile.
interface Output1 {
type1Output: string;
}
interface Output2 {
type2Output: string;
}
export type Output = Output1 | Output2;
function f(_input: WebGLTexture): Output1;
function f(_input: WebGLBuffer): Output2;
function f(_input: WebGLTexture | WebGLBuffer): Output {
return null as any as Output;
}
const canvas = document.createElement("canvas")!;
const gl = canvas.getContext("webgl")!;
const input = gl.createBuffer()!;
const output = f(input);
output.type2Output // Does not compile because the compiler thinks that output is of type Output1
Am I missing something?
I am reading-ish through these TypeScript issues:
- https://github.com/Microsoft/TypeScript/issues/27131
- https://github.com/microsoft/TypeScript/issues/14107
As well as these other questions:
- Typescript overloading functions with union types
- Typescript function overloads not working for the union type case
But what troubles me is the difference between the behavior using existing types (i.e. the WebGL objects) versus custom classes.
Actually, I am adopting Alex idea
It looks verbose but with it I can get away with a single implementation. It's kinda of a no-win for me because of the way other parts of the app are organized; something, somewhere, is going to look ugly. But this is good for now! Thanks!
Real-life code below.
export type WebGLResource =
{ texture: WebGLTexture } |
{ buffer: WebGLBuffer } |
{ program: WebGLProgram } |
{ renderbuffer: WebGLRenderbuffer } |
{ framebuffer: WebGLFramebuffer };
export type ResourceMeta = TextureMeta | BufferMeta | ProgramMeta | RenderbufferMeta | FramebufferMeta;
function getMeta(<...omitted params...> resource: { texture: WebGLTexture }, required: true): TextureMeta;
function getMeta(<...omitted params...> resource: { buffer: WebGLBuffer }, required: true): BufferMeta;
function getMeta(<...omitted params...> resource: { program: WebGLProgram }, required: true): ProgramMeta;
function getMeta(<...omitted params...> resource: { renderbuffer: WebGLRenderbuffer }, required: true): RenderbufferMeta;
function getMeta(<...omitted params...> resource: { framebuffer: WebGLFramebuffer }, required: true): FramebufferMeta;
function getMeta(<...omitted params...> resource: { texture: WebGLTexture }, required: false): TextureMeta | null;
function getMeta(<...omitted params...> resource: { texture: WebGLBuffer }, required: false): BufferMeta | null;
function getMeta(<...omitted params...> resource: { buffer: WebGLProgram }, required: false): ProgramMeta | null;
function getMeta(<...omitted params...> resource: { renderbuffer: WebGLRenderbuffer }, required: false): RenderbufferMeta | null;
function getMeta(<...omitted params...> resource: { framebuffer: WebGLFramebuffer }, required: false): FramebufferMeta | null;
function getMeta(<...omitted params...> resource: WebGLResource, required: boolean): ResourceMeta | null {
...
}