question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Class property type inference failure when destructuring return value of generic function

See original GitHub issue

Bug Report

🔎 Search Terms

Class Property Destructuring Type Inference Generic

🕗 Version & Regression Information

  • This is a crash: No
  • The issue occurs on the latest nightly (v4.9.0-dev.20221017), as well as on version 4.7.4. I haven’t tested on other versions, but my suspicion is that this has been a hidden bug for a while.

⏯ Playground Link

Playground link with relevant code

💻 Code


class Foo {
    constructor() {
        [this.x, this.y] = init();
    }

    x; // Any
    y; // Any
}

function init<T>() {
    return [0, ""] as [number, string];
}

🙁 Actual behavior

The x and y properties are typed as any.

🙂 Expected behavior

The x and y properties should be typed as number and string respectively.

Further investigation

If the return value of the function is captured in a local variable, and then destructured from there, everything works as expected:


class Foo {
    constructor() {
        const bar = init();
        [this.x, this.y] = bar;
    }

    x; // number
    y; // string
}

function init<T>() {
    return [0, ""] as [number, string];
}

Or, if the generic argument is omitted, everything works as expected:


class Foo {
    constructor() {
        [this.x, this.y] = init();
    }

    x; // number
    y; // string
}

function init() {
    return [0, ""] as [number, string];
}

Making use the generic type argument T does not appear to resolve the issue:


class Foo {
    constructor() {
        [this.x, this.y] = init(false);
    }

    x; // Any, should be number
    y; // Any, should be boolean
}

function init<T>(value: T) {
    return [0, value] as [number, T];
}

Issue Analytics

  • State:open
  • Created a year ago
  • Comments:8 (1 by maintainers)

github_iconTop GitHub Comments

2reactions
fatcerberuscommented, Oct 17, 2022

As an additional note: If you’re wondering why the compiler thinks T might depend on the type of x and y, it’s due to the same contextual typing mechanism that makes this work:

let x: Promise<number> = new Promise(resolve => resolve("foo"));  // error on resolve call
1reaction
fatcerberuscommented, Oct 17, 2022

For context:

'x' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.

Looks like a design limitation. Essentially x and y become part of the context for the generic call: TS wants to know T, but first it needs to know what the type of x is (because contextual typing plays into generic inference), but for that it needs to instantiate T, etc. That the return value is actually non-generic doesn’t matter because it doesn’t have a usable first-order function signature until it’s instantiated init. So the end result is you hit a circularity and the compiler gives up.

tl;dr: the type of a property is both the input and output of a generic instantiation, the compiler can’t resolve the cycle and so falls back to any

Read more comments on GitHub >

github_iconTop Results From Across the Web

Meta-issue: Use Full Unification for Generic Inference? #30134
Function type argument inference indirectly lost with generic function #29900 ... Class property type inference failure when destructuring return value of ...
Read more >
How to resolve generic type of property value from decorated ...
The decorator is basically a curried function f(x)(y) , and to fix this the compiler would have to infer the type of f...
Read more >
Documentation - More on Functions - TypeScript
Functions are the basic building block of any application, whether they're local functions, imported from another module, or methods on a class.
Read more >
Deconstructing tuples and other types | Microsoft Learn
The following example uses type inference when deconstructing the three-tuple returned by the QueryCityData method.
Read more >
Type Inference - TypeScript Deep Dive - Gitbook
This is an example of types flowing from left to right. The same assignment style type inference works if you create a function...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found