I'm using Flavored types to catch errors where a user name is used in place of a user id. However, I'm having trouble when the flavored type is a key of a dictionary. I'd like to make a dictionary which "strictly" contains keys of a certain flavor. However, if I use the wrong key type, TypeScript keeps inferring 'any' instead of 'never' or 'undefined'.
declare const flavor: unique symbol;
type Flavored<T, F> = T & { readonly [flavor]?: F };
type UserId = Flavored<string, 'UserId'>;
type UserName = Flavored<string, 'Name'>;
type Color = Flavored<string, 'Color'>;
const alice: UserName = 'alice';
const id: UserId = 'user-0001';
const blue: Color = 'blue';
const yellow: Color = 'yellow';
type ColorsByUserId = {
[Key in UserId]: Color[];
} & {
[Key in Exclude<PropertyKey, UserId>]: undefined;
}
// Unexpected error:
// Type '{ [x: string]: Color[]; }' is not assignable to type 'ColorsByUserId'.
// Type '{ [x: string]: Color[]; }' is not assignable to type '{ [x: number]: undefined; [x: symbol]: undefined; }'.
// 'string' and 'number' index signatures are incompatible.
// Type 'Color[]' is not assignable to type 'undefined'.
const favorites: ColorsByUserId = {
[id]: [ blue, yellow ],
};
// Should have error here because alice is not a UserId. Instead we get color as 'any'.
for (const color of favorites[alice]) {
console.log(color);
}
Remember to check if you have Strict Mode or noImplicitAny on: https://www.typescriptlang.org/tsconfig/#noImplicitAny
If you are using VS Code's built-in TypeScript without a tsconfig file, then Strict Mode is off by default.
This turned out to be my original problem.