Basically, I am trying to find a way to use function types as generic types to find the return type of the function with the constraint that I choose. However, I'm not sure if it's possible because I don't understand how generic function parameters work in conditional clauses. Here is what I'm trying currently:
type FooFormatter = <S extends string>(input: S) => `foo-${S}`;
type StringFormatter = <S extends string>(input: S, ...args: never) => string;
type ComputeFormatter<Input extends string, T extends StringFormatter> =
T extends (input: Input, ...args: never) => `${infer $Value}`
? $Value
: never;
type foo = ComputeFormatter<"hey", FooFormatter>; // this is `foo-${string}` but I want "foo-hey"
In ComputeFormatter
I'm trying to check if I can somehow constrain the generic in T
by overwriting the function's first parameter to the Input
type. Thanks for any help!
Short answer, there is an PR merged for TS4.7 for this feature. Of particular relevance to your question...
type Box<T> = ReturnType<typeof makeBox<T>>; // { value: T }
This is super close to a solution of #37181. Will it will allow us to do...
return { value }; }; // Can we do it or something similar? Currently doesn't compile :( type MakeBox<T> = ReturnType<typeof makeBox<T>> // As it now allows us to do (no generics though) const stringMakeBox = makeBox<string>; type MakeBox = ReturnType<typeof stringMakeBox> // And even more relevant to support generics if we can now do: type MakeBox = ReturnType<typeof makeBox<string>>
Yes, you can indeed use that pattern to capture a generic return type of a generic function. This is something that previously wasn't possible. I'll update the PR description to include an example.
I can't find anything on whether this works on type aliases for functions, or just works on runtime functions only (inferred with typeof
), but you can possibly just use the actual runtime implementations and use typeof
as needed
Here is this working on the nightly build, note that you'll still need a runtime implementation, whether or not it actually implements anything is up to you, it could be some mock function that doesn't return anything
type FooFormatter = <S extends string>(input: S) => `foo-${S}`;
const FooFormatterImplementation: FooFormatter = {} as any; //mock implementation
type StringFormatter = <S extends string>(input: S, ...args: never) => string;
type foo = ReturnType<typeof FooFormatterImplementation<"hey">>
// ^? `type foo = "foo-hey"`
I highly recommend investigating some of the types they have over at type-fest
, if you haven't already. Especially
Split
Change case utilities
,Reading through the library, it looks like you've got a good sense of types, and I think you may actually be limited by the TS version of Deno.
There are a bunch of workarounds, but I'm not sure if they're really applicable in your usecase.
This is not dependent upon TS4.7 Hooray!
type FooFormatter = <S extends string>(input: S) => `foo-${S}`;
type BarFormatter = <S extends string>(input: S) => `bar-${S}`
export interface Formatters {
FooFormatter: FooFormatter,
BarFormatter: BarFormatter
}
// then anyone wanting to add a custom formatter, has to modify and reexport the interface,
// this is similiarly done in @react-mui, using module augmentation
// https://www.typescriptlang.org/docs/handbook/declaration-merging.html
function takeInnerTest<T extends string, FmtKey extends keyof Formatters>(input: T, formatter: FmtKey)
{
type formattersDiscriminated = {
[K in keyof Formatters]: Formatters[K]
}[FmtKey]
const __mockFunction: formattersDiscriminated = ((...args: any[]) => undefined) as any
const __mockReturn = __mockFunction(input)
type String = ReturnType<typeof __mockFunction>
type ReturnValue = Extract<typeof __mockReturn, String>
return null! as ReturnValue
}
const foo3 = takeInnerTest("hey", "FooFormatter")
type foo3 = typeof foo3
// ^?
const foo4 = takeInnerTest("hey", "BarFormatter")
type foo4 = typeof foo4
// ^?