It is possible to assign a function that can handle a wider range of values to a function that can handle only a limited subset of those values because the RHS function can safely deal with the restricted set of values accepted by the LHS function.
type EntityType = "x" | "y"
type myFunc = (id: number, categoryType: EntityType) => void
const x: myFunc = (id: number, categoryType: string) => {
// some code here
}
x(1, 'x')
x(2, 'y')
x(3, 'a') // error
TS playground
Answer to updated question:
Based on the example above, it may seem like we can assign typeX to typeY if typeY includes all elements of typeX.
In short: no, this is not allowed.
Assignment only works when the RHS type is a subtype of the LHS type. For functions, this principle applies as well. Functions have two types and are contravariant in their argument type while being covariant in their return type. This behavior is determined by variance. Thus, for functions, subtyping follows these rules:
type Fn1 = (arg1: Arg1) => Ret1
type Fn2 = (arg2: Arg2) => Ret2
Fn1 extends Fn2 // implies const fn2: Fn2 = fn1 as Fn1
<=> // if and only if
(arg1: Arg1) => Ret1 extends (arg2: Arg2) => Ret2
<=>
Ret1 extends Ret2 'and' Arg2 extends Arg1
The relationship between argument types goes in the opposite direction, allowing assignment only when encountered in the argument's position. However, this does not permit types to be directly assignable like plain values. The contravariant direction only holds true within arguments.