Skip to content

Generic inference different between equivalent function expression and arrow function expression in object literal #38845

Closed

Description

TypeScript Version: Nightly

Search Terms: parameter, inference, generic, function expression, arrow function expression

Expected behavior:

In function b, parameter a should be inferred as a: () => 42.

Actual behavior:

When using function expression instead of arrow function expression, parameter a is inferred as a: unknown.

Related Issues: #32230

Code

export declare function foo<A>(
    options: {
        a: A
        b: (a: A) => void;
    }
): void;

foo(
    {
        a: () => { return 42 },
        b(a) {}, // a: () => 42
    }    
);

foo(
    {
        a: function () { return 42 },
        b(a) {}, // a: unknown
    }    
);

foo(
    {
        a() { return 42 },
        b(a) {}, // a: unknown
    }    
);
Output
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
foo({
    a: () => { return 42; },
    b(a) { },
});
foo({
    a: function () { return 42; },
    b(a) { },
});
foo({
    a() { return 42; },
    b(a) { },
});
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

Activity

  1. Nathan-Fenner commented on May 29, 2020

    @Nathan-Fenner
    Contributor

    Just to add another data-point to the strangeness, if b is an arrow function or an anonymous function expression instead of a method-shorthand, it also gets the () => 42 type inferred for its argument:

    foo(
        {
            a: () => 42,
            b: (a) => {}, // a: () => 42
        }
    )
    foo(
        {
            a: () => 42,
            b: function(a) {}, // a: () => 42
        }
    )

    so this suggests that the inference problem also has to do with how b is defined, not just a

  2. fsenart commented on May 29, 2020

    @fsenart
    Author

    While experimenting around this problem, I found out another strange behavior that I suspect to be related to this one:

    export declare function foo<C, A>(options: {
        c: C;
        a: (c: C) => A;
        b: (b: A) => void;
    }): void;
    
    // a(): no parameter -> return type can be inferred
    foo({
        c: 42,
        a: () => {}, // a: (c: number) => void
        b(b) {}, // b: (b: void) => void
    });
    
    // a(): a parameter (with inferred type) -> return type cannot be inferred
    foo({
        c: 42,
        a: (c) => {}, // a: (c: number) => unknown
        b(b) {}, // b: (b: unknown) => void
    });
    
    // a(): a parameter (with explicit type) -> return type can be inferred
    foo({
        c: 42,
        a: (c:number) => {}, // a: (c: number) => void
        b(b) {}, // b: (b: void) => void
    });

    However, if function a() is the "last link in the chain", then everything works as expected:

    export declare function foo<C, A>(options: {
        c: C;
        a: (c: C) => A;
    }): void;
    
    // a(): no parameter -> return type can be inferred
    foo({
        c: 42,
        a: () => {}, // a: (c: number) => void
    });
    
    // a(): a parameter -> return type can be inferred
    foo({
        c: 42,
        a: (c) => {}, // a: (c: number) => void
    });
  3. RyanCavanaugh commented on Jun 12, 2020

    @RyanCavanaugh
    Member

    Anders Hejlsberg (@ahejlsberg) I tried to reason about the context-sensitiveness of this and failed. Can you weigh in, or is this just a bug?

  4. ahejlsberg commented on Jun 30, 2020

    @ahejlsberg
    Member

    This is a design limitation. Similar to #38872. A arrow function with no parameters is not context sensitive, but a function expression with no parameters is context sensitive because of the implicit this parameter. Anything that is context sensitive is excluded from the first phase of type inference, which is the phase that determines the types we'll use for contextually typed parameters. So, in the original example, when the value for the a property is an arrow function, we succeed in making an inference for A before we assign a contextual type to the a parameter of b. But when the value is a function expression, we make no inferences and the a parameter is given type unknown.

  5. added
    Design LimitationConstraints of the existing architecture prevent this from being fixed
    and removed
    Needs InvestigationThis issue needs a team member to investigate its status.
    on Jun 30, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Design LimitationConstraints of the existing architecture prevent this from being fixedFix AvailableA PR has been opened for this issue

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions