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

Generic type mysteriously fallback to base type #9319

Closed
unional opened this issue Jun 22, 2016 · 6 comments
Closed

Generic type mysteriously fallback to base type #9319

unional opened this issue Jun 22, 2016 · 6 comments
Labels
Duplicate An existing issue was already created

Comments

@unional
Copy link
Contributor

unional commented Jun 22, 2016

[x] Many common issues and suggestions are addressed in the FAQ
https://github.com/Microsoft/TypeScript/wiki/FAQ
[x] Search for duplicates before logging new issues (tried my best, couldn't find one)
https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93&q=is%3Aissue
[x] Questions are best asked and answered at Stack Overflow
http://stackoverflow.com/questions/tagged/typescript

TypeScript Version:

(1.9.0-dev.20160622)

Code

interface ReduxAction {
  type: string;
}

interface Action<T extends string> extends ReduxAction {
  type: T;
};
function createActioner<
  A extends ReduxAction,
  T extends (...args: any[]) => A>(type: string, create: T) {
  return {
    create,
    cast(action: ReduxAction): A {
      return action as A;
    }
  };
}

// then...
interface TestAction extends Action<'TEST'> {
  message: string;
}
const actual = createActioner('TEST', (message: string): TestAction => {
  return {
    type: 'TEST',
    message
  };
});

const action = actual.create('abcd');
const otherAction = { type: 'TEST', message: 'pqr' };
actual.cast(action); // <--- here
actual.cast(otherAction);

Expected behavior:
actual.cast(action) returns generic type A

Actual behavior:
actual.cast(action) returns type ReduxAction

What's even more strange is in the source code, it is detected correctly:
image

But in the consuming side, it is not:
image

@unional
Copy link
Contributor Author

unional commented Jun 22, 2016

It seems like the (message: string) => TestAction somehow inferred A as Action (ReduxAction) rather than TestAction

@unional
Copy link
Contributor Author

unional commented Jun 23, 2016

For reference, why I have T extends (...args: any[]) => A because the create() signature can be inferred in the call-site, so that:
image

An alternative usage that works would look like this:

const actual = createActioner
  <TestAction, (message: string) => TestAction>('TEST', (message: string) => {
  return {
    type: 'TEST',
    message
  };
});

Is there a way to indicate T to be inferred?

@RyanCavanaugh
Copy link
Member

Can you make the example self-contained and more clearly indicate what invocation of cast you're referring to?

@unional
Copy link
Contributor Author

unional commented Jun 23, 2016

Do you mean not importing redux? Updated the code above.
cast() is the actual.cast(action)

EDIT: update cast call.

@sandersn
Copy link
Member

The root problem is that T's extends constraint refers to A. Then calls to createActioner would have to infer A by inferring from T's constraint. However, TypeScript doesn't support inferring type parameters whose constraints refer to other type parameters.

This is a duplicate of #7234, which gives the workaround: infer from an intersection of type parameters instead:

function createActioner<A extends ReduxAction, T>(
    type: string, 
    create: T & ((...args: any[]) => A)) {
  return {
    create,
    cast(action: ReduxAction): A {
      return action as A;
    }
  };
}

Now calls to createActioner can check that create is a function that returns A (inferring A) and also return the rest of the type as T & ((...args: any[]) => A).

@sandersn sandersn added the Duplicate An existing issue was already created label Jun 23, 2016
@unional
Copy link
Contributor Author

unional commented Jun 23, 2016

Nice! Thanks for your excellent work. 👍

@microsoft microsoft locked and limited conversation to collaborators Jun 19, 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

3 participants