-
Notifications
You must be signed in to change notification settings - Fork 13.2k
Description
π Search Terms
Function definition parameter type inference implicit any noImplicitAny
β 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
function a(x: number) {}
function b(x: string) {}
function f(x) {
if (...) a(x)
else b(x)
}Ideally f would have an inferred type (x: number | string) => void as opposed to an implicit any, i.e. (x: any) => void
π Motivating Example
A common dependency injection pattern involves booting up (usually stateful) services, wrapping them up in an object, and passing it around as an argument.
// Instantiate stateful services
const db = new Database()
const auth = new Auth()
const analytics = new Analytics()
const env: Env = {db, auth, analytics}
function getUser(env: Env, args) { ... }
function createUser(env: Env, args) { ... }
function login(env: Env, args) { ... }
function logout(env: Env, args) { ... }As your application grows, you might end up with dozens of different services and several contexts for running the application / calling these functions. You end up with a growing need to break apart this big Env type.
First, when writing tests, you might not want to boot up every service. So you'll start to define which services you need in the env argument.
function createUser(env: {db: Database}, args) { ... }Now I can test this function without dealing with redis, cache, auth, etc.
Second, you might be running code from a few different contexts. Lets say that you want to run a few functions like createUser from the CLI without booting up a whole server. So now you might create a more than one different environment for dependency injection.
type ServerEnv = {db: Database, auth: Auth, analytics: Analytics}
type CliEnv = {db: Database, auth: Auth, aws: AWS}
type ServerOrCliEnv = { db: Database, auth: Auth }When you have 20 different services and 5 different contexts (api server, background job server, cli, ci, etc.), these types become a mess.
And when you want to add a service dependency to a function, you need to find all of the callers of that function and plumb that type through everywhere -- it's a bit laborious. For example:
function a(env: {a}) {}
function b(env: {b}) {}
function x(env: {a, b}) {
a(env)
b(env)
}
function y(env: {a}) {
a(env)
}Now if I want need to update a with a new dependency, I need to propagate that change everywhere manually.
function a(env: {a, c}) {}
function x(env: {a, b, c}) {}
function y(env: {a, b}) {}But in an ideal world, function parameters without type definitions aren't implicitly any but inferred.
function a(env: {a}) {}
function b(env: {b}) {}
function x(env) { // env is inferred to be {a,b}
a(env)
b(env)
}
function y(env) { // env is inferred to be {a}
a(env)
}This would allow type inference to handle all of the dependency injection for us and eliminate a lot of plumbing work of types.
π» Use Cases
- What do you want to use this for? See "Motivating Example"
- What shortcomings exist with current approaches? Probably some performance cost and some nontrivial backtracking.
- What workarounds are you using in the meantime? Manually plumb it yourself.