4

I have a function with an overload:

interface FunctionWithOverload {
    (): {
        a: 1
        b: 1
    }

    <T>(arg: T): {
        a: 1
        b: 1
    } & (T extends number ? { c: 1 } : {})
}

const fwo: FunctionWithOverload = () => {return {} as any}

const result = fwo() // result has a nice type: {a: 1, b: 1}
const result1 = fwo(1) // result1 does not: {a: 1, b: 1} & {c: 1}

Playground

If you hover over result, you can see it has a nice type {a:1,b:1} in the tooltip, however result1 has an ugly type {a:1, b:1} & {c:1}.

The question is: how do I somehow merge {a:1, b:1} & {c:1} to {a:1, b:1, c:1} in my case?

Requirements

The function overloads must be as they are, i.e. I am not allowed to add a mutual optional property c to the return type.

Any type alias names shouldn't be added to the output in the tooltip (unless it's the only solution to this problem).

The prettiness matters because it's in the requirements of my task.

Nurbol Alpysbayev
  • 19,522
  • 3
  • 54
  • 89
  • 1
    You can try an `Id` mapped type `type Id={ [P in keyof T] :T[P]} ` but the compiler sometimes expands mapped types sometimes it does not.. – Titian Cernicova-Dragomir Jan 01 '19 at 07:49
  • @TitianCernicova-Dragomir Hi Titian, Happy New Year! The funny thing is that it doesn't even help, I tried it, produces: `Id<{a:1,b:1} & {c:1}>`. It maybe that I put it wrongly like this: `(arg: T): Id<{..`. Oh wait, I got it, you meant exactly this by *sometimes expands sometimes not* – Nurbol Alpysbayev Jan 01 '19 at 08:24
  • Happy new year to you too :). Yup, that's exactly what I meant, it's hard to get the compiler to do what you want in this case...I've been meaning to loo at compiler code to see how it decides what to expand and what not but have not gotten a chance – Titian Cernicova-Dragomir Jan 01 '19 at 08:30
  • @TitianCernicova-Dragomir I have good news! You know how I managed to expand mapped type (if *expand* means removing type alias name and showing as a plain object literal type)? This: `type Id={} & { [P in keyof T] :T[P]}` ! Weird! Any ideas why that happens? :D – Nurbol Alpysbayev Jan 01 '19 at 08:31

1 Answers1

5

I've managed (with the help of TitianCernicova-Dragomir) to solve this! Like this:

interface FunctionWithOverload {
    (): {
        a: 1
        b: 1
    }

    <T>(arg: T): Id<{
        a: 1
        b: 1
    } & (T extends number ? { c: 1 } : {})>
}

type Id<T>={} & { [P in keyof T] :T[P]}

const fwo: FunctionWithOverload = () => {return {} as any}

const result = fwo() // result has a nice type: {a: 1, b: 1}
const result1 = fwo(1) // result1 DOES HAVE TOO!!! {a: 1, b: 1, c: 1} 

Playground

I think the way it works probably is: the TS engine considers mapped types as worthy of being named types, like interfaces, while any unions, intersections (like in the solution above) or basic types like string literals are not.

Nurbol Alpysbayev
  • 19,522
  • 3
  • 54
  • 89
  • LIke I said in the comments .. unpredictable ... I would not be surprised if this does not always work, but it's a good option to try :) – Titian Cernicova-Dragomir Jan 01 '19 at 08:35
  • 1
    I just tried it on a complex example where I was also frustrated by the lack of expansion and it worked, soo looks like the intersection helps :) – Titian Cernicova-Dragomir Jan 01 '19 at 08:38
  • @TitianCernicova-Dragomir I think the way it works probably is: the TS engine considers mapped types as worthy of being named types, like interfaces, while any unions, intersections or basic types like string literals are not! – Nurbol Alpysbayev Jan 01 '19 at 08:38
  • Perhaps .. but I an sure I have seen mapped types expanded as well. – Titian Cernicova-Dragomir Jan 01 '19 at 08:43
  • @TitianCernicova-Dragomir you mean mapped types assigned to a type alias? Hmm.. Well I hope this will not happen to my case. BTW if you find such example, please let me know! I am curious what can lead to this behavior. – Nurbol Alpysbayev Jan 01 '19 at 08:47