Typescript: Add refactoring action to annotate a variable or a function signature component with its inferred type

Created on 28 Dec 2016  Â·  14Comments  Â·  Source: microsoft/TypeScript

Hi, I'm using latest stable VSCode / latest nightly TS and I'm not sure if codefixes and refactorings have been implemented there yet, in any case, having a refactoring option to take any variable which receives an implicit inferred type, say, through a function call:

function test(): number[] {
  // ...
}

let x = test(); 

And being able to right-click x and invoke an action like Annotate with inferred type (or Extract inferred type), such that the resulting statement would look like:

let x: number[] = test();

could be very useful!

Awaiting More Feedback Refactorings Suggestion

Most helpful comment

I would certainly appreciate having the opposite capability.

All 14 comments

I would certainly appreciate having the opposite capability.

Marking as "Awaiting More Feedback". Would love to hear from others what are the expected experience here.

@aluanhaddad and others who showed support.

I'm taking it you're serious about this, otherwise I have don't really know what you mean here, and I suggest adding more details.

I agree it would be useful to have the opposite capability as well, where annotated types that match their inferred types can be safely removed, but if that action is available, it seems natural to have the one I suggested for completion anyway.

@mhegazy

This is just a tool suggestion, not a language feature, so it doesn't really have any special impact on any meaningful experience, I mean, it can't really "degrade" the experience of using the language or the editor, it's just a utility. The editor experience is that the user can right click an individual identifier within a let, var or const statement or a class property with an immediate initializer, choose Annotate with inferred type and "document" the type into the code. There are cases where I find this useful, especially when the type is complex, or I want to "freeze" the type of a variable such that any change in the origin of the inferred type (function return type, secondary variable, literal) would mark the declared type as mismatched, rather than having various random subtle errors scattered all over the code, that are harder to interpret.

There are cases where I would use the opposite functionality as well, I guess, perhaps more like a global action, per file or per selection. I think this is a personal style choice, and depends on the particular scenario.

Also, being able to annotate functions or methods with their inferred return types, e.g.

function test() {
  return [1,2,3,4];
}

Right click test, select Annotate with inferred return type, leading to:

function test(): number[] {
  return [1,2,3,4];
}

Would also be very useful. I do that a lot, sometimes to have it visibly documented, or make sure that the return type is stabilized such that further changes to the body of the function that break it would be detected.

There are also cases where callback's signature types are inferred like

function example(f: (a: number, b: string) => boolean) {
}

example((x, y) => {
})

Where right clicking x, y or => and choosing Annotate with inferred type/Annotate with inferred return type or even combined to a single action like Annotate with inferred signature (maybe when, say, the arrow is right clicked for an anonymous function) could also be useful.

It would be awesome if there are both the array and tuple inferences to choose from in the (small heterogenous) array case. Currently it's very irritating to manually annotate them..

@rotemdan

I agree it would be useful to have the opposite capability as well, where annotated types that match their inferred types can be safely removed, but if that action is available, it seems natural to have the one I suggested for completion anyway.

I agree, I just worry that there will be some cultures where it becomes the established practice to run this refactoring as matter of course.
A big negative result of this is that type inference is short circuited and improvements and refinements to types no longer propagate to their use sites.
Another thing that comes to mind is that it may mean a very different thing to freeze the type of a value typed by an explicitly declared type as compared to freezing the type of a value typed by a "synthesized" union or intersection that has resulted from some confluence of inferences.
I am not saying this is a bad idea, but it would be important to use it prudently.

@aluanhaddad

This is just a utility, not a language feature, as I've already said, it cannot "degrade" the experience of the language or even the editor. It is not the role of the authors of refactoring tools to try to manipulate what users should or shouldn't do with the utilities they provide. I also suggested the annotation as an individual action, not a global one so that part of the process is that the inferred type is visually validated by the developer.

