[Typescript] The partial Inference Problem
Problem for partial inference:
export const makeSelectors = <
TSource,
TSelectors extends Record<string, (source: TSource) => any> = {},
>(
selectors: TSelectors,
) => {
return selectors;
};
interface Source {
firstName: string;
middleName: string;
lastName: string;
}
const selectors = makeSelectors<Source>({
getFullName: (source) =>
`${source.firstName} ${source.middleName} ${source.lastName}`,
getFirstAndLastName: (source) => `${source.firstName} ${source.lastName}`,
getFirstNameLength: (source) => source.firstName.length,
});
So makeSelectors
has type: const makeSelectors: <Source, {}>(selectors: {}) => {}
If remove the generic type:
const selectors = makeSelectors({
getFullName: (source) =>
`${source.firstName} ${source.middleName} ${source.lastName}`,
getFirstAndLastName: (source) => `${source.firstName} ${source.lastName}`,
getFirstNameLength: (source) => source.firstName.length,
});
Then it has type:
/*
const makeSelectors: <unknown, {
getFullName: (source: unknown) => string;
getFirstAndLastName: (source: unknown) => string;
getFirstNameLength: (source: unknown) => any;
}>(selectors: {
getFullName: (source: unknown) => string;
getFirstAndLastName: (source: unknown) => string;
getFirstNameLength: (source: unknown) => any;
}) => {
getFullName: (source: unknown) => string;
getFirstAndLastName: (source: unknown) => string;
getFirstNameLength: (source: unknown) => any;
}
*/
So Problem is If we are using one generic type TSource
in more than one place, like so:
export const makeSelectors = <
TSource,
TSelectors extends Record<string, (source: TSource) => any> = {},
>
Typescript only infer the first generic type, doesn't infer from the second.
Solution could be make it a high order function, each time give one generic type:
import { Equal, Expect } from '../helpers/type-utils';
export const makeSelectors =
<TSource = 'expect to receive a source type'>() =>
<TSelectors extends Record<string, (source: TSource) => any> = {}>(
selectors: TSelectors
) => {
return selectors;
};
interface Source {
firstName: string;
middleName: string;
lastName: string;
}
const selectors = makeSelectors<Source>()({
getFullName: (source) =>
`${source.firstName} ${source.middleName} ${source.lastName}`,
getFirstAndLastName: (source) => `${source.firstName} ${source.lastName}`,
getFirstNameLength: (source) => source.firstName.length,
});
type tests = [
Expect<Equal<typeof selectors['getFullName'], (source: Source) => string>>,
Expect<
Equal<typeof selectors['getFirstAndLastName'], (source: Source) => string>
>,
Expect<
Equal<typeof selectors['getFirstNameLength'], (source: Source) => number>
>
];