Skip to content

Generic function argument lost when passed to a generic identity function #38007

Closed
@invliD

Description

@invliD

TypeScript Version: 3.8.3

Search Terms:

  • generic function
  • "instantiated with a different subtype of constraint"

Expected behavior:
In TypeScript 3.7.5, the function returned by define is of type <E>(argument: E, somethingElse: { key: E; }) => (a: string) => number.

Actual behavior:
In TypeScript 3.8.3, the function returned by define is of type (argument: E, somethingElse: { key: E; }) => (a: string) => number.

I believe this behavior is a regression, since a function with generic argument types without itself being generic doesn't really make sense, and the coded did work with TS 3.7. My assumption here is that TS doesn't understand the two Es actually must be the same type.

Code

// In reality there are more arguments of an Action and they have complex types
type Action<R> = (a: string) => R;

// The purpose of this method is to allow definitions of action creators without explicitly typing an
// Action's argument types every time
function define<C extends (...args: any[]) => Action<any>>(creator: C): C {
    return creator;
}

// Type inferred in TS 3.7.5: <E>(argument: E, somethingElse: { key: E; }) => (a: string) => number
// Type inferred in TS 3.8.3: (argument: E, somethingElse: { key: E; }) => (a: string) => number
const definedAction = define(
    <E>(
        argument: E,
        somethingElse: { key: E }
    ) => (a /* a has a properly inferred type */) => 5,
);

const extra = { test: "" };

definedAction(extra, { key: extra });
/*
Error only in TS 3.8.3:
Argument of type '{ test: string; }' is not assignable to parameter of type 'E'.
  '{ test: string; }' is assignable to the constraint of type 'E', but 'E' could be instantiated with a different subtype of constraint '{}'.(2345)
*/
Output
"use strict";
// The purpose of this method is to allow definitions of action creators without explicitly typing an
// Action's argument types every time
function define(creator) {
    return creator;
}
// Type inferred in TS 3.7.5: <E>(argument: E, somethingElse: { key: E; }) => (a: string) => number
// Type inferred in TS 3.8.3: (argument: E, somethingElse: { key: E; }) => (a: string) => number
const definedAction = define((argument, somethingElse) => (a /* a has a properly inferred type */) => 5);
const extra = { test: "" };
definedAction(extra, { key: extra });
/*
Error only in TS 3.8.3:
Argument of type '{ test: string; }' is not assignable to parameter of type 'E'.
  '{ test: string; }' is assignable to the constraint of type 'E', but 'E' could be instantiated with a different subtype of constraint '{}'.(2345)
*/
Compiler Options
{
  "compilerOptions": {
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictPropertyInitialization": true,
    "strictBindCallApply": true,
    "noImplicitThis": true,
    "noImplicitReturns": true,
    "useDefineForClassFields": false,
    "alwaysStrict": true,
    "allowUnreachableCode": false,
    "allowUnusedLabels": false,
    "downlevelIteration": false,
    "noEmitHelpers": false,
    "noLib": false,
    "noStrictGenericChecks": false,
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "esModuleInterop": true,
    "preserveConstEnums": false,
    "removeComments": false,
    "skipLibCheck": false,
    "checkJs": false,
    "allowJs": false,
    "declaration": true,
    "experimentalDecorators": false,
    "emitDecoratorMetadata": false,
    "target": "ES2017",
    "module": "ESNext"
  }
}

Playground Link: Provided

Related issues:

Metadata

Metadata

Assignees

Labels

DuplicateAn existing issue was already created

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions