Repository navigation
Generic interface should not be invariant when type arg only used in parameter list of function property #32674
Description
Activity
- addedNeeds InvestigationThis issue needs a team member to investigate its status.This issue needs a team member to investigate its status.
on Aug 5, 2019 jack-williams commented
on Aug 6, 2019 CollaboratorMore actionsI think this might be a duplicate / related to #32311.
Is this the same or a separate issue:
interface Fn<A, B> { (a: A): B; done: (fn: (b: B) => void) => Fn<A, void>; } declare const f: Fn<string, number>; const g: Fn<"a", number> = f; // error!? // Type 'Fn<string, number>' is not assignable to type 'Fn<"a", number>'. // Type 'string' is not assignable to type '"a"'.(2322) // ... but why is Fn<A, B> not contravariant in A?
jack-williams commented
on Dec 19, 2019 CollaboratorMore actionsI think that might be separate because the issue you post is probably related to recursive types. My guess as to what is going on there is:
- Measuring variance for
Fn<A, B>observesAas contravariant from the function input. - The recursive instantiation
Fn<A, void>has a different type id toFn<A, B>so it doesn't get related using the recursive assumption. RelatingFn<A, void>toFn<A, void>(whereAis a marker) probable hits the following code path:
// When variance information isn't available we default to covariance. This happens // in the process of computing variance information for recursive types and when // comparing 'this' type arguments.
- The effect of 2 is to mark
Aas covariant, and in conjuction with 1, marksAas invariant.
Reacted by Joe Calzaretta- Measuring variance for
Thanks. I wonder if this warrants a brand new issue or should be added as a curiosity onto #1394 for a case where an explicit contravariance annotation would be useful.
jack-williams commented
on Dec 19, 2019 CollaboratorMore actionsI think this warrants a new issue, at least for future referencing. The core problem is that
Fn<A, B>gets marked with a special marker flag (to ensure structural checking) butFn<A, void>does not.The closest duplicate is probably #33872, but that was a specific regression.
- addedRescheduledThis issue was previously scheduled to an earlier milestoneThis issue was previously scheduled to an earlier milestone
on Aug 31, 2020 Here's a simpler repro:
// Covariant because T[keyof T] has no alias symbol and we always relate structurally type A<T> = T[keyof T]; declare let a1: A<{ a: bigint }>; declare let a2: A<{ a: bigint | null }>; a1 = a2; // Error a2 = a1; // Invariant because of variance measurement for B<T> type B<T> = { prop: T[keyof T] }; declare let b1: B<{ a: bigint }>; declare let b2: B<{ a: bigint | null }>; b1 = b2; // Error b2 = b1; // Error
We consider
T[keyof T]to be invariant inT. Ideally we should consider it contravariant, but as outlined in #32311 there are reasons we can't. So, variance measurements forTend up being too conservative, andT[keyof T]becomes a device for revealing when we perform variance measurements. In the example above, it reveals that we don't measure variance forA<T>because we can't attach a type alias to an indexed access type. However, we do measure variance forB<T>and thus end up being more conservative.It's a tough problem to solve. If type relations were fully evaluated structurally at all times we'd get the expected results, but we'd also have horrible performance (we've tried, it's not pretty). So, not sure there's much we can do here.
Reacted by AnyhowStep- addedDesign LimitationConstraints of the existing architecture prevent this from being fixedConstraints of the existing architecture prevent this from being fixedand removedNeeds InvestigationThis issue needs a team member to investigate its status.This issue needs a team member to investigate its status.RescheduledThis issue was previously scheduled to an earlier milestoneThis issue was previously scheduled to an earlier milestone
on Oct 25, 2020 Actually, let me clarify a bit.
T[keyof T]is sometimes covariant, sometimes contravariant, and sometimes invariant, depending on howTvaries. For example- for a supertype
{ a: string | number }and a subtype{ a: string },T[keyof T]is covariant, but - for a supertype
{ a: string }and a subtype{ a: string, b: boolean },T[keyof T]is contravariant, and - for a supertype
{ a: string | number }and a subtype{ a: string, b: boolean },T[keyof T]is invariant.
So, the only safe thing to assume is that
T[keyof T]is invariant, but it may indeed be too conservative at times.Reacted by AnyhowStep and Oleg ValterReacted by SlurpTheo- for a supertype
If we could manually annotate type params with a particular variance in the future, would that solve this problem?
Or would we run into cases where it would be too expensive to check that the annotated variance is correct?
TypeScript Version: 3.5.1
Search Terms:
generic interface, invariant, contravariant, type arg, parameter list, function property
Code
Expected behavior:
This should be allowed,
Actual behavior:
Playground Link: Playground
Related Issues: