In this TypeScript code, also repeated below, the type PickedAttributeSimpler is a generic type with a parameter. The parameter type is restricted (using extends) to a pretty small set - in this example, just one specified possibility, but the restriction doesn't have to always be so narrow.
Later in the type definition, I would expect that references to the generic type parameter would be narrowed to the type the parameter was restricted to, i.e. what's on the right side of the extends keyword. In any particular use context, it might even be narrower. However, in this example and in several more complicated ones eventually boiled down to this one, replacing references to the type parameter with the type that parameter is limited to (to the right of extends) produced the desired behavior, allowing TypeScript to figure out the type of the generic as a whole.
Unfortunately, in practice, some additional narrowing is sometimes needed, where an instance of the type specifies a parameter more specific than the type that parameter must be limited to, and additional references to the generic type parameter are needed to track that through consistently - so just dropping the use of generic type parameters altogether is not a valid solution beyond this simplified example.
Instead, a better understanding is sought of the conditions in which limiting the parameter type will NOT result in later references to the generic type parameter reflecting that narrowing.
Separate questions address why some simplifications that appear possible from this example such as hard-coding the ObjectModelMap or using a conditional type in ObjectModel don't adequately preserve the issue being demonstrated.
It is possible this is due to a bug in TypeScript. The closest issue I could find is #33014 but that seems to be more specific about functions than defining other types. 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>
}
type PickedAttributeSimpler<T extends 'Moon'> = DRYObjectModelMap[T]['visibleStarsCount'];
//The error disappears when the definition below rather than above is used.
//type PickedAttributeSimpler<T extends 'Moon'> = DRYObjectModelMap['Moon']['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
Well, typescript is a little bit wierd sometimes.
If you use a mapped type of a Union to look up the type value it's working. It would also work with just "Moon", btw. playground