Description
π Search Terms
"type alias in function signature", "type alias in function body", "type scope", "duplicate types", "hoist type alias"
β Viability Checklist
- 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 isn't a request to add a new utility type: https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types
- This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
β Suggestion
This is a weird request, and I am certain it's not going to be accepted easily. I am basically asking to break lexical scoping for this use case. But I want to show the pain point, so that maybe a better solution can be found.
My suggestion is to hoist type declarations at the top of a function body to outside of the function, so that those type declarations can be used in the function signature.
π Motivating Example
Suppose a function like this:
function myFunction<T1, T2>(param1: CalculateParam1Type<T1,T2>, param2: CalculateParam2Type<T1, T2>): CalculateReturnType<T1, T2> {
type TParam1 = CalculateParam1Type<T1, T2>;
type TParam2 = CalculateParam2Type<T1, T2>;
type TReturn = CalculateReturnType<T1, T2>;
const result: TReturn = { /* ... */ };
return result;
}
Notice how I wrote the same types multiple times. It's often worse than that when you have more generic type parameters, and type parameters are named descriptively like TMyValue
instead the short T1
etc. The code becomes unreadable quickly. To reduce code duplication, you often have to write intermediate generic types that are only used for that function, and still pass the long list of generics to that type each time.
Typescript should allow us to write the function above like:
function myFunction<T1, T2>(param1: TParam1, param2: TParam2): TReturn {
type TParam1 = CalculateParam1Type<T1, T2>;
type TParam2 = CalculateParam2Type<T1, T2>;
type TReturn = CalculateReturnType<T1, T2>;
const result: TReturn = { /* ... */ };
return result;
}
This can be a breaking change, as TParam1
could exist outside of this function for example. In such a case, the type declaration that is outside should be picked to not cause a breaking change.
π» Use Cases
- What do you want to use this for?
Generic functions with complex types would benefit from this change. Library level types often need to handle many cases at once and make invalid states irrepresentable. That often requires complex types, and it goes out of hand without a feature like this.
- What shortcomings exist with current approaches?
Current approach is to duplicate the type everywhere it's needed. Duplicate code can lead to bugs. Also code becomes unreadable with large types.
- What workarounds are you using in the meantime?
Defining each type declaration multiple times.