microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
101.01k stars 12.49k forks source link

Proposal: Partial Type Argument Inference #26242

Open weswigham opened 6 years ago

weswigham commented 6 years ago

After exploring the concept in #23696, we've come to the conclusion that implicitly making type arguments available by name would unnecessarily expose previously unobservable implementation details. As such, I'll be breaking that proposal down into two parts. One is just the partial inference (which is here), which can stand on its own, as discussed in #20122 and #10571.

To recap, partial type argument inference is the idea that some number of type arguments in a type argument list may be provided, while others are elided and fulfilled via inference using the rest. Specifically, in #10571 a number of different syntax proposals will brought forward:

For the following examples, assume we have a

declare foo<A,B,C>(): [A, B, C];

Variant 1 - elided entries

foo<,,string>(); // returns [{}, {}, string]

This is the most terse option, as simply the lack of inputs implies inference sites. This would almost seem to logically follow from how not providing a list also causes inference to occur at all sites. This does have issues, however: specifying a final parameter as needing inference would require a trailing , in the list (something we currently explicitly disallow), and lists could very easily become confusing, as a , is very easy to skip over.

Variant 2 - Sigil marker

foo<*, *, string>(); // returns [{}, {}, string]

As the second most terse option, this also has appeal; however I think it also fails on a few points. First, * is non-obvious what it means; it implies a "wildcard" of some kind, but in the context of types that could mean an inferred type, a bound, or an existential. Second, as a single-character sigil, we're unlikely to meaningfully provide completions for it even though it is contextually relevant. Finally, we're considering other work to do with existentials and generated type parameters in the future which we'd like to be able to use the * as an indicator for.

Variant 3 - Keyword marker

a. auto

foo<auto, auto, string>(); // returns [{}, {}, string]

b. infer

foo<infer, infer, string>(); // returns [{}, {}, string]

Neither of these are as terse as the others, but both are still likely substantially shorter than providing the entire list of types by hand in situations where partial inference is desired. Of the two keywords, auto may be shorter, but currently carries no meaning within the language. infer on the other hand is already used for marking inference positions within conditional types. The infer method was explored in #22368, however was taken much father - almost fully replicating the arbitrary placement and naming the operator affords within conditional types.

In the end, I'm advocating for variant 3b - the infer placeholder, with none of the extra features afforded in #22368 (we can always add them later if there is demand).

treybrisbane commented 6 years ago

My vote is for either 1 or 3b :)

AlCalzone commented 6 years ago

Maybe adopt the ? from the partial application proposal

foo<?, ?, string>(); // returns [{}, {}, string]

which already stands for optional in TypeScript. Actually, the meaning of would be pretty similar to the proposal and could allow for something like this:

type Foo<T, U, V> = T | U | V;
type Bar = Foo<?, string, ?>; // equal to type Bar<A, B> = A | string | B;
jsiwhitehead commented 6 years ago

Will this include the option for simply omitting trailing type arguments, and having them automatically set as inferred?

Ie for these to be equivalent:

foo<string, infer, infer>();

foo<string>();
weswigham commented 6 years ago

@jsiwhitehead consider:

declare const foo: {
  <A, B>(): A & B;
  <A>(): A;
};
foo<string>(); // The signature this refers to would be ambiguous if `infer`s were autofilled

So I don't think so - those likely won't be equivalent.

jsiwhitehead commented 6 years ago

@weswigham sorry I wasn't fully clear, I meant in the case when the trailing type arguments are optional / have defaults, so in situations where they can already be left out.

The change I'm asking about is how the unprovided optional types are resolved. Currently there are two different cases:

