Reputation: 317
I have this code, which works just fine in a simple case, meaning the "permissionList" const is type inferred, VSCode can suggest me the properties, such as "permission_1". Also inside the "constraintFn", when I declare the object from which the type is inferred, it suggests me "code" and "id", since these are the valid properties that you can use based on the generic type "T".
type PropType<T> = {
[key: string]: T;
};
type Codes = 'code1' | 'code2';
class PermissionType {
code: string;
id: number;
}
const constraintFn = <T, TRe extends PropType<T>>(t: { new(): T; }, obj: TRe): TRe => {
return obj;
};
export const permissionList = constraintFn(PermissionType, ({
permission_1: {
code: 'code1',
id: 1,
},
permission_2: {
code: 'code2',
id: 2,
},
}));
Now, if I change one thing:
class PermissionType {
code: Codes; // this changed from string, to Codes
id: number;
}
The inference still works when I declare the object in the function param, I can chose from a list of Codes, BUT the inference of the const "permissionList" disappears, and it only displays:
PropType<PermissionType>
Instaead of:
{
permission_1: {
code: string;
id: number;
};
permission_2: {
code: string;
id: number;
};
}
Thanks, I hope there's a solution to this. It would be fun to create these kind of types.
Upvotes: 1
Views: 52
Reputation: 317
My solution so far is this. I don't really consider this as a perfect one, since it involves some kind of weird type recursion (see how TRe is constrained by a type that accepts TRe as a generic parameter), which feels like a hack. Anyways, if it helps someone, this compiles fine.
[edit]: reflecting on the answer containing "as const", that's something I don't want to write on the end of each object that I supply as parameter to the "constraintFn". I much rather find a solution that does this inside the function.
type PropConstraintType<TObj, TProp> = {
[K in keyof TObj]: TProp;
};
type Codes = 'code1' | 'code2';
class PermissionType {
code: Codes;
id: number;
}
const constraintFn = <T, TRe extends PropConstraintType<TRe, T>>(t: { new(): T; }, obj: TRe): TRe => {
return obj;
};
export const permissionList = constraintFn(PermissionType, ({
permission_1: {
code: 'code1',
id: 1,
},
permission_2: {
code: 'code2',
id: 2,
},
}));
Upvotes: 1
Reputation: 384
When we use generics for type alias, the Typescript compiler generally doesn't go to infer types at each of the levels, hence we just see the alias PropType<PermissionType>
There is a trick using which we can make this type expand with a custom type alias Id<T>
type Id<T> = {} & {[k in keyof T]: T[k]}
// This type allows the expansion of other type
type Id<T> = {} & {[k in keyof T]: T[k]}
const constraintFn = <T, TRe extends PropType<T>>(t: { new(): T; }, obj: TRe): Id<TRe> => {
return obj;
};
export const permissionList = constraintFn(PermissionType, {
permission_1: {
code: "code1",
id: 1,
},
permission_2: {
code: 'code2',
id: 2,
},
});
// Now permission list has types => { [x: string]: PermissionType }
Upvotes: 0
Reputation: 26324
Odd that using as const
here makes TypeScript give you the whole type...
export const permissionList = constraintFn(PermissionType, {
permission_1: {
code: "code1",
id: 1,
},
permission_2: {
code: 'code2',
id: 2,
},
} as const); // added here
Upvotes: 1