Typescript: New keyword infer to intentionally identify a known inferable type

Created on 27 Jan 2020  ·  7Comments  ·  Source: microsoft/TypeScript

Search Terms

infer

Suggestion

Add the possibility to type a variable with const myValue: infer = complexThing;

Use Cases

This solves the problem of having to disable the line with eslint/tslint if the rule typedef is intentionally activated

Examples

_(only one example)_
I have created a very complex smart class that deeply infers column-names for a table.
The class member table is now a very huge type and therefore I dont want to type it.
But I also dont want to write table: any or table: unknown

Bildschirmfoto 2020-01-27 um 13 27 58

@Component
export default class MyVueComponent {
  // eslint-disable-next-line @typescript-eslint/typedef
  public readonly table = new DataTable({
                    // ~~ dont want it to be any or unknown
    columnDefinitions: [
      {
        name: 'id',
        text: this.$t('table.id'),
        align: 'end',
        sortable: true,
        width: 120
      },
      {
        name: 'value',
        text: this.$t('table.value'),
        sortable: true,
        width: 250
      },
      {
        name: 'type',
        text: this.$t('table.type'),
        sortable: true,
        width: 150
      },
      {
        name: 'synonyms',
        text: this.$t('table.synonyms'),
        sortable: false
      },
      {
        name: 'link',
        text: this.$t('table.link'),
        sortable: true,
        width: 90,
        hideable: true,
        guard: () => this.$auth.isDeveloper()
      },
      {
        name: 'linkApproved',
        text: this.$t('table.link-approved'),
        sortable: true,
        width: 140,
        hideable: true,
        guard: () => this.$auth.isDeveloper()
      },
      {
        name: 'modifiedAt',
        text: this.$t('table.modified-at'),
        sortable: true,
        width: 150,
        hideable: true
      },
      {
        name: 'textAdUsage',
        text: this.$t('table.text-ad-usage'),
        sortable: true,
        width: 150,
        hideable: true
      },
      {
        name: 'group',
        text: this.$t('table.entity-group'),
        sortable: true,
        width: 140,
        hideable: true
      },
      {
        name: 'relation',
        text: this.$t('table.relation-to'),
        sortable: true,
        width: 170,
        hideable: true
      },
      {
        name: 'standalone',
        text: this.$t('table.standalone'),
        sortable: true,
        width: 180,
        hideable: true
      },
      {
        name: 'textAdAlternative',
        text: this.$t('table.text-ad-alternative'),
        sortable: true,
        width: 170,
        hideable: true
      },
      {
        name: 'actions',
        text: this.$t('global.table.header.actions'),
        width: 120
      }
    ]
  });
}

eslint blames me about to add a typedef to this line.

  1. I dont want to write the complete typedef to table and want the behaviour as not writing a typedef.
  2. I want to signalise other developers that this is fully intended and want the behaviour.
    So I can still profit of it when calling a function of it like this.table.setColumnVisibility('link', false);, were the first parameter is an union string literal of all possible hidden columns!

We cant create a type or interface with the name infer and use it as type definition
Therefore this will not be a breaking change!

Checklist

My suggestion meets these guidelines:

  • [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • [x] This wouldn't change the runtime behavior of existing JavaScript code
  • [x] This could be implemented without emitting different JS based on the types of the expressions
  • [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • [x] This feature would agree with the rest of TypeScript's Design Goals.
Declined Suggestion

Most helpful comment

At the risk of issuing a rare style proclamation from the TypeScript team, we strongly advise against the typedef rule. It's actively harmful in a lot of cases, and we're not going to add language features to work around a lint rule.

All 7 comments

Is disabling the typedef rule for the particular line in question really a problem? Adding features to TS isn't trivial, so I'd hope for a more compelling use case that can't be easily handled by existing mechanisms.

I think it would be nice if the infer keyword could result in a compile-time error if the right hand side is of type any or unknown
I'm also totally fine if this feature only make it into version 4.0+
It's only a proposal I suggest that could make TypeScript a little bit stronger

@ilogico Please describe what you don't like about this proposal and don't just downvote without giving a reason

@Shinigami92 I believe linters should solve the problems they create.
Not everyone use linters, so we shouldn't burden the language with features that are only useful for programmers who use specific linters and specific linting rules.
Maybe you can request the linter's maintainers to change the rule so it becomes more flexible, or maybe you can add a @eslint-ignore rule, or maybe the linter could have some specific mechanism for this.
Inferring the variable type is already the default behavior, so the feature would provide no value from a type checking point of view. As a TypeScript user, I would not like to see this clutter in code, that's why I voted against your suggestion.

At the risk of issuing a rare style proclamation from the TypeScript team, we strongly advise against the typedef rule. It's actively harmful in a lot of cases, and we're not going to add language features to work around a lint rule.

So if you don't see a benefit of not being able to assign an unknown or any to infer, which should lead to a compile-time error, we can close this proposal
It was just an idea and then I have to live with my strict lint settings 🤷‍♂️

@RyanCavanaugh I've got a few cases where type inference fails, and having partial inference would be way better than having none at all:

  1. On occasion, when I'm working with a generic with a complicated constraint and it infers the wrong type, I have to explicitly specify the full generic when really it's just one part, usually near the top level, that it's struggling to pick up on. The worst offenders here are generic recursive types that involve unions.
  2. When you have a union and you want to downcast to a single variant that's generic and more complicated than a simple array, infer would make that experience so much easier.
  3. For functions returning object literals with inner functions whose own types are dictated in part by the outer function's generics, infer could make them a lot easier to use.

    • In particular, if you omit that initial return type, here's two of the biggest side effects that make this such a big problem in my experience:



      • The result isn't immediately verified against the desired interface, so if you forget something, you're not told there, but likely in some completely different part of the code. And in my experience, tracing those errors is hardly any easier than tracing runtime errors in JS - the only difference is you didn't actually execute the code.


      • You get neither type inference nor auto-completion with any of the object's members or any of its methods' arguments.



    • This is basically the functional programming equivalent of classes implementing interfaces, and often comes up in similar situations.

    • This also comes up from time to time with functions returning such inner functions directly, but not as often in my experience.

For 3, here's a concrete example of how infer would help, in a more real-world scenario (pay attention to the on* hooks for each component):

I've personally witnessed 3 bite people countless times in Mithril's Gitter chat room with them asking questions about code down that vein, because you have to pass it to get anything useful, and when you do, you still could end up in some awkward situations with it due to TypeScript lacking any way to infer the type based on the return value. Furthermore, I also ran into this issue while making this example (a port of this file modified to be a lot more idiomatic and readable) - I briefly tried having oncreate delegate to onupdate, and it didn't work like I would've preferred, and so I had to factor it out into a separate function (updateFocus in the first two examples).

Or to put it another way, it's a bit more than just a minor nice-to-have for us, and it'd be nice for me to have something simple to throw out at less experienced TypeScript users running into issues (and later the docs + type definitions) to just say "do this and you're good" without any asterisks, and have that integrated into the type definitions.

Was this page helpful?
0 / 5 - 0 ratings