Skip to content

Assignment to type alias containing T[keyof T] property does not respects variance #32311

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
smoogly opened this issue Jul 9, 2019 · 2 comments
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@smoogly
Copy link

smoogly commented Jul 9, 2019

TypeScript Version: 3.6.0-dev.20190709

Search Terms: generic variance, tuple overload, T[keyof T], generic assignment

Code

class A { public a = 1; }
class B extends A { public b = 1; }

type Mapped<T> = {a: T};
type Values<T> = {a: T[keyof T]};
// Values<Mapped<T>> is {a: T}

declare const b: B;
const a: A = b; // Ok

declare const mappedB: Mapped<B>;
const mappedA: Mapped<A> = mappedB; // Ok

declare const valueOfMappedB: Values<Mapped<B>>;
const valueOfMappedA: Values<Mapped<A>> = valueOfMappedB; // Fails, expected to be Ok
/*
Type 'Values<Mapped<B>>' is not assignable to type 'Values<Mapped<A>>'.
  Type 'Mapped<A>' is not assignable to type 'Mapped<B>'.
    Property 'b' is missing in type 'A' but required in type 'B'.
 */

// But
const tst: {a: A} = valueOfMappedB;

// Also
type StraightupValues<T> = T[keyof T];
declare const straightupValueOfMappedB: StraightupValues<Mapped<B>>;
const straightupMappedA: StraightupValues<Mapped<A>> = straightupValueOfMappedB; // Ok


// Practical example: combine array of Sometype into Sometype of array.
type Sometype<T> = { toList(): Array<T[keyof T]> };

function combine<T1>(input: [Sometype<T1>]): Sometype<[T1]>; // Fails, because any[] is incorrectly checked against [T1]
function combine<T1, T2>(input: [Sometype<T1>, Sometype<T2>]): Sometype<[T1, T2]>;
function combine<T1, T2, T3>(input: [Sometype<T1>, Sometype<T2>, Sometype<T3>]): Sometype<[T1, T2, T3]>;
function combine<T>(input: Array<Sometype<T>>): Sometype<T[]>;
function combine(input: Array<Sometype<any>>): Sometype<any[]> {
    return null as any;
}

Expected behavior:
No error.

Actual behavior:
Error.

Playground Link:
Playground

Related Issues:
Not sure. Maybe #31659?

@jack-williams
Copy link
Collaborator

jack-williams commented Jul 13, 2019

This is a design limitation in variance analysis. The analysis marks the type parameter T in Values as invariant because it occurs in a positive position (the indexer type) and a negative position (the keyof type).

What is missing in the analysis is that the same type parameter is composed in the form T[keyof T] which is actually just covariant (probably due to some Galois connection), but looking for specific forms like this breaks compositional type-checking.

It's technically wrong but correcting this is probably going to be costly. There are many subtle interactions caused by variance analysis and I wonder whether these should be documented in the new handbook. (Not the subtle interactions, but the fact that variance analysis is a thing and may cause aliases to fail checking)

@fatcerberus
Copy link

@jack-williams It's interesting from a theoretical POV that T[keyof T] makes T covariant. Even if we read T[keyof T] as a single unary type operator propof T, T still occurs in negative position. I would have expected contravariance, if anything.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

4 participants