'Intersection type discrimination and function overloads with TS
This is the most concise i could get my example. I think i ultimately want to do:
type Foo<T> = (a:T)=>(b:Bar<T>)=>R1|R2|R3...
Where T is also an intersection. This snippet does exactly what i want, but aside from being a bit verbose, i use an any in the final signature for the overloaded function.
I've tried replacing any with Data<Shapes> and the whole thing just melted. How could i replace this any and is there a more concise way to infer some of this stuff or achieve exactly this (or something better if you see where i'm going with this). Thank you.
// id like to discriminate by this
enum Shape {
Triangle,
Square,
Hexagon,
}
// id like to have a generic "foo"
type FooProp = Record<string, number>
// id like to have an object that has foo, but foo should be better typed than FooProp
type MyShape<F extends FooProp = FooProp> = {
foo: F
}
// different foo type for each shape
type FooTriangle = {
triangle: number
}
type FooSquare = {
square: number
}
type FooHexagon = {
hexagon: number
}
// the actual shapes
type Triangle = MyShape<FooTriangle> & {
type: Shape.Triangle
}
type Square = MyShape<FooSquare> & {
type: Shape.Square
}
type Hexagon = MyShape<FooHexagon> & {
type: Shape.Hexagon
}
type Shapes = Triangle | Square | Hexagon
type Data<T extends MyShape, A = T['foo']> = {
shape: T
data: { [key in keyof A]: string }
}
type ResultTriangle = {
aa: string
}
type ResultSquare = {
bb: string
}
type ResultHexagon = {
cc: string
}
type Results = ResultTriangle | ResultSquare | ResultHexagon
//MY PROBLEM
function createResult(shape: Triangle): (data: Data<Triangle>) => ResultTriangle
function createResult(shape: Square): (data: Data<Square>) => ResultSquare
function createResult(shape: Hexagon): (data: Data<Hexagon>) => ResultHexagon
function createResult(shape: Shapes): (data: any) => Results { // <-- THIS LINE
switch (shape.type) {
case Shape.Triangle:
return doTriangle(shape)
case Shape.Square:
return doSquare(shape)
case Shape.Hexagon:
return doHexagon(shape)
}
}
// all this works great!
const doTriangle = (shape: Triangle) => (
data: Data<Triangle>
): ResultTriangle => ({
aa: shape.foo.triangle.toString() + data.data.triangle,
})
const doSquare = (shape: Square) => (data: Data<Square>): ResultSquare => ({
bb: shape.foo.square.toString() + data.data.square,
})
const doHexagon = (shape: Hexagon) => (data: Data<Hexagon>): ResultHexagon => ({
cc: shape.foo.hexagon.toString() + data.data.hexagon,
})
const a: Triangle = { type: Shape.Triangle, foo: { triangle: 0 } }
const b: Square = { type: Shape.Square, foo: { square: 0 } }
const c: Hexagon = { type: Shape.Hexagon, foo: { hexagon: 0 } }
const da: Data<Triangle> = { shape: a, data: { triangle: 'x' } }
const db: Data<Square> = { shape: b, data: { square: 'x' } }
const dc: Data<Hexagon> = { shape: c, data: { hexagon: 'x' } }
const ra = createResult(a)
const rb = createResult(b)
const rc = createResult(c)
ra(da) // works
ra(db) // error - as expected
rb(db) // workss
Solution 1:[1]
Something like this? There will still be more to work out though:
function createResult<T extends Shapes>(shape: T): (data: Data<T>) =>
T extends Triangle
? ResultTriangle
: T extends Square
? ResultSquare
: T extends Hexagon
? ResultHexagon
: never
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Jason Kleban |