Hopefully this proposal for partial inference could include allowing inference to continue working in the second case (as requested in #19205, which is closed as a duplicate of #10571).

E.g.

declare function foo<A, B = any>(b: B): [A, B];

foo<number>(null); // Resolves to [number, any], should resolve to [number, null]

Or does that also lead to new ambiguities?

weswigham commented 6 years ago

Or does that also lead to new ambiguities

In your example no, for more complex types with multiple signatures, yes. It's also technically a break to do that since we'd suddenly be doing inference where previously people we relying on defaults. By making it explicit with foo<number, infer>(null) we avoid both issues.

jsiwhitehead commented 6 years ago

I'm struggling to see how this could lead to an ambiguity that wasn't already there sorry, even with multiple signatures. The change I'm asking about is only about how unprovided type arguments are resolved, not about the process Typescript uses to choose which signature to use.

E.g. there is an ambiguity in the following, but that is the case already, and currently is just resolved by taking the first possible match, which is fine.

declare const foo: {
  <A, B = any>(b: B): [A, B];
  <A>(b: any): A;
};
foo<string>(null); // This resolves to [string, any], but should resolve to [string, null]

Sorry to keep digging on this, but I'm trying to type a selector based API which will be vastly less usable if infer has to be written for all types:

do<string>('key1', 'key2', ..., 'keyN', (value1, value2, ..., valueN) => ...)

vs

do<string, infer, infer, ..., infer (N times)>('key1', 'key2', ..., 'keyN', (value1, value2, ..., valueN) => ...)

Of course, if the fact that this would technically be a break means it's a no go either way, then that's just how it is!

sledorze commented 6 years ago

@weswigham We are wondering how this feature would play with a new encoding we're likely to use in fp-ts.

Given:

right: <L = never, A = 'reason is you cannot partially bind Type Params to `right`'>(a: A) => Either<L, typeof a>

What would be the typing for:

// x3: Either<string, number> or Either<string, ???>
const x3 = right<string, infer>(1)

The thread is discussed here: https://github.com/gcanti/fp-ts/issues/543#issuecomment-412274064

Thanks in advance.

opiation commented 6 years ago

Not sure if I'm a little late to the game here but I'd like to give a simple use case for when this might be be useful.

Given the following:

interface Options<S = {}, Payloads = {}> {
  reducers: {
    [key in keyof Payloads]: (state: S, payload: Payloads[key]) => S
  }
  state: S
}

function identity<S, Payloads = {}>
  (options: Options<S, Payloads>): Options<S, Payloads> {
  return options
}

const options = {
  reducers: {
    add: (state, payload: number) => ({
      ...state,
      answer: state.answer + payload
    })
  },
  state: {
    answer: 42
  }
}

Type inference works wonderfully as expected when no type arguments are supplied to the identity function...

// Both State and ReducerPayloads are inferred correctly provider `state` and `payload` type safety
const fullyInferred = identity(options)

When one explicitly types the S however, type inference for Payloads is lost and defaults to {} despite the inferable type information for Payloads being more specific and arguably safer to use.

// When explicitly specifying the State however, ReducerPayloads is no longer inferred and
// defaults to {}.  We effectively lose type inference for `partiallyInferred.reducers`
const partiallyInferred = identity<{ answer: number }>(options)

Using infer here would allow the API consumer to specify when the inferable type should be used in place of the default.

const partiallyInferred = identity<{ answer: number }, infer>(options)

If there's already a means of achieving this partial inference in this example, feel free to share it here as it would seem quite useful.

insidewhy commented 6 years ago

@jsiwhitehead I feel your pain, I've been writing an API that uses a type involving many generic string literals (they are used to build action creators for ngrx). It's annoying having to iterate every argument even with the version of typescript from this branch.

I wonder if maybe a trailing ** could ease our pain. So instead of needing:

<T, *, *, *, *, *>() => {}

where the last 5 here are string literals and I need to add a new * everytime I need to add a new string literal to my class, I could write:

<T, **>() => {}

to infer all subsequent generics

flushentitypacket commented 6 years ago

@ohjames I like that. Further, that:

<T>() => {}

is equivalent to

<T, **>() => {}
insidewhy commented 6 years ago

@flushentitypacket Yes, I wish the language was designed that way in the first place, however now it's too late to implement things that way, given it will conflict with default generic parameters?

aleclarson commented 6 years ago

I wonder if maybe a trailing ** could ease our pain. <T, *, *, *, *, *>() => {} => <T, **>() => {}

Maybe, a trailing "elided entry" could imply all remaining parameters are inferred.

<T,>() => {}

edit: Actually, this would be an issue for multi-line parameters.

falsandtru commented 6 years ago

related: #21984

lukescott commented 5 years ago

Could you simply make trailing types optional, while making leading types required? It would fully depend on how the types were ordered, but I see that as a feature rather than a limitation. Optional function arguments work similarly.

Given:

declare foo<A,B,C>(): [A, B, C];

This means you can do any of these:

foo()
foo<string>()
foo<string,string>()
foo<string,string,string>()

But not:

foo<,string>()
foo<,,string>()
foo<,string,string>()
foo<,string,>()

This wouldn't require any special syntax parsing. It would simply fix the expected arguments error.

insidewhy commented 5 years ago

@lukescott That proposal is here: https://github.com/Microsoft/TypeScript/issues/10571 It's also how type parameters work in C++. I think TypeScript probably inherited this limitation from C#. Looks like it's being fixed in both languages, will probably land in C# first.

lukescott commented 5 years ago

@ohjames I do see omitting trailing types mentioned in #10571, but it looks like it advocates for an auto keyword, which would open the door to foo<auto,string>(). Later comments in the issue mention _ and *. I'm not sure I see much of a difference. IMO, leading types should be required. Even with that, making trailing types optional solves a lot of use-cases.

insidewhy commented 5 years ago

@lukescott read the initial comment on that issue, the talk of * etc. is tangential to the issue.

Edit: Maybe it is lacking a bit of focus. If there isn't an issue for simply omitting trailing types then maybe someone should open one. Might it conflict with type parameter defaults though?

lukescott commented 5 years ago

@ohjames

Maybe it is lacking a bit of focus. If there isn't an issue for simply omitting trailing types then maybe someone should open one.

That was my thinking as well. There is a lot of overlap between each of these proposals, with similar thoughts being shared. I haven't seen any mention to rein this into an MVP. IMO, the current proposals are too broad.

Might it conflict with type parameter defaults though?

I'm not sure how. At least any more than the current proposals would. Requiring leading types is more restrictive. Default types are also restrictive:

function foo<A,B = string,C>(a: A, b: B, c: C) {}
// error on C: Required type parameters may not follow optional type parameters.

Do you have something in mind?

insidewhy commented 5 years ago

@lukescott I think it's something like:

function totoro<A, B = object>(a: A, b: B) { ... }

Currently if I call this function thusly:

totoro('friend', new Date(2018, 3, 19))

Inside of totoro the B will still be of type object. However if we allow omitting parameters, in this case B could be inferred as Date. That would then make this a backwards incompatible change. Honestly I think it'd be better to do it like C++, and have it only fallback to the default type when inference is not possible; but for the sake of not breaking backwards compatibility this level of inference could be restricted to type parameters that do not specify defaults.

lukescott commented 5 years ago

@ohjames I think what you're saying is:

function totoro<A, B = object>(a: A, b: B): [A,B] {
    return [a,b]
}

const result1 = totoro('friend', new Date(2018, 3, 19))
const result2 = totoro<string>('friend', new Date(2018, 3, 19))

result1 comes back with [string, Date] and result2 comes back with [string, object]. Inside the function is {} unless you do B extends object = object.

It would seem like either proposal has the same effect on this. In either case you could either change how it works and infer Date, or use object as it currently works.

Personally I would prefer to break compatibility here and use default only when the type can not be inferred.

insidewhy commented 5 years ago

@lukescott Thanks for fixing my example. Glad you agree with the compatibility break but not sure if others will see it the same as us. It's been bothering me ever since C# adopted this limitation. Feel like it takes a lot away without bringing anything.

Richiban commented 5 years ago

Can't we support a {type} = {Expression} syntax at the call site?

I have a feeling that f<,,,,,,string,,,,,>() will be simply impossible to read with more than three type parameters.

My syntax would allow for supplying just the single type parameter that is causing a complete lack of type inference. For example, when calling the below function f the type D cannot be inferred, but the others can:

function f<A, B, C, D>(a : A, b : B, c : C) : D {
    ...
}

I suggest making it possible to call this with the syntax:

var d = f<D = string>(1, 2, 3);
ExE-Boss commented 5 years ago

I would also like to be able to omit all inferred types when unambiguous:

interface SpecialArray<T> extends Array<T> {}

let a: SpecialArray<_> = [1]
// and
let b: SpecialArray<> = [1]
// are both equivalent to:
let c: SpecialArray<number> = [1];
miguel-leon commented 5 years ago

Is this the same as #10571?

ExE-Boss commented 5 years ago

@miguel-leon Seems to be the case.

insidewhy commented 5 years ago

@miguel-leon @ExE-Boss has been pointed out before, but no, they are different. Read the histories.

pocesar commented 5 years ago

pretty please? 😍 3b is my choice as well had an issue that I had to leave out the strong type of the code that had to infer the return type of a callback to be injected as a return type of a property function... right now it's impossible without setting the generic and do the Promise<infer R>

btw, couldn't the logic be reversed? instead of:

function SomeFunction<A, T extends MyCallbackType = any, R = any>(a: A, t: T) => R {
}
SomeFunction<number, infer, typeof somethingElse>(1, () => Promise.resolve(somethingElse))

it would be

function SomeFunction<A, T extends MyCallbackType = infer>(a: A, t: T): T extends (...args: any) => Promise<infer R> ? R : void {
}

SomeFunction<number>(1, () => Promise.resolve(somethingElse))

so, it will never fallback to any but always use the parameter provided by a?

matthewadams commented 5 years ago

I just got bitten by this. SO question, with minimal example & answer, is at https://stackoverflow.com/questions/57347557/why-does-this-typescript-mixin-employing-generics-fail-to-compile/

I feel like this should really be documented somewhere. There's no mention of it that I could find in the TypeScript Handbook. I'd expect it to be discussed in https://www.typescriptlang.org/docs/handbook/generics.html.

Kethku commented 5 years ago

At this point it might not be helpful to pile on, but I have run into this as well.

In my case I wanted to capture a string constant type from an argument while still allowing the user to specify a generic parameter. Then I wanted to generate an object with the function as one of it's properties which returns the specified return type.

function example<TResult, TCommand extends string>(command: TCommand) {
  let foo: any = {};
  foo[command] = () => {} as TResult; // somehow get TResult here
  return foo as { [index in TCommand]: () => TResult };
}

This works, but you have to pass the command argument as the generic and as the actual function parameter, which really sucks and defeats the purpose.

example<number>("foo"); // Compiler error :(
example<number, "foo">("foo"); // Works but is lame because of the repeated string

So this is my vote that this feature should be considered. If you read it, thanks for your time. You guys are awesome

lifaon74 commented 5 years ago

I often encouter this situation:

// M should default to 'void'
function foo<T, M extends string>(value: T, mode: M = 'void'): M extends 'void' ? void : T {
  // some code
}

// user wants to write
foo<number>(10, 'void');
// instead of 
foo<number, 'void'>(10, 'void');
// Sadly its currently not possible without overloading the function (sometimes a lot)

Personally I prefer the variant 1 for this reason (less verbose).

Hope it will land soon 👍

sam-s4s commented 5 years ago

My simple scenario example:

class Test<A, B> {
    constructor(blah: B) { }
    typeA: A;
    typeB: B;
}
new Test<string>(3); // error, wants 2 type params :/

I want to be able to do the above, and feel like I should, because B is known from the param.

(I hope this feature gets implemented soon, and I hope that in this case, I won't need a sigil, eg. Test<string, _>(3) because there are no types specified after...)

sam-s4s commented 5 years ago

Also, hopefully this example will be fixed, too...

class Test<Z extends any, A extends boolean = boolean, B extends (A extends true ? number : string) = (A extends true ? number : string)> {
    constructor(blah: A) { }
    typeA: A;
    typeB: B;
}
new Test<number>(true).typeB; // should be typed as 'number', but is instead typed as `string | number'

It knows A is true, and so it should know B is number...

I know I could just calculate B whenever I want to use it, as it is just based on A, however I'm using it in about 20 places, and I'd rather not have 20 ternary types :P

kalbert312 commented 5 years ago

Also wishing for this. I'm trying to infer an array's type to a subset of keyof T:

interface Test {
    a?: string.
    b: string,
    c?: string,
}

const fn = <T extends object, K extends ReadonlyArray<OptionalKeys<T>>(keys: K): K => keys;

const keys = fn<Test>(["c"]);
type KeyExtractor<T> = T extends ReadonlyArray<infer U> ? U : never;

I'm hoping for the type of keys to be ("c")[], evaluated type of KeyExtractor<typeof keys> to be "c", then have this pass keyof T checks. Hopefully that makes sense. Difficult for me to articulate.

More concrete example: https://gitlab.com/kyle.albert/use-defaults#defaults-as-an-exportable-variable. Looking to extract the ["p2"] parameter on the call to useDefaults to a variable and use it as a source of truth for the defaultFuncOpts type parameter and the function call itself. The problem is that I get the error along the lines of type "string" is not assignable to <list of T keys>. If I explictly type the array to (keyof T)[] then the type is ALL the keys, no matter the contents of the array. Currently it requires maintaining a union type and a value to match it.

falsandtru commented 5 years ago

This feature can be used to remove any type in recursive type definitions.

type Tree1<T, U extends Tree1<T, any>[]> = [T, U];
type Tree2<T, U extends Tree2<T, *>[]> = [T, U];
jack-williams commented 5 years ago

I think a variant of this becomes especially pertinent in the presence of #32695 and #33580.

Narrowing needs explicit type annotations to find assertion signatures, which might be cumbersome for generic assertions. Using partial inference might provide a mechanism to flag the explicit assertion signature in a manner that is cheap for the user. Example copied from #34596

import { types } from "mylib"

const User: ObjectType<*> = types.Object({
  name: types.string,
  tags: types.array(types.union(types.literal("happy"), types.literal("sad")))
})

User.assert(someData) // ok now because of explicit annotation ObjectType<*>.
ChayimFriedman2 commented 4 years ago

I think just omitting the last arguments is intuitive, and breaking change is acceptable in this case. But even if not, I would prefer to simply not use inference when specified default values.

ulivz commented 4 years ago

Some supplements on basis of @jsiwhitehead

declare function foo<A = any, B = any>(a: A, b: B): [A, B];

Type Infer works when all generic types are omitted

foo(1, 1); // function foo<number, number>(a: number, b: number): [number, number]
foo('1', '1'); // function foo<string, string>(a: string, b: string): [string, string]

Type Infer does not work when parts of generic types are omitted

foo<number>(1, 1); // function foo<number, any>(a: number, b: any): [number, any]
foo<string>('1', '1'); // function foo<string, any>(a: string, b: any): [string, any]
foo<number>(1, 1); // function foo<number, any>(a: number, b: number): [number, number]
foo<string>('1', '1'); // function foo<string, any>(a: string, b: string): [string, string]

BTW, if we can get the type of parameters in current function, we can solve it manually.

ivoiv commented 4 years ago

Yet another bump for this feature. Variant 3.b looks most logical to me as the word infer explains well what's happening, whereas neither ",,Type" or "*, *, Type" are intuitive without reading about them in the docs. (how would you even google them?)

There are many situations where you need to specify some argument types, but infer others for certain concepts to work.

Returning a new function or using a wrapper to take the inferable parameters does work around the issue, but it's an odd design choice and I feel like most developers don't understand the need or reasoning for it.

c-vetter commented 4 years ago

Conceptually, I second @insidewhy's proposal for a marker to tell that all following types should be inferred. However, as a proponent of the infer keyword for partial inference, I'd like to propose this:

foo<string, ...infer>(...arguments)

I think both the infer keyword and the similarity to the well-known spread operator communicate the intent quite clearly.

The great value of the feature itself for complex interfaces itself should be obvious, I think.

c-vetter commented 4 years ago

I have repeatedly had the case that I'd like to infer some Types but have the consumer supply others. While this proposal would enable that use case, I would really love the following addition in order to be able to make the intent clear.

I'd like to write this (note the double Generic notation):

const server = {write(data:{v:{}}){}}
function foo< R > <
    T extends (data: U['v'])=>R,
    U extends { v: {} },
> (data: U, cb: T): { r: R} {
    server.write(data)
    return {r:cb(data.v)}
}

When called without anything (foo({v:{}},()=>{'s'})), it would yield this incomplete typing, just like now:

function foo<unknown, () => void, {
    v: {};
}>(data: {
    v: {};
}, cb: () => void): {
    r: unknown;
}

When called with an explicit type for R (foo<string>({v:{}},()=>{'s'})), it would yield this proper typing, while inferring everything that is intended for inference:

function foo<string, () => void, {
    v: {};
}>(data: {
    v: {};
}, cb: () => void): {
    r: string;
}

To my mind, this would be akin to double arrow notation: (a: string) => (b: number) => boolean.

I'm confident double Generic notation would greatly aid in understanding intent and would be rather easy to understand.

Nokel81 commented 3 years ago

I too have run into this problem and I think this would be a good solution for it. I have an object that has a specific set of keys and a specific generic type as a value but I would like it if typescript would be able to give more specific typings without having to type it out.

Namely, if I have the following:

class Foo<T>;

enum Bar;

const mapping: Partial<Record<Bar, Foo<?>>> = { ... };

It would be very nice for mapping[key] where key is of type Bar to be of type undefined | Foo<Concrete> if in that singular declaration there existed a [key]: new Foo<Concrete> and undefined | Foo<any> if that was not present.

jasonkuhrt commented 3 years ago

Adding a use-case here. In the following I want user to be able to specify first type param but let second infer from the return type of the defined provider method which is contained within the parameters. This inferred type is then used in the return type position of yet another function (first function is higher order).

export function createProvider<ContextRequired extends BaseContext, ContextContributed extends BaseContext>(
  //                                                                  (1) ^-------------- this infers from...
  config: {
    parameters?: BaseParameters
    provider: (context: BeforeCaseContext<ContextRequired>) => MaybePromise<ContextContributed> 
  //                                                                        (2) ^-- whatever user returns here
  }
): <Context extends ContextRequired>(context: BeforeCaseContext<Context>) => MaybePromise<Context & ContextContributed> { 
  //                                                              in order to be put here later -----^ (3)
  // ...
}
lukeapage commented 3 years ago

What about just not supporting multiple overloads? e.g.

declare const foo: {
  <A, B>(): A & B;
  <A>(): A;
};
foo<string>(); // use old behavior - multiple overloads, no inference

and

declare const foo: {
  <A, B>(): A & B;
};
foo<string>(); // use new behavior - infer B

This:

on the downside, it is harder to explain and can be more confusing as to why things are breaking when you add an overload. However imo its confusing already ;- when inference doesn't work you have to do some digging to work out why its not working.

askirmas commented 3 years ago

One more practical use case – limit argument to be subset of some Source and then process exact keys of argument

type Toggling = <
  Source,
  Toggles extends {[S in keyof Source]?: boolean} /** = {[S in keyof Source]?: boolean}  - no way */
>(
  p: Toggles
) => {
  [T in keyof Toggles]: Source[T]
}

// Somehow const toggling: Toggling<?>  = ... 

const toggled = toggling<{a: string, b: string}>({a: true})
// desired `typeof toggled` === {a: string}

Possible hacks I know for the present time: typescriptlang.org/play

I also tried with infer in all possible places, but nothing

essenmitsosse commented 3 years ago

Just some idea: how about questioning why the order of type arguments matter, and instead use an unordered, but named way to pass them.

This would be analogue to how these two functions handle similar arguments in different ways:

/// VERSION A
const foo = (a?: number, b?: string, c?: boolean) => {}

/// VERSION B
const foo = (args: {a?: number, b?: string, c?: boolean}) => {}

The difference is, that Version B allows any argument to omitted, while A only allows C to be omitted or B&C or A&B&C.

For type arguments it could look like this:

const foo = <{TA, TB = TA, TC = TB}>(a: TA, b: TB, c: TC) = {}

foo<{TC: string}>();

This wouldn't introduce any new markers people had to learn, or weird empty lists. It's less verbose, since one doesn't have to go through the list of all arguments that should be skipped. Also this would be nice in general for functions, with several type arguments.

The behaviour (and therefor) implementation would be similar, just that all omitted type argument keys would implicitly be assumed to be interfered. Its basically just as type arguments are currently working, only that you can access them by name.

Bonus points: Pass an interface as a type argument for better reusability

const foo = <{TA, TB, TC}>(a: TA, b: TB, c: TC) = {}

interface TypeArguments {
    TB: number;
    TC: string;
}

foo<TypeArguments>();
lifaon74 commented 3 years ago

@essenmitsosse I really like your idea. This would bring an elegant way to provide Generics !

jasonkuhrt commented 3 years ago

I love that @essenmitsosse, positional is usually inferior anyways.

It might be a bit more complex for the engine to figure out the dependencies, and also easier for users to introduce circular dependencies, but only as a function of the increased expressiveness here and something that can be detected, nice user error message, etc.

supermacro commented 3 years ago

Not sure if helpful, but I ran into a real use case for this and figured it wouldn't hurt to plop it in here:

from the typescript official discord server


Hey friends,

I have the following function:

const route = <T, B>(parser: Decoder<B>, handler: RouteHandler<T, B>) => { ... }

T represents the server output, and B represents the request body of an incoming HTTP request.

I would like callers of route to specify T before they even implement their route - that way they have the compiler guiding them as they implement their route handlers.

route<Cat>(catDataParser, createCat)

Since catDataParser (or any parser in general) already has some type Decoder<U> , then the generic type B on the route definition should be inferred.

Unfortunately, calling route with only a single type argument (such as Cat) means the B is assumed to be unknown.

So my question is: Is there a way of inferring the B type without having to actually specify it when calling the route function?

So again:

// catDataParser : Decoder<{ name: string, color: string }>

route<Cat>(catDataParser, createCat) // --> B is { name: string, color: string }

I know that perhaps currying the function would solve this problem, but then it lends itself to a not-so-good usage:

const curriedRoute = <B>(parser: Decoder<B>) => <T>(handler: RouteHandler<T, B>) => { ... }

So i'd rather not have a curried route if possible.

typeofweb commented 3 years ago

Since #22368 was closed and not merged, is there a simpler PR coming just for supporting partial generics inference?