- Notifications
You must be signed in to change notification settings - Fork 13.2k
Description
(It's a question as well as a suggestion)
Update: a proposal #10717
I've supposed that for structural type system as TypeScript is, type variance isn't applicable since type-compatibility is checked by use.
But when I had read @RyanCavanaugh 's TypeScript 1.4 sneak peek (specifically 'Stricter Generics' section) I realized that there's some lack in this direction by design or implementation.
I wondered this code is compiled:
functionpush<T>(arr: T[],a: T){arr.push(a);}varns=[1];push<{}>(ns,"a");// ok, we added string to number[];More clear code:
interfaceA{a: number;}interfaceBextendsA{b: number;}interfaceCextendsB{c: number;}vara: A,b: B,c: C;varas: A[],bs: B[];as.push(a);// okas.push(b);// ok, A is contravariant here (any >=A can be pushed) bs.push(a);// error, B is contravariant here, so since A<B, A cannot be pushed -- fair bs.push(b);// okbs.push(c);// ok, C>=Bas=bs;// ok, covariance used?as.push(a);// ok, but actually we pushed A to B[]How could B[] be assignable to A[] if at least on member push is not compatible. For B[].push it expects parameters of type B, but A[].push expects A and it's valid to call it with A.
To illustrate:
varfa: (a: A)=>void;varfb: (b: B)=>void;fa(a);fa(b);fb(a);// error, as expectedfa=fb;// no errorfa(a);// it's fb(a)Do I understand it correctly that is by design?
I don't think it can be called type-safe.
Actually, such restriction that could make B[] to be unassignable to A[] isn't desirable.
To solve it I suggest to introduce variance on some level (variable/parameter, type?).
Syntax
varas: A[];// no variancevarbs: outB[];// covariant, for "read"<outA[]>bs;// ok, covariance used<A[]>bs;// same is before, out should be inferred(<A[]>bs)[0];// means, allow to get covariant type(<A[]>bs).push(a);// means, disallow to pass covariant type<inA[]>bs;// failsfunctionpush<T>(data: inT[],val: outT): void{data.push(val);}push(animals,dog);// allowed, T is Animalpush(dogs,animal);// disallow, T can't be inferred// In oppositefunctionfind<T>(data: outT[],val: outT): bool{ ... }// allow only get from datafind(animals,dog);// allowedfind(cats,dog);// allowed T can be inferred as MammalI'm not sure where variance should be applied - to variable or type?
Looks like it closer to variable it self, so the syntax could be like:
varina: number[];function<T>(ina: T[],outb: T): { ... }Questions to clarification
- inference variance from usage (especially for functions)
- is it applicable for types (like C# uses for interfaces/delegates)
- how to describe variance on member-level (does it need?)
- can variable be
in out(fixed type)? - can variable be neither
innorout(open for upcast/downcast)?
So this topic is a discussion point.