microsoft / microsoft/TypeScript

New type param modifier to allow for partial inference based on signature declaration

Open
#53,999 6 comments 62 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

In Discussion Suggestion
Dominant language
Go
Stars
111k
Forks
14.3k
Avg merge
1d 19h
Merged PRs (30d)
117

Description

Suggestion

🔍 Search Terms

partial inference modifiers type parameters

✅ 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

Type inference is a powerful tool but for certain scenarios it suffers from a usability issue - it's all or nothing and doesn't allow for "partial application".

There are two angles here:

  1. users sometimes would like to partially apply the type arguments list
  2. library authors would like to accept partial type arguments list

When it comes to the first one there is already an open proposal for it here and even a PR implementing it here. Based on reactions below both of those I think it's fair to say that it's a highly requested feature.

However, even if that feature would get its way to the language it would still suffer from some usability issues for the mentioned second use case. The problem is that this feature would require an explicit opt-in by the user to leverage this feature. I think that's an acceptable tradeoff for library authors - we certainly wouldn't be super picky about it if that feature would land.

I think though that this could easily be improved by allowing a special type parameter modifier - like preferinfer or just infer:

declare function get<T, preferinfer K extends keyof T>(obj: T, key: K): T[K]

// `T` provided as `User`, `K` inferred as `name`
const res = get<User>({ name: 'Andarist', age: 32 }, 'name') 
res // string

Both use cases are very much related but I feel like they are orthogonal as one can exist without the other. This proposal focuses only on the second use case. A draft implementation for the feature can be found here.

📃 Motivating Example

In XState we accept a couple of type arguments but we'd also like to additionally perform some additional inference/validation based on the received config argument. It's a JSON-like schema:

declare function createMachine<
  TEvent extends { type: string },
  preferinfer const TConfig extends MachineConfig<TEvent>
>(config: TConfig): StateMachine<TConfig, TEvent>

type MyEvents = { type: 'INC' }

createMachine<MyEvents>({
  initial: 'foo',
  states: {
    foo: {
      on: {
        INC: 'bar', // should be allowed
        OTHER: 'bar', // should be an error
      }
    },
    bar: {}
  }
})

This would be especially useful for us in combination with the recently introduced const type parameters.

At the moment we have to resort to this, rather silly, workaround:

createMachine({
  types: {
    events: {} as MyEvents // workaround here
  },
  initial: 'foo',
  states: {
    foo: {
      on: {
        INC: 'bar', 
        OTHER: 'bar',
      }
    },
    bar: {}
  }
})

Drawbacks of this approach:

  • this puts some unnecessary pressure on TS to infer things that we don't really want to infer. They are meant to be explicitly given by the developers.
  • the types also become part of the inferred TConfig
  • it's easy to accidentally infer TEvent from locations that shouldn't be used as inference sources. A rogue any somewhere added to inference candidates might implicitly deopt all locations. We have to use NoInfer trick to avoid this

Another workaround for this issue is to change the API, use factory functions to "bind" explicit type parameters to the returned function. This alters the API though and I don't think that's really a suitable workaround when better alternatives could exist.

IIRC, Redux Toolkit also uses a similar pattern in its types to "accept an explicit type" for TState. And I distinctly remember that Tanner from Tanstack would give a lot for a feature like this (perhaps this would be more useful for Tanstack Table than Tanstack Query). I'm pretty sure that a ton of OSS library authors would just love, love to see a solution for this to land in some shape.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the proposed preferinfer modifier and the motivating get and createMachine signatures in this issue, then review the draft implementation in PR #52241. Compare the examples with current TypeScript inference behavior and determine the type-checking and regression-test coverage needed for explicit parameters to coexist with inference.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
compilers
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.