The funny thing is that the "remove unused variables" action is potentially more dangerous, as it can accidentally cause the loss of intermediate work that's in progress (though I personally don't see any problem with having it, again, it's just a utility, no one is inclined to use it). For some reason though people decided to antagonize over this one.

_Edit: To clarify, for the action that removes annotations, I meant that it would only remove the annotation if the inferred type _precisely_ matches the annotation, letter to letter - I mean, not just assignable or structurally matching with it._

Here's an enhancement idea: maybe instead of using a text like Annotate with inferred type, the tool will immediately show the actual suggested type in the menu option itself:

Something like:

function func(): number[] {};

let x = func();

Right clicking x the menu option would look like Annotate as 'number[]', so now the programmer can inspect and validate the type even before it is added to the code.

Similarly:

function func() {
  return ["a", "b", "c"];
}

Right clicking func the menu option would look like Annotate as 'function(): string[]'.
To address @gcnew's request, it could also include a separate option Annotate as 'function(): [string, string, string]'.

What about ['a', 'b', 'c']?

This would be helpful to me, also. One use case I have is being able to extract an interface from a private or anonymous class so it can be referenced in other scopes.

For example, inner classes (that is, classes as non-static properties of an _instance_ of an outer class) can't be easily statically typed:

class OuterClass {
  InnerClass: { new(...args: any[]): any }; // would like something more specific here
  outerProp: string;
  outerMethod() {
    return this.outerProp;
  }
  constructor(outerProp: string) {
    this.outerProp = outerProp;
    var outerInstance = this;
    this.InnerClass = class InnerClass {
      outerInstance = outerInstance;
      innerProp: string;
      constructor(innerProp: string) {
        this.innerProp = innerProp;
      }
      innerMethod() {
        return outerInstance.outerProp + ': ' + this.innerProp;
      }
    };
  }
}

var outerInstance = new OuterClass('howdy');
console.log(outerInstance.outerMethod());
var innerInstance = new outerInstance.InnerClass(0xd00d1e); // oops, bad argument
console.log(innerInstance.innerMethud()); // oops, bad method name

It would be nice if I go to the class InnerClass line and extract interfaces for the instance and static sides, such as:

interface InnerClassInstance {
  outerInstance: OuterClass;
  innerProp: string;
  innerMethod: () => string;
}
interface InnerClassStatic {
  new(innerProp: string): InnerClassInstance;
}
class OuterClass {
  InnerClass: InnerClassStatic;
â‹®

which would catch the errors below. Of course, one can manually examine the class definition and pull an interface out of it (as I did), but it can be tedious and error-prone. It would be amazing if the tooling could tell us what structural type the type checker thinks an expression is, and either annotate the expression with that type or let us copy the type somewhere else.

EDIT: I somehow unassigned this issue? Didn't mean to and don't think I can undo it. Oops?

@jcalz I believe editing the comment triggered GitHub to "fix" an invalid assignment (zhengbli is no longer a contributor). So no worries.

Broader refactoring support is on the way and things like this will be easy for us to add, so I expect we might do this in the future.

This feature is gonna be great improvement of developer experience. In PureScript for example if a top level declaration has no type annotation compiler gives a warning and using purescript-ide-vscode extension user can perform quick fix on such warning and type annotation will be added to to the declaration instantly.

If anyone knows an extension which provides such functionality please comment here

Hey guys, just wanted to mention, I had the same issue and made an extension for this.
Check it out and send me your feedback!
https://marketplace.visualstudio.com/items?itemName=nick-lvov-dev.typescript-explicit-types

Was this page helpful?
0 / 5 - 0 ratings

Related issues

uber5001 picture uber5001  Â·  3Comments

MartynasZilinskas picture MartynasZilinskas  Â·  3Comments

wmaurer picture wmaurer  Â·  3Comments

DanielRosenwasser picture DanielRosenwasser  Â·  3Comments

jbondc picture jbondc  Â·  3Comments