Skip to content

Surprising/incorrect composition of generic type functions (when one is a mapped type) #17908

@jcalz

Description

@jcalz

When trying to compose generic type aliases I've run into some instances where the compiler breaks the intended semantics. Here is as small/self-contained an example as I can come up with:

TypeScript Version: 2.4.0 / nightly (2.5.0-dev.20170627)

Code

// Let's define some generic type aliases:

// union of values of T:
type Valueof<T> = T[keyof T];

// mapped keys of properties:
type MappedKeyof<T> = {
  [K in keyof T]: keyof T[K]
}

// and a concrete type to act on:
type Foo = {
  one: { prop1: string },
  two: { prop2: number }
}

// We want the union of all keys of properties of Foo, namely: 'prop1' | 'prop2'
type KeyofPropertyofFoo = Valueof<MappedKeyof<Foo>> // 'prop1' | 'prop2'; correct

// hey, this "union of keys of properties of" operation would be useful for me,
// let's define the composition as follows:
type KeyofPropertyof<T> = Valueof<MappedKeyof<T>>;

// apply it to foo, and... it is broken!
type KeyofPropertyofFooBroken = KeyofPropertyof<Foo> // never; broken

Expected behavior:
I expect KeyofPropertyof<Foo> to be equivalent to Valueof<MappedKeyof<Foo>>.

Actual behavior:
What's KeyofPropertyof<T> doing? Quickinfo says type KeyofPropertyof<T> = keyof (T[keyof T]). So it took Valueof<MappedKeyof<T>> and "simplified" it to keyof Valueof<T>, but that's the intersection of the keys of the properties, not the union. Hence the never instead of 'prop1'|'prop2'🙁


In general, given

type A = ...
type F<T> = ...
type G<T> = ...
type FoG<T> = F<G<T>>

I expect F<G<A>> to be equivalent to FoG<A>. Anything else makes it very hard to reason about what is going on.

This seems like something @tycho01 has run into before: is it the same issue as #16244 and/or #16018? Is it a bug? design limitation? intention? there any way to work around it in general? My use cases here are mostly type manipulation in the absence of built-in subtraction/exact/conditional mapped types. Someone on Stack Overflow asks a question, I try to build something that solves the problem, and I hit this.

Thoughts? Thanks!

Metadata

Metadata

Assignees

No one assigned

    Labels

    FixedA PR has been merged for this issue

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions