Skip to content

Check order dependence with mutually-recursive non-unary generics #44572

Closed
@erikbrinkman

Description

@erikbrinkman

Bug Report

Adding an implementation of an interface allows an invalid assignability check of the interface to pass

🔎 Search Terms

assignability, unnecessary implementation

🕗 Version & Regression Information

  • This changed between versions 3.7.5 and 3.8.3

It's still present in all versions including nightly.

⏯ Playground Link

Playground link with relevant code

Thanks @MartinJohns for the link

💻 Code

interface Parent<A, B> {
  getChild(): Child<A, B>;

  iter(): Iterable<Parent<A, B>>;
}

interface Child<A, B>
  extends Parent<A, B> {
  readonly a: A;
  readonly b: B;
}

class Impl<A, B> implements Parent<A, B> {
  constructor(readonly child: Child<A, B>) {
  }

  getChild(): Child<A, B> {
    return this.child;
  }

  *iter(): Iterable<Parent<A, B>> {
    const map = new Map<Child<unknown, unknown>, Child<A, B>[]>();

    function* gen(
      inp: Child<A, B>
    ): Iterable<Child<A, B>> {
      yield* map.get(inp) || [];
    }
  }
}

const x: Parent<unknown, unknown> = {} as any;
const _: Parent<null, unknown> = x; // should not pass

The final assignment should not pass. It accurately errors in 3.7.5, if the Impl class is removed (or any aspect of the iter implementation is modified), or by adding a sentinel type like sentinel?: A to the Parent interface to aid in type checking.

🙁 Actual behavior

No error is thrown in the final assignment.

🙂 Expected behavior

A n error is throw:

Type 'Parent<unknown, unknown>' is not assignable to type 'Parent<null, unknown>'.
  Type 'unknown' is not assignable to type 'null'.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Design LimitationConstraints of the existing architecture prevent this from being fixed

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions