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.

Check order dependence with mutually-recursive non-unary generics

See original GitHub issue

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'.

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:3
  • Comments:9 (9 by maintainers)

github_iconTop GitHub Comments

2reactions
Andaristcommented, Feb 17, 2022

Iā€™ve spent the last 1.5 days debugging something that looks awfully like this one. This is way beyond my head and the understanding of the type system so I canā€™t fully assess if my issue is exactly the same or just similar. Therefore Iā€™m going to post it, for the time being, to avoid creating a new issue that might be a duplicate of this one.

TS playground

code from the playground
declare class StateNode<TContext, TEvent extends { type: string }> {
  // if I comment out this property then error is never reported
  _storedEvent: TEvent
  // if I comment out this property then error is always reported
  _action: ActionObject<TEvent>
  // if I comment out this property then error is always reported
  _state: StateNode<TContext, any>;
}

interface ActionObject<TEvent extends { type: string }> {
  exec: (meta: StateNode<any, TEvent>) => void
}

export declare function createMachine<
  TEvent extends { type: string }
>(action: ActionObject<TEvent>): StateNode<any, any>

export declare const execute: <TEvent extends { type: string }>(
  handler: (event: TEvent) => any
) => ActionObject<TEvent>;

export declare function interpret<TContext>(
  machine: StateNode<TContext, any>
): void

// uncomment to get a correct report in the `createMachine` call below
// const test: ActionObject<{ type: "PLAY"; value: number } | { type: "RESET" }> =
//   {} as ActionObject<{
//     type: "PLAY";
//     value: number;
//   }>;

const machine = createMachine({} as any);

// comment out to get a correct report in the `createMachine` call below
interpret(machine);

createMachine<{ type: "PLAY"; value: number } | { type: "RESET" }>(
  execute((ev: { type: "PLAY"; value: number }) => {
    console.log(ev);
  })
);

Iā€™ve also found out that this has changed between 3.7 and 3.8. More accurately - between 3.8.0-dev.20200117 and 3.8.0-dev.20200118. If only Iā€™ve tracked down correctly from which commits those two were built then the behavior change is related to this diff: https://github.com/microsoft/TypeScript/compare/e2e1f6fd85b5076186ba6ee68fe59e11ccac1d3f..afa11d3c7ac37c49fc97230a897e4208ee132ae4 Given this diff, the change was introduced in this PR: https://github.com/microsoft/TypeScript/pull/36261

I know that at this point Iā€™m probably just reiterating what you probably already know but since Iā€™ve already done this detective work Iā€™m posting this here as a reference, just in case.

I also have a hunch that another issue that Iā€™ve posted some months ago might be related to this one: https://github.com/microsoft/TypeScript/issues/45859 (I would have to confirm this when this one here gets fixed)

2reactions
RyanCavanaughcommented, Jun 14, 2021

Simplified and cleaned up a little to make the violation more apparent

interface Parent<A> {
  child: Child<A> | null;
  parent: Parent<A> | null;
}

interface Child<A, B = unknown> extends Parent<A> {
  readonly a: A;
  // This field isn't necessary to the repro, but the
  // type parameter is, so including it
  readonly b: B;
}

function fn<A>(inp: Child<A>) {
  // This assignability check defeats the later one
  const a: Child<unknown> = inp;
}

// Allowed initialization of pu
const pu: Parent<unknown> = { child: { a: 0, b: 0, child: null, parent: null }, parent: null };
// Should error
const notString: Parent<string> = pu;
// Unsound read on .child.a
const m: string = notString.child!.a;
Read more comments on GitHub >

github_iconTop Results From Across the Web

Investigations in intersection types:
14.29 Constraint solving rules to also handle non-unary type constructor ... In order to solve this problem, Russell [121] defined a theory.
Read more >
Fundamental Approaches to Software Engineering
Integrating Topological Proofs with Model Checking to Instrument ... Automated Generation of Consistent Graph Models with First-Order Logic.
Read more >
Efficiency Three Ways - School of Computer Science
checking dependent types is intricate, and, in the general case, undecidable. In short, this is because dependent types remove the isolation between valuesĀ ......
Read more >
Information Technologyā€” Z Formal Specification Notationā€” Syntax ...
its decidable type system, which allows some well-formedness checks on a ... first word preceded by expression of non-unary function or generic.
Read more >
Fundamental Approaches to Software ... - OAPEN Library
Integrating Topological Proofs with Model Checking to Instrument ... Note that ordering is dependent on the run time type. ... Java generics. Dyer...
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