Closed
Description
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'.
Activity
MartinJohns commentedon Jun 13, 2021
It's reproducible on the playground: Playground link
Deleting the unused
Impl
class results in the error being shown.[-]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[/+][-]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[/+]RyanCavanaugh commentedon Jun 14, 2021
Simplified and cleaned up a little to make the violation more apparent
andrewbranch commentedon Jul 19, 2021
Debugging notes: variance measurement for
Parent
is getting set toVarianceFlags.Independent
, implying that its type parameter is never witnessed at all. It arrived at this conclusion by checking the assignability ofParent
instantiated with marker types. It first checks assignability in both directions with instantiations with super/sub-related marker types, andassignability appears to return true in both directions; however, it actually is returning
Ternary.Unknown
, due to being unable to answer questions about the assignability of the types'parent
andchild
properties without knowing their variances. After (incorrectly) concluding thatParent
is bivariant onA
, it checks another set of instantiations with markers that are unrelated to each other. That too comes back asTernary.Unknown
but is interpreted as true, so the variance gets updated toIndependent
, since instantiatingParent
with 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
a
andb
, which should provide some non-recursive concrete variance information. I'm also not sure ifoutofbandVarianceMarkerHandler
should have been called at some point, but it was not.andrewbranch commentedon Aug 20, 2021
@weswigham, I may need some help/advice on this one.
weswigham commentedon Aug 20, 2021
We should probably make a
Ternary.Unknown
result witnessed bygetVarianceWorker
result in aUnmeasurable
variance.21 remaining items