Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to create a type from an inferred type and reuse it #13949

Closed
benstevens48 opened this issue Feb 8, 2017 · 11 comments
Closed

Ability to create a type from an inferred type and reuse it #13949

benstevens48 opened this issue Feb 8, 2017 · 11 comments
Labels
Needs Investigation This issue needs a team member to investigate its status.

Comments

@benstevens48
Copy link

benstevens48 commented Feb 8, 2017

First of all, apologies if this is already possible or has already been suggested.

I'm a bit lazy when it comes to front-end development, so the type inference provided by TypeScript is very useful to avoid having to define interfaces etc.

However, sometimes I know that an object is of the same type as (or an extension of) another object but there's no way for the compiler to know. Therefore it would be useful for me to be able to save an inferred type for later reuse.

For example, I use VueJS components. To do this I need to provide a component options object which contains a methods object and a data function which returns the data. For example

function createData(){
  return {
    dataProp1: <string>null
  }
}

function method1(){
  let self = this;
  console.log(self.dataProp1);
  self.method2();
}

function method2(){
  let self = this;
  //dostuff
}

let componentOptions = {
  data: createData,
  methods: {method1, method2}
}
//todo: register component...

The self in the above code has the any type, but I as the developer know that the contents of methods and the result of create data will will available on this type. So it would be great it I could do something like this.

function createData(){
  return <=MyData>{
    dataProp1: <string>null
  }
}

function method1(){
  let self: MyComponent = this;
  console.log(self.dataProp1);
  self.method2();
}

function method2(){
  let self: MyComponent = this;
  //dostuff
}

interface MyComponent extends MyData, MyMethods {};

let componentOptions = {
  data: createData,
  methods: <=MyMethods>{method1, method2}
}
//todo: register component...

I'm not sure about the syntax but the =MyType is meant to mean that instead of requiring the object to be an instance of that type, it defines a new type MyType equal to the inferred type of the object. (The syntax should also work if I'd used let, as in, let methods: =MyMethods = {method1, method2}, instead of the angle brackets). Then I would have intellisense and type checking.

Apologies again if this is already possible or has already been suggested. I just thought I'd suggest it in case it was a good idea.

@benstevens48 benstevens48 changed the title Ability to create a type from and inferred type and reuse it Ability to create a type from and inferred type an reuse it Feb 8, 2017
@benstevens48 benstevens48 changed the title Ability to create a type from and inferred type an reuse it Ability to create a type from an inferred type and reuse it Feb 8, 2017
@DanielRosenwasser
Copy link
Member

DanielRosenwasser commented Feb 9, 2017

Just to understand, is the idea that <=MyMethods> creates a type that grabs hold of the following object type, and makes it visible throughout that object's scope? Could give an example to demonstrate how you plan to use it?

I think that is an interesting idea, however, I'd like to understand how that would augment the Vue experience. While you could add an explicit type for this (or just use variable named self like you did) on each method, I imagine that would get pretty repetitive. If you had this, would this an acceptable way to work with Vue?


The problem that we've run into with supporting Vue's options object is that there's circularity between the method & computed portions, as well as Vue's base instance type itself. You can see details at #12846.

The motivation is that we want to say that the type of this in every method and computed property is something like Vue & Data & Methods & Computed.

@benstevens48
Copy link
Author

Hi Daniel,
Thanks for you reply. Yes, that's correct. I see it as a hybrid between type inference and manual typing. In terms of an example, I thought the example I gave above was a decent one - ie using the types MyMethods and MyData in the interface MyComponent extends MyData, MyMethods {} and then being able to declare the variable self of type MyComponent .

It could also be used much more widely - any time you write an object without first declaring an interface for it, and you want to, for example, define a function that takes an object of that type. (This is perhaps lazy programming, but also I think being able to be lazy is one of the strengths of JavaScript). I can even imagine being able to export the generated type.

In terms of working with Vue, it would be acceptable to me to work this way (but not sure about others). It is often necessary to define let self=this anyway if using callbacks inside the function (Ok, you can use the arrow syntax but it still doesn't feel safe to use this inside those to me because half the time I use the function syntax out of habit and also personally I think they should behave the same as the function syntax).

Also, personally I've been creating modules which export the component options object, because this gives me more flexibility in how to use it (as a local component etc) and importing the module then doesn't change global state. In this case there is no way the compiler could infer the this type, so it's necessary to specify it manually. Also, as you can see, I like to be able to split up definitions of nested objects so they don't become too nested, and the above example allows me to do that.

I don't know whether you would run into any circularity problems like you have in the Vue issue you linked to, but I feel like it should be possible to implement this (although it would need to be built into the type inference system I suppose).

I'm not an expert in either Vue or TypeScript, so I apologise if I've used incorrect terminology or have overlooked certain features of either of them. Also, just for the record, this suggestion has much wider scope than Vue, but that's what gave me the idea in the first place.

@ProTip
Copy link

ProTip commented Feb 20, 2017

After a lot of searching this is the first issue I have come across that is near what I have been wanting. My case is for react-redux. A few functions return a type that can be used in inference, or match against other types, but I can't use it myself :( This means I have to create the interface boiler that matches the function return and use that:

interface ActionProps {
    toggleSelect: typeof toggleSelect
}

function mapDispatchToProps(dispatch: Dispatch<any>) {
    return bindActionCreators({
        toggleSelect: toggleSelect
    }, dispatch)
}

interface TermListProps {
    title: string
    filter?: Partial<Term>
}

class TermList extends React.Component<TermListProps & StateProps & ActionProps, any>

In situations like this it would be nice if I could reference the return type of the function ... & typeof mapDispatchToProps() or something similar. I've run into other situations where it would be nice to pin a type to the return of methods(such as default/empty generators).

@benstevens48
Copy link
Author

@ProTip Nice that someone else also likes this idea!

If I understand correctly what you want, then you could rewrite your code with my proposed syntax (still not sure about the best syntax) as follows.

function mapDispatchToProps(dispatch: Dispatch<any>): =ActionProps {
    return bindActionCreators({
        toggleSelect: toggleSelect
    }, dispatch)
}

interface TermListProps {
    title: string
    filter?: Partial<Term>
}

class TermList extends React.Component<TermListProps & StateProps & ActionProps, any>

@benstevens48
Copy link
Author

benstevens48 commented Feb 20, 2017

Since a couple of people have replied, I thought I'd go through possible drawbacks and implementation of this idea.

Possible drawbacks

  1. May encourage bad practice in some situations. But this could be mitigated by documenting it as an advanced feature and warning the user of potential drawbacks, plus I think users should have the right to choose for themselves.
  2. May provide alternative ways to do things which is potentially confusing. I think the counter-arguments from 1. apply here.
  3. Syntax may conflict with possible other future syntax. The obvious solution is to think carefully about the syntax!
  4. Possible unexpected side effects - I don't know if thing kind of thing has been done before. The only way to find out is to try!
  5. Possible difficultly to implement and potential increase in compile time. I will address this in my ideas on implementation below.

Possible implementation

I first thought about a "poor-man's implementation". In Visual Studio Code, you can hover over a variable (which can be a complicated object) and see its inferred type. Imagine if there was a button to generate an interface representing this inferred type (or perhaps there already is). Conceptually this is a useful starting point, especially as you can see that it would be no problem even to export the generated type from a module. However, this "poor-man's implementation" has two defects:

  1. You have to keep redoing it as you add more stuff inside the variable object.
  2. You may have to iterate a few times if you want to reference the type from within the variable object (as in my original case above).

Now 1 could be solved by adding an extra step at the start of the compilation process that reads my suggested syntax and simply automates the "poor-man's implementation" as described above. To solve 2 using this method you would need to iterate it a few times. However, I'm sure there is a cleverer way to do it as this kind of thing must come up during the type inference, type checking or compilation process anyway. I imagine that the type inference process runs first and generates intermediate "files" with explicit types added, which is very similar to what I've just described, therefore it seems to me that the easiest way to implement it would be to integrate this syntax into the type inference system. This should also keep the increase in compile time to a minimum.

Any chance someone could think seriously about this?

@ProTip
Copy link

ProTip commented Feb 21, 2017

I don't have a syntax preference. It seems to be that, naively, if typeof foo gives you the type signature then typeof foo() may give you the inferred(or otherwise) return type.

@benstevens48
Copy link
Author

@ProTip Ah, I didn't realise you could use typeof in this way in TypeScript. So it looks like this suggestion is simply an extension of that, plus maybe an alternative syntax. I think the syntax I suggested would cover the largest possible number of scenarios, but I also see how your syntax makes sense.

@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label May 24, 2017
@Aqours
Copy link

Aqours commented Feb 9, 2018

Good idea.

class A {
    static readonly ERROR_EXAMPLE = {
        110: 'error msg',
        111: 'error msg',
        112: 'error msg',
        113: 'error msg',
        121: 'error msg',
        122: 'error msg',
        123: 'error msg',
        124: 'error msg',
        ...
        // too many items
        ...
    }
}

In this case, I want A.ERROR_EXAMPLE's property to be readonly. I shall be write long types, like:

interface X {
    readonly 110: string;
    readonly 111: string;
    ...
}

Why not use { readonly [k: string]: string }? Oh, its type is too grand.

Assumption:

class A {
    static readonly ERROR_EXAMPLE: Readonly<TypeInferred> = {
        110: 'error msg',
        111: 'error msg',
        112: 'error msg',
        113: 'error msg',
        121: 'error msg',
        122: 'error msg',
        123: 'error msg',
        124: 'error msg',
        ...
        // too many items
        ...
    }
}

// export its type
export <TypeSpecified>A.ERROR_EXAMPLE;
export <TypeInferred>A.ERROR_EXAMPLE;

It sounds nice.

@Aqours
Copy link

Aqours commented Feb 9, 2018

function freeze<T>(obj: T): Readonly<T>;
const d = freeze({val: 1}); // d's type equal `interface D { readonly val: number };`

But we cannot use d's type.

type D = freeze({val: 1});

It looks fine if above code works, but actually it's not.

@RyanCavanaugh
Copy link
Member

The broad use cases here are now capturable with ReturnType<...> and indexed access types, so I don't think we need additional syntax for it. The implied scoping rules here wouldn't really work in practice anyway.

@matborowiak
Copy link

matborowiak commented Aug 26, 2022

As this comes up as one of the first things in google when looking for such questions asked...

If you want to create/extract type from inferred type of some known variable, here is the simple example of how you can do that without any fancy syntax or generics:

const person = {
   name: 'john',
   surname: 'doe',
   age: 33,
   employed: true,
}

type PersonType = typeof person // getting person's inferred type

PersonType will be exactly as inferred type of person object - you have created/extracted a type reference from inferred type. This will work with just anything, any shape of the object, function etc. This is a nice way to go when you work with large objects that have hard-coded values that are consumed by some functions and you are missing types for those functions arguments.

If you need to pull out only the return type from a function that has a known/inferred return type, previous answer using ReturnType<...> is the way to go, but firstly you have to pull out the inferred type of such function in the same exact way as in earlier example:

const personCreator = () => ({
   name: 'john',
   age: 33,
   employed: true,
})

type CreatorFunctionType = typeof personCreator // getting function's inferred type

type CreatorFunctionReturnType = ReturnType<CreatorFunctionType>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Investigation This issue needs a team member to investigate its status.
Projects
None yet
Development

No branches or pull requests

6 participants