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

Type definition: Adding type argument breaks type inferrence #15985

Closed
stelcheck opened this issue May 22, 2017 · 6 comments
Closed

Type definition: Adding type argument breaks type inferrence #15985

stelcheck opened this issue May 22, 2017 · 6 comments
Labels
Duplicate An existing issue was already created

Comments

@stelcheck
Copy link

TypeScript Version: 2.3.2

Code

Ref: mage/mage#42

Given the following type definition: https://github.com/stelcheck/mage/blob/5b5f116f1d09f93b54f089a11aa43f841cf1aa05/types.d.ts

By adding the type argument to the IState interface, state is no longer inferred.

// A *self-contained* demonstration of the problem follows...
import * as mage from 'mage'

export = <mage.core.IUserCommand> {
    acl: ['*'],
    execute: async function(state /* will complain that state is of type any */) {
        console.log(state)
        return 'ok'
    }
}

Expected behavior:

The type of state should be inferred to mage.core.IState

Actual behavior:

The type of state must now be explicitly stated.

@mhegazy
Copy link
Contributor

mhegazy commented May 22, 2017

The only place the new type parameter is referenced is in a return type, and return types are not inference position. #11152 tracks changing that.

@mhegazy mhegazy added the Duplicate An existing issue was already created label May 22, 2017
@stelcheck
Copy link
Author

My issue is actually not with the return type (which is correctly inferred in this case anyway - so a bit surprised to read that's currently an issue?); my issue is that as soon as I add a type parameter, the state variable is no longer inferred.

For instance, I still have the same issue if the type definition is as follow:

interface IUserCommand {
    acl?: string[];
    execute<T>(state: IState, second: T, ...args: any[]): any;
}

Or even as follow (spoecifying a type variable but not using it, and stripping all parts irrelevant to the issue being reported here):

interface IUserCommand {
    execute<T>(state: IState): any;
}

In all those cases the state parameter loose its inferrence (even though the very interface causing issue explicitly specifies the state of the first parameter). As soon as I remove the type parameter, state gets inferred as IState as expected.

@mhegazy
Copy link
Contributor

mhegazy commented May 23, 2017

My issue is actually not with the return type (which is correctly inferred in this case anyway - so a bit surprised to read that's currently an issue?); my issue is that as soon as I add a type parameter, the state variable is no longer inferred.

In the .d.ts file linked in the OP, the function definition is:

        interface IUserCommand {
            acl?: string[];
            execute<T>(state: IState, ...args: any[]): Promise<T>;
        }

see https://github.com/stelcheck/mage/blob/5b5f116f1d09f93b54f089a11aa43f841cf1aa05/types.d.ts#L1365

@mhegazy
Copy link
Contributor

mhegazy commented May 23, 2017

For instance, I still have the same issue if the type definition is as follow:

interface IUserCommand {
    acl?: string[];
    execute<T>(state: IState, second: T, ...args: any[]): any;
}

Generic signatures are not used as a contextual type. When you did not have a type parameter, the type of the signature was used to contextually type async function(state /* will complain that state is of type any */) { and state had a type. when you added the T, that stopped, and thus state had the type any.

related to #9366

@stelcheck
Copy link
Author

stelcheck commented May 23, 2017

Thank you for the explanation, much appreciated.

I see however that in the following case, the parameter's type is properly inferred:

type MyFunction<T> = (val: T) => string

const func = <MyFunction<number>> function (val) {
  return val.toString()
}

It appears that while generic interfaces do not properly carry the inferrence, types do. Just so that I may better understand the inferrence system as it exists today, would you be able to explain why types are treated differently from interfaces in this case?

Edit: In this particular case, to make it work with the code part of the original report, I had to externalise the type definition of the attribute, and explicitly type the attribute thereafter - simply changing the interface into a type didn't work:

import * as mage from 'mage'

type ExecuteMethod<T> = (state: mage.core.IState, ...args: any[]) => Promise<T>;

type UserCommand<T> = {
    acl?: string[],
    execute: ExecuteMethod<T>
}

export = <UserCommand<string>> {
  acl: ['*'],
  execute: async function (state) {
    console.log('sample.sample state', state)
    return 'hello world'
  }
}

In this example, simply turning UserCommand into an IUserCommand interface would bring back the problem once again.

@mhegazy
Copy link
Contributor

mhegazy commented Jun 6, 2017

Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

@mhegazy mhegazy closed this as completed Jun 6, 2017
@microsoft microsoft locked and limited conversation to collaborators Jun 14, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

2 participants