Is it possible, while skipping NoInfer
, distinguish a case “we are passing argument into a generic” vs “we are inferring types from data passed”?
Goal is to provide typing for simple array generator(it’s a learning exercise to wrap my head around inferring, so let’s ignore low practical value)
function generateArr(count, init) {
if (typeof init === 'function') {
return new Array(count).fill(undefined).map((_value, index) => init(index))
} else {
return new Array(count).fill(init)
}
}
ideally it should allow both inferring and providing type explicitly
function generateArr<T extends (index: number) => any>(count: number, init: T): ReturnType<T>[];
function generateArr<T>(count: number, init: T): T[];
function generateArr(count: number, init: unknown): unknown[] {
if (typeof init === 'function') {
return new Array(count).fill(undefined).map((_value, index) => init(index))
} else {
return new Array(count).fill(init)
}
}
const a1 = generateArr(2, 'a')
// ^? string[]
const a2 = generateArr<'a' | 's'>(2, 'a')
// ^? ('a' | 's')[]
const a3 = generateArr(2, () => 's' as 'a' | 's')
// ^? ('a' | 's')[]
const a4 = generateArr(2, {a :4})
// ^? { a: number }[]
const a5 = generateArr<'a' | 's'>(2, () => 's')
// ERROR Argument of type '() => string' is not assignable to parameter of type '"a" | "s"'.(2345)
TS rightfully goes to a second overload and requires me to have init
of the same type 'a' | 's'
.
I was finally able to reach this with NoInfer<>
, but I don’t like how complex it became
function generateArr<T extends (index: number) => any>(count: number, init: T): ReturnType<T>[];
function generateArr<T>(count: number, init: NoInfer<(index: number) => T>): T[]
function generateArr<T>(count: number, init: T): T[];
function generateArr(count: number, init: unknown): unknown[] {
if (typeof init === 'function') {
return new Array(count).fill(undefined).map((_value, index) => init(index))
} else {
return new Array(count).fill(init)
}
}
Is there easier way? Maybe with conditional types or default values for type placeholders?