#function parameters cannot handle type unions in function overload

12 messages · Page 1 of 1 (latest)

next roost
#

Hello, I feel like this is a bug or maybe already known, asking here just to be sure...

I have a type A which takes one generic that defaults to any.
I have a type B which takes two generics and they both default to any.
I have a type Mix which is a union of A and B.

I have a function which has two overloads.
In the first overload, it'll take in a function that itself takes a parameter of type A.
In the second overload, it'll take in a function that itself takes a parameter of type B.

In the implementation, it takes in a function that itself takes a parameter of type Mix but now, TS complains that the first overload isn't compatible with the implementation signature.

That is very weird because I don't have this error when the implementation takes in a union of a function that takes in a parameter of type A and of a function that takes in a parameter or type B which should (?) be the same thing because Mix is a union of A and B.

Here is a playground that's probably easier to understand:

rose pumiceBOT
#
shigu64#0

Preview:```ts
type A<T = any> = {param: T}
type B<T = any, U = any> = {param: T; param2: U}

type Mix = A | B

function func(b: B): void
function func(a: A): void

function func(value: Mix): void {
value
}

function func2(b: () => B): void
function func2(a: () => A): void
...```

next roost
#

Weirdly, the error goes away when A and B happen to take in the same number of generics. For example:

rose pumiceBOT
#
shigu64#0

Preview:```ts
type A<T = any, U = any> = {param: T; param2: U}
type B<T = any, U = any> = {param: T; param2: U}

type Mix = A | B

function func(b: B): void
function func(a: A): void

function func(value: Mix): void {
value
}

function func2(b: () => B): void
...```

plush mantle
#

this error doesn't have anything to do specifically with overload signatures or generic types or unions. it's because function types are contravariant with respect to their parameter types. you can assign a function whose parameter types are wider to a function type with narrower parameter types, not the other way around (and in your first example, B is narrower than Mix)

#

here's a more focused example:

rose pumiceBOT
#
type Subtype = { a: unknown }
type Supertype = { a: unknown, b: unknown }

declare const subtype: Subtype
declare const supertype: Supertype

const subtype2: Subtype = supertype // ok
const supertype2: Supertype = subtype // error
//    ^^^^^^^^^^
// Property 'b' is missing in type 'Subtype' but required in type 'Supertype'.

declare const acceptsSubtype: (subtype: Subtype) => void
declare const acceptsSupertype: (supertype: Supertype) => void

const acceptsSubtype2: (subtype: Subtype) => void = acceptsSupertype // error
//    ^^^^^^^^^^^^^^^
// Type '(supertype: Supertype) => void' is not assignable to type '(subtype: Subtype) => void'.
//   Types of parameters 'supertype' and 'subtype' are incompatible.
//     Type 'Subtype' is not assignable to type 'Supertype'.
const acceptsSupertype2: (supertype: Supertype) => void = acceptsSubtype // ok
plush mantle
#

with normal values (or function return types) the subtyping rule allows a more specific value to be used where a more general one is needed. but function arguments go the other way

#

to be more concrete: the problem is that acceptsSupertype could access .b on its parameter, but if you assign it to a function that takes Subtype as the parameter then there won't be a .b

#

!:variance

rose pumiceBOT
#
tjjfvi#0
`!t6:variance`:

Here's the example with Dog and Animal, explaining co/contra/in variance:

  • Covariance: () => Dog is assignable to () => Animal, because Dog is assignable to Animal; it "preserves the direction of the assignability"
  • Contravariance: (Animal) => void is assignable to (Dog) => void, because something that expects an Animal can also take a Dog; it "reverses the direction of the assignability"
  • Invariance: (Animal) => Animal is not assignable to (Dog) => Dog, because not all returned Animals are Dogs, and (Dog) => Dog is not assignable to (Animal) => Animal, because something expecting a Dog cannot take any other kind of Animal
next roost
#

wow, took me quite a bit to understand but it makes sense, now, thanks