I have published a proposal document that makes attempt to address an outstanding issue with type variance, that was brought and discussed at #1394 The work is currently not complete, however the idea is understood and just needs proper wording and documenting. I would like to hear feedback from the TypeScript team and community before I waste too much :). Please see the proposal here - https://github.com/Igorbek/TypeScript-proposals/tree/covariance/covariance, and below is a summary of the idea ## Problem There's a huge hole in the type system that assignability checking does not respect a contravariant nature of function input parameters: ``` ts class Base { public a; } class Derived extends Base { public b; } function useDerived(derived: Derived) { derived.b; } const useBase: (base: Base) => void = useDerived; // this must not be allowed useBase(new Base()); // no compile error, runtime error ``` Currently, [TypeScript considers input parameters _bivariant_](https://github.com/Microsoft/TypeScript-Handbook/blob/master/pages/Type%20Compatibility.md#function-parameter-bivariance). That's been designed in that way to avoid too strict assignability rules that would make language use much harder. Please see [links section](#links) for argumentation from TypeScript team. There're more problematic examples at the original discussion #1394 ## Proposal summary Please see [proposal document](https://github.com/Igorbek/TypeScript-proposals/blob/covariance/covariance/proposal.md) for details. 1. Strengthen input parameters assignability constraints from considering _bivariant_ to considering _contravariant_. 2. Introduce type variance annotations (`in` and `out`) in generic type argument positions 1. `in` annotates _contravariant_ generic type arguments 2. `out` annotates _covariant_ generic type arguments 3. `in out` and `out in` annotate _bivariant_ generic type arguments 4. generic type arguments without these annotations are considered _invariant_ 3. The annotated generic types annotated with `in` and `out` are internally represented by compiler constructed types (transformation rules are defined in the proposal) Additionally, there're a few **optional** modifications being proposed: 1. Allow type variance annotation (`in` and `out`) in generic type parameter positions to instruct compiler check for co/contravariance violations. 2. Introduce write-only properties (in addition to read-only), so that contravariant counterpart of read-write property could be extracted 3. Improve type inference system to make possible automatically infer type variance from usage ## Details Within a type definitions each type reference position can be considered as: - _covariant position_, that means for output (such as method/call/construct return types) - _contravariant position_, that means for input (such as input parameters) So that when a generic type referenced with annotated type argument, a new type constructed from the original by stripping out any variance incompatibilities: - `write(x: T): void` is removed when `T` referenced with `out` - `read(): T` is reset to `read(): {}` when `T` referenced with `in` - `prop: T` becomes `readonly prop: T` when `T` referenced with `out` - ... see more details in the proposal document ## Examples Say an interface is defined: ``` ts interface A<T> { getName(): string; // no generic parameter referenced getNameOf(t: T): string; // reference in input whoseName(name: string): T; // reference in output copyFrom(a: A<in T>): void; // explicitly set contravariance copyTo(a: A<out T>): void; // explicitly set covariance current: T; // read-write property, both input and output } ``` So that, when it's referenced as `A<out T>` or with any other annotations, the following types are actually constructed and used: ``` ts interface A<in T> { getName(): string; // left untouched getNameOf(t: T): string; // T is in contravariant position, left whoseName(name: string): {}; // T is in covariant position, reset to {} copyFrom(a: A<in T>): void; // T is contravariant already //copyTo(a: A<out T>): void; // T is covariant, removed //current: T; // T is in bivariant position, write-only could be used if it were supported } interface A<out T> { getName(): string; // left untouched //getNameOf(t: T): string; // T is in contravariant position, removed whoseName(name: string): T; // T is in covariant position, left //copyFrom(a: A<in T>): void; // T is contravariant, removed copyTo(a: A<out T>): void; // T is covariant, left readonly current: T; // readonly property is in covariant position } interface A<in out T> { // bivariant getName(): string; // left untouched //getNameOf(t: T): string; // T is in contravariant position, removed whoseName(name: string): {}; // T is in covariant position, reset to {} //copyFrom(a: A<in T>): void; // T is contravariant, removed //copyTo(a: A<out T>): void; // T is covariant, removed readonly current: {}; // readonly property is in covariant position, but type is stripped out } ``` ## Links - Original suggestion/discussion #1394 - Stricter TypeScript #274 - Suggestion to turn off parameter covariance #6102 ## Call for people @ahejlsberg @RyanCavanaugh @danquirk @aleksey-bykov @isiahmeadows