Ran Lottem
Ran Lottem

Reputation: 486

Typescript - nested map type gives confusing type hints

I've written a nested Omit type in two different ways, but the one that makes more sense to me gives convoluted type hints while the one that's less intuitive actually looks best. Here goes:

type HasSameNested = {
    a: {
        c: number; d: string
    },
    b: {
        c: string; d: number
    }
}; // flipped C and D types

type Omit2<T, K1 extends keyof T, K2 extends keyof T[K1]> = {
    [P1 in keyof T]: P1 extends K1 ? {
        [P2 in Exclude<keyof T[K1], K2>]: T[P1][P2]
    } : T[P1]
}

type Omit2_2<T, K1 extends keyof T, K2 extends keyof T[K1]> = {
    [P1 in keyof T]: P1 extends K1 ? {
        [P2 in Exclude<keyof T[P1], K2>]: T[P1][P2]
    } : T[P1]
}

type omit_union = Omit2<HasSameNested, 'a' | 'b', 'c'>;
let T6: omit_union;
T6.a; // works, type is { d: string }
T6.b; // works, type is { d: number }

type omit_union_2 = Omit2_2<HasSameNested, 'a' | 'b', 'c'>;
let T7: omit_union_2;
T7.a.d = 'a'; // works but types are ugly:
// a: { [P2 in Exclude<keyof HasSameNested[K1 & "a"], "c">]: HasSameNested[K1 & "a"][P2]; }
// d: d: HasSameNested[K1 & "a"]["d"]

T7.b.d = 4; // works but types are ugly, as above

Assigning the wrong type to T7.b.d or T7.a.d does tell me e.g. string is not assignable to number, but I don't understand why using Exclude<keyof T[P1], K2> gives such convoluted typing even though P1 in keyof T and P1 extends K1, and Exclude<keyof T[K1], K2> gives the correct types.

Upvotes: 2

Views: 215

Answers (1)

jcalz
jcalz

Reputation: 328608

This looks like a bug in the compiler to me. I've just filed Microsoft/TypeScript#31326 and will report back with updated information from there. Good luck!

UPDATE, 2019-05-10: This has been marked as bug by one of the language designers. Not sure when it will be fixed, but at least we know you're right to be confused about that type information.

UPDATE, 2019-05-11: As you noticed, this bug is now fixed and has been merged into master. That means if you now try your code with typescript@next you should see the new behavior:

type omit_union_2 = Omit2_2<HasSameNested, 'a' | 'b', 'c'>;
declare let T7: omit_union_2;
T7.a; // works, type is { d: string }
T7.b; // works, type is { d: number }

I don't see a difference between your T6 and T7 types anymore. So, looks like this was entirely caused by that compiler bug, and should go away when TypeScript 3.5 is released on or around May 30, 2019.

Okay, good luck to you!

Upvotes: 2

Related Questions