Why does mapping a closed set through a generic type not permit the resolution repetition does?

Viewed 23

In this TypeScript code, also repeated below, there is a mapped type

type DRYObjectModelMap = {
    [PlanetaryBodyClass in keyof FieldNameWithValueMap]: ObjectModel<PlanetaryBodyClass>
}

where keyof FieldNameWithValueMap = 'Moon' | 'Planet' but the mapped type does NOT produce the equivalent of the expected:

type ObjectModelMap = { //This requires more hard-coded repetition
    Moon: ObjectModel<'Moon'>;
    Planet: ObjectModel<'Planet'>;
}

Using this expected version, even by mapping to it within the mapped type (putting ObjectModelMap[PlanetaryBodyClass] to the right of the colon) causes the error further below to disappear, but also makes the code significantly harder to maintain, requiring brittle repetition. It seems like mapped types are intended to help avoid the need for this repetition, but aren't actually working towards that end in this example.

A separate question addresses why adding a wrapper type with a superfluous conditional higher up in the code can unexpectedly cause the error to disappear.

It is possible this is due to a bug in TypeScript, though not one I can find in the Issues/PRs list - or maybe I just don't understand what's happening here well enough to search with the right terms or report it. (Please feel free to file a new bug report there if you think it's appropriate and not duplicative).


The code at the Playground link is:

import BN from "bn.js";

//Simplified version of externally fixed model; many fields omitted for simpler example
type FieldNameWithValueMap = {
    Moon: [fieldName: "visibleStarsCount", fieldValue: BN] | [fieldName: "hostStar", fieldValue: string];
    Planet: [fieldName: "visibleStarsCount", fieldValue: BN] | [fieldName: "hostPlanet", fieldValue: string];
}
//type PlanetaryBodyName = keyof FieldNameWithValueMap; //'Moon' | 'Planet'; not used for simpler example
type TupleToObject<T extends [string, any]> = { [key in T[0]]: Extract<T, [key, any]>[1] };
type ObjectModel<T extends keyof FieldNameWithValueMap> = TupleToObject<FieldNameWithValueMap[T]>;
//Hovering over these two examples, the types are as expected; they need to be derived from imported types.
type MoonObject2 = ObjectModel<'Moon'>; //{visibleStarsCount: BN; hostPlanet: string;}
type PlanetObject2 = ObjectModel<'Planet'>; //{visibleStarsCount: BN; hostStar: string;}

type DRYObjectModelMap = {
    [PlanetaryBodyClass in keyof FieldNameWithValueMap]: ObjectModel<PlanetaryBodyClass>
    //The below works, but not the above, and requires the undesired hard-coded repetition
    //[PlanetaryBodyClass in keyof FieldNameWithValueMap]: ObjectModelMap[PlanetaryBodyClass]
}
type ObjectModelMap = { //This works in place of using DRYObjectModelMap, but requires more hard-coded repetition
    Moon: ObjectModel<'Moon'>;
    Planet: ObjectModel<'Planet'>;
}
type PickedAttributeSimpler<T extends 'Moon'> = DRYObjectModelMap[T]['visibleStarsCount'];
function genericFunction<
    T extends 'Moon', //union type that includes | 'Planet' omitted for simpler example.
    //Omitting the generic type parameter and hard-coding it into each usage below fixes the error,
    //but the real version of the function not oversimplified for the example needs to be generic.
>(
    PlanetaryBodyClass: T,
) : PickedAttributeSimpler<T> { //Same if using DRYObjectModelMap[T]['visibleStarsCount']
    return new BN(42); //ERROR TS(2322): Type 'BN' is not assignable to type 'PickedAttributeSimpler<T>'.
}
type ExamplePickedAttribute = PickedAttributeSimpler<'Moon'>; //This is type 'BN' as expected
type ExampleGenericPickedAttribute<T extends 'Moon'> = PickedAttributeSimpler<T>; //This is DRYObjectModelMap[T]['visibleStarsCount']
type ExampleGenericInstanceOfPickedAttribute = ExampleGenericPickedAttribute<'Moon'>; //This is BN as expected
type HoverToSeeHowTypeShouldResolve<T extends 'Moon'> = DRYObjectModelMap[T]['visibleStarsCount'];
type HoverToSeeHowTypeShouldResolveInstance = HoverToSeeHowTypeShouldResolve<'Moon'>; //This is BN as expected
0 Answers
Related