Description
Variance and Variance Annotations on Type Parameters
(continued from #48209)
type Foo<T> = {
x: T;
f: FooFn<T>;
};
type FooFn<T> = (foo: Bar<T[]>) => void;
type Bar<T> = {
value: Foo<T[]>;
};
// Try this in TypeScript 4.6.
// Whether or not you see an error on 'fn2 = fn1'
// depends on whether or not Section (B) comes before Section (A)
// Section (A)
declare let foo1: Foo<string>;
declare let foo2: Foo<unknown>;
foo1 = foo2;
foo2 = foo1;
// Section (B)
declare let fn1: FooFn<string>;
declare let fn2: FooFn<unknown>;
fn1 = fn2;
fn2 = fn1;
-
Could handle these better, but would break code.
-
Idea: variance annotations
-
Compare
T
by covariant rules:type Foo<out T> = { x: T; f: FooFn<T>; };
-
Compare
T
by contravariant rules:type Foo<in T> = { x: T; f: FooFn<T>; };
-
Compare
T
by invariant rules:type Foo<in out T> = { x: T; f: FooFn<T>; };
-
-
Can help with documentation, correctness, perf.
- Perf results? 10-20% improvements because we have to measure variance.
- This short-circuits any circularity.
-
Existing code that doesn't use these gets nothing out of it.
- Should we emit these annotations in
.d.ts
files if we can accurately measure?- We can come up with states like bivariant, unmeasurable, and independent. Won't try to represent those. But for some subset (i.e. covariant, contravariant, invariant), we could do that.
- But introducing these in declaration files would be a breaking change for downstream
.d.ts
consumers! - Start out by not doing this unless a user explicitly specified it.
- Should we emit these annotations in
-
Would be nice if we could surface this information in quick info though.
-
Interface merging combines the variance annotations.
- Does that mean that interface emerging can invalidate the annotation of another interface?
- Yes, but this was always the case. The compiler will issue an error and you'll manually have to fix that up.
- Does that mean that interface emerging can invalidate the annotation of another interface?
-
Variance annotations on generic signatures we don't allow - doesn't make sense.
- Doesn't it? We do a lot of work on this sort of thing.
-
Error messages?
- Could special-case the last part.
- Avoid saying "covariant" or "contravariant" - "input position" and "output position".
- We report based on the marker types, using
Super
andSub
as the displayed names.- Those could be real names.
-
Who would benefit the most from the perf aspect here?
- xstate, lodash, immutable, Ember?
- Could use
typesVersions
to just give an instant perf boost to 4.7 users if this ships by then. - We don't have a way to measure "how much time is being spent in variance"
-
Maybe urge caution.
- Lots of stuff that stops working if you start marking everything as
in out
(invariant).
- Lots of stuff that stops working if you start marking everything as
-
Do we feel okay about the fact that two instantiations of the same type might be incompatible but structurally identical types would be compatible?
- Yes-ish.
- Usually the names of types are not material.
- Really means we have to message this well.
- Invariant is the only place where you can throw off how the type-checker is usually used.
- Well if you say co/contravariant when we would've otherwise measured bivariant.
in out
future-proofs the API - kinda nice.
- Yes-ish.
-
So declaration emit?
- No, not automatically. Breaking change for older versions.
-
This will force us to create a 4.7-based rift on DefinitelyTyped.
typesVersions
?- If you use JSDoc, it's ignored by older versions.
- Out of band file?
-
Should we have some sort of support for JSDoc here?
-
How does this play with "Types as Comments"?
- Doesn't affect it, the proposal ignores content between
<
and>
- Doesn't affect it, the proposal ignores content between
-
JSDoc for variance annotations?
- It might be the case that we need to lean on JSDoc for
public
,private
,protected
. - So
in
andout
.
- It might be the case that we need to lean on JSDoc for
-
Our node factory APIs would need to take a breaking change.
- We've done this, but people understandably are unhappy about it.
- Optional parameters? Arity check won't work.
NodeFlags
?- Overload that checks the first argument, and maybe deprecates the old overload?
-
Back to syntax!
- So actual keywords?
- Minimally yes, but also indecisive about back-compat and allowing
/** @in @out */
- Minimally yes, but also indecisive about back-compat and allowing
- So actual keywords?