The following example says it all:
function fn<R>(cb: <S1 extends string>(string: S1) => R) {
return <S2 extends string>(str: S2) => cb(str)
}
const f = fn((passedString) => {
return { passedString }
})
// I expect an error: This comparison appears to be unintentional ... but there's
// no error which means `passedString` is not known to be of type `""`
export const someValue = f("").passedString === "dfafa"
I would expect f("").passedString
to be of type ""
, but it looks like the compiler couldn't pick the value of type S1
after the call to fn
. Is there any way to get around this?
Update
I am aware that I can achieve the desired behaviour by placing the type parameter on the callback function itself passed to fn
as also mentioned in @jesus Diaz Rivero
answer below. However, all parameters to the callback must be explicitly typed in such case, which I'm trying to avoid.
function fn<R, V>(cb: (number: number, value: V) => R) {
return (number: number, str: V) => cb(number, str)
}
// `number` must be typed explicitly
const f = fn(<V>(number: number, passedValue: V) => {
return { number, passedValue }
})
export const someValue = f(8, "" as const).passedValue === "dfafa" // correctly errors
In this case, I would like the number
parameter to not have to be explicitly typed when calling fn
, but instead taken from the type signature of fn
. In my case, the extra parameter (also the first parameter) is not as simple as number
. If the callback were not generic, it would work, but with the introduction of <V>
, it doesn't.
PS: The actual function I'm working with is a create
method on a builder that takes advantage of the type info on the builder to specify the types of the parameters of the callback, and returns a middleware. The middleware is used as a function call. It looks something like:
export const builder = {
// Rather than `number`, the actual type is based on type parameters on the builder
// which I don't want caller to explicitly specify
create<R, P extends unknown[]>(cb: (opts: number, ...params: P) => R) {
return (...params: P) => {
// Actual middleware
return (opts: number) => cb(opts, ...params)
}
}
}
In order to let TS correctly infer the type you should the S1 generic parameter to be part of fn
:
function fn<R, S1 extends string>(cb: (input: S1) => R) {
return cb
}
const f = <T extends string>(passedString: T) => {
return { passedString }
}
// Non-callback version correctly throws error
const someValue = f("").passedString === "da";
// Callback version now throws error
const someCallbackValue = fn(f)("").passedString === "da";
Note I have simplified fn
since it seems to be a bit redundant to return a function which returns the callback.