Repository navigation
Check order dependence with mutually-recursive non-unary generics #44572
Description
Activity
MartinJohns commented
on Jun 13, 2021 ContributorMore actionsUnfortunately, this only occurs across several files, so I can't link to the playground.
It's reproducible on the playground: Playground link
Deleting the unused
Implclass results in the error being shown.Reacted by Erik Brinkman- changed the title
[-]Typescript passing incorrect assignability check in very strange circumstance.[/-][+]Adding an implementation of an interface allows an invalid assignability check of the interface to pass[/+]on Jun 13, 2021 - changed the title
[-]Adding an implementation of an interface allows an invalid assignability check of the interface to pass[/-][+]Check order dependence with mutually-recursive non-unary generics[/+]on Jun 14, 2021 RyanCavanaugh commented
on Jun 14, 2021 MemberMore actionsSimplified 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;
Reacted by Erik Brinkman and Maxime Richardandrewbranch commented
on Jul 19, 2021 MemberMore actionsDebugging notes: variance measurement for
Parentis getting set toVarianceFlags.Independent, implying that its type parameter is never witnessed at all. It arrived at this conclusion by checking the assignability ofParentinstantiated with marker types. It first checks assignability in both directions with instantiations with super/sub-related marker types, and
assignability appears to return true in both directions; however, it actually is returningTernary.Unknown, due to being unable to answer questions about the assignability of the types'parentandchildproperties without knowing their variances. After (incorrectly) concluding thatParentis bivariant onA, it checks another set of instantiations with markers that are unrelated to each other. That too comes back asTernary.Unknownbut is interpreted as true, so the variance gets updated toIndependent, since instantiatingParentwith all kinds of different markers with different assignability to each other apparently had no effect on the instantiations' assignability to each other.I'm not sure if any of those comparisons ever actually looked at
aandb, which should provide some non-recursive concrete variance information. I'm also not sure ifoutofbandVarianceMarkerHandlershould have been called at some point, but it was not.- addedRescheduledThis issue was previously scheduled to an earlier milestoneThis issue was previously scheduled to an earlier milestone
on Aug 20, 2021 andrewbranch commented
on Aug 20, 2021 MemberMore actionsWesley Wigham (@weswigham), I may need some help/advice on this one.
We should probably make a
Ternary.Unknownresult witnessed bygetVarianceWorkerresult in aUnmeasurablevariance.4 remaining items
Andarist commented
on Feb 17, 2022 ContributorMore actionsI'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.
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.20200117and3.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: #36261I 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: #45859 (I would have to confirm this when this one here gets fixed)
Reacted by whzx5byb and Alexey Berezin- addedDesign LimitationConstraints of the existing architecture prevent this from being fixedConstraints of the existing architecture prevent this from being fixedand removedBugA bug in TypeScriptA bug in TypeScriptFix AvailableA PR has been opened for this issueA PR has been opened for this issueRescheduledThis issue was previously scheduled to an earlier milestoneThis issue was previously scheduled to an earlier milestone
on Mar 13, 2022 - added 3 commits that reference this issue
on Feb 10, 2026
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
It's still present in all versions including nightly.
⏯ Playground Link
Playground link with relevant code
Thanks Martin Johns (@MartinJohns) for the link
💻 Code
The final assignment should not pass. It accurately errors in 3.7.5, if the
Implclass is removed (or any aspect of theiterimplementation is modified), or by adding a sentinel type likesentinel?: Ato theParentinterface to aid in type checking.🙁 Actual behavior
No error is thrown in the final assignment.
🙂 Expected behavior
A n error is throw: