Skip to content

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

Open
@Andarist

Description

@Andarist

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    In DiscussionNot yet reached consensusSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions