Description
Suggestion
π Search Terms
Call function type
Invoke function type
Function return type
Generic function return type
Simplify extends conditions
β Viability Checklist
My suggestion meets these guidelines:
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This feature would agree with the rest of TypeScript's Design Goals.
β Suggestion
Allow callable function types invoking on type level. The logic behind it is the same as we have for usual function calling
interface One {
(): string
}
type Result = One() // Result is string
If function type has arguments we must pass them as well, but use types as argument
interface Data {
name: string
}
type One = (value: number, data: Data) => boolean
type Result = One(number, Data) // Result is boolean
The same logic with generics
interface Data {
name: string
}
type One = <T>(value: number, data: T) => T
type Result = One<Data>(number, Data) // Result is Data
type Result = One(number, Data) // Result is Data because of type inference
type Result2 = One(number, boolean) // Result is boolean
However, there is a problem when the type itself has a generic
type One<V> = <T>(value: V, data: T) => T
// Implicitly merge them in one row?
type Result = One<number, Data>(number, Data)
// Think about some other syntax ?
type Result = One<number><Data>(number, Data)
// Surround by brackets?
type Result = (One<number>)<Data>(number, Data)
π Motivating Example
Typescript has callable types, i.e.
interface One{
(value: string): string
}
type Two = () => string
and when we need to to get the return type we can use ReturnType
type to inference the type
interface One {
(value: string): string
}
type Result = ReturnType<One>
But there are problems when you have overloading
interface One {
(value: string): string
(value: number): number
}
type Result = ReturnType<One>
The things becomes even more complex if you have generics
interface One {
<T>(value: T): T
}
type Result = ReturnType<One>
The Result
will be just unknown
π» Use Cases
We will be able to create a type of what function is returning:
declare function createData(condition: 'a'): { a: number; b: number }
declare function createData(condition: 'b'): { c: number; d: number }
type CreateData = typeof createData
type DataA = CreateData('a') // DataA is { a: number; b: number }
type DataB = CreateData('b') // DataB is { c: number; d: number }
We will be able to deal with generic functions
declare function factory<T>(data: T): { getValue(): T; setValue(value: T): void }
type Factory = typeof factory
type MyFactory1 = Factory({ name: string }) // MyFactory1 is { getValue(): { name: string }; setValue(value: { name: string }): void }
type MyFactory2 = Factory({ email: string }) // MyFactory2 is { getValue(): { email: string }; setValue(value: { email: string }): void }
We can simplify extends
conditions. For example we have a type with a lot of ?
conditions. If there are a lot of conditions it is becomes harder to read them.
interface Data {
name: string
}
type SomeCondition<T> = T extends number ? 'number' : T extends Data ? 'data' : T extends string ? 'string' : unknown
and since functions can have overloading we can use it as switch case
interface Data {
name: string
}
interface SomeCondition {
(value: number): 'number'
(value: Data): 'data'
(value: string): 'string'
// Works as default case
(...value: any[]): unknown
}
// or
interface SomeCondition {
<T extends number>(value: T): 'number'
<T extends Data>(value: T): 'data'
<T extends string>(value: T): 'string'
// Works as default case
(...value: any[]): unknown
}
type A = SomeCondition(number) // A is 'number'
type B = SomeCondition(Data) // B is 'data'
type C = SomeCondition(string) // C is 'string'
type D = SomeCondition(true) // D is unknown
So it works exactly the same as we would just normally call functions
declare const data: Data
declare const logic: SomeCondition
const a = logic(1) // a is 'number'
const b = logic(data) // b is 'data'
const c = logic('foo') // c is 'string'
const d = logic(true) // d is unknown