'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.

enter image description here

// 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