Skip to content

Instances of generic interfaces are incompatible because their generic arguments are incompatible; even when two instances in question are structurally identical #61100

Closed as not planned
@fictitious

Description

@fictitious

🔎 Search Terms

"type relationships" "type compatibility" instances generic interface "type parameter" measuring variance

🕗 Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about type compatibility for generics

⏯ Playground Link

https://www.typescriptlang.org/play/?ts=5.7.3#code/JYOwLgpgTgZghgYwgAgCoAtQHMCMyDeAUMicgG5wA2ArhAFzIDOYU2A3IQL6GGiSyIUGbACYCxUhRr0mLdhJIQAHizgNmrEFg7de4aPCTIA6lDgAHc9AA8wrcmWQQAE0YEptBtRABrEAHsAdxBOAD5xUnIqTzRMLQBtAHIPCESAXR0eZwgESjgoFAR-EGZkMDjcBlMLKyhbCpxQjiKSsDKKkSqzSxs7LBFwgF527BwOHjAATytkAHF-f2dqnrq+hxUIFzd8FK9fAOCw5GH8TmQAMgjJaJk+pJT0nXHs3PzC4tKsBec+nAZ5xbLWr1UZNQgtT7fPqdObfIG9DpDZBfRa-DhAA

💻 Code

interface Thing1 {
    value: string;
}

interface Thing2 {
    value: string;
    extra: string;
}

interface Wrapper<Thing extends {value: unknown}> {
    value: Thing['value'];
}

declare const thing1: Wrapper<Thing1>;
const thing2: Wrapper<Thing2> = thing1;

🙁 Actual behavior

Type 'Wrapper' is not assignable to type 'Wrapper'.
Property 'extra' is missing in type 'Thing1' but required in type 'Thing2'.

extra is not used at all in the definition of Wrapper, but somehow it affects the compatibility between different instantiations of Wrapper.

🙂 Expected behavior

No error, Wrapper<Thing1> and Wrapper<Thing2> are structurally identical and should ideally be the same type internally.

Additional information about the issue

The error does not happen if Wrapper is defined as an intersection with a dummy {} type:

type GoodWrapper<Thing extends {value: unknown}> = {} & {
    value: Thing['value'];
};

declare const goodThing1: GoodWrapper<Thing1>;
const goodThing2: GoodWrapper<Thing2> = goodThing1; // ok

The only similar issue that I managed to find is Generic interfaces flagged incompatible, as if nominal typing [fixed], which does not produce an unexpected error any more, but also is marked "Working as intended". The explanation is that it's a result of an optimization, and "This optimization depends on types being sensible".

If this is indeed a result of the optimization which is suppressed by using an intersection type, the question is how reliable that suppression is.

Also, I don't see anything not sensible in the example code - in the real code, types Thing1 and Thing2 are defined in the user code, and the Wrapper is defined in the library that only cares about one particular aspect of things.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Not a DefectThis behavior is one of several equally-correct options

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions