microsoft/TypeScript

Proposal: Partial Type Argument Inference

weswigham opened this issue ยท 106 comments

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).

My vote is for either 1 or 3b :)

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;

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>();

@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.

@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:

  • No types are provided explicitly => all types are inferred
  • At least one type is provided explicitly => no inference, all unprovided types just take their default

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?

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.

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!

@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:
gcanti/fp-ts#543 (comment)

Thanks in advance.

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.

@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

@ohjames I like that. Further, that:

<T>() => {}

is equivalent to

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

@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?

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.

related: #21984

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.

@lukescott That proposal is here: #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.

@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.

@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?

@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?

@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.

@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.

@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.

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);

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];

Is this the same as #10571?

@miguel-leon Seems to be the case.

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

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?

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.

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

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 ๐Ÿ‘

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...)

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

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.

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];

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<*>.

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

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

  • Actual๏ผš
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]
  • Expect๏ผš
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

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.

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.

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.

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.

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)
  // ...
}

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:

  • keeps the call site clean
  • does not require new syntax
  • much simpler I'd guess to implement and ship
  • probably solves most use-cases or gives people ability to solve it - refactor multiple overloads
  • if you looked at required argument count to allow multiple overloads it would make it cover even more cases

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.

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

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>();

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

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.

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.

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

y-nk commented

Nothing to add except my support for this proposal ๐Ÿ™

Need this.

This would be a great addition to TypeScript. I think, personally, it would make more sense for the generic function to define which generics are inferred (and could be overridden):

function createAction<
    Props extends unknown[],
    Name extends string = infer
>(name: Name): (...args: Props) => { name: Name, props: Props } {
    ...
}

This way, the caller only has to provide the Props generic, and the Name generic would be inferred. But, if for some reason the user wanted to override Name, they could.

Since there is still no real solution to this I find currying to be a pretty viable solution:

const x = <I>(v: I) => <O>((v: I) => O) => O

// fully typed
x<number>(10)<string>((n) => `${n * 2}`);

// partial inference NO.1
x(10)<string>((n) => `${n * 2}`);

// partial inference NO.2
x<number>(10)((n) => `${n * 2}`);

// full inference
x(10)((n) => `${n * 2}`);

That's also what I've landed on @the-yamiteru but when designing APIs that are supposed to be used by both JS and TS developers this is quite an unfortunate solution as non-TS users now asking themselves why there's another function call needed where's often not strictly needed.

@weswigham is there any movement to be expected on this area?

@schickling Yeah that's true. Either way I'm going to assume most of the JS devs use TS and/or they don't mind an extra function call. At least for now. But it's a real pain in the ass when typing system dictates the shape of the implementation.

Is there really any scenario where you would want an error instead of inference? 99% of the time, wouldn't inference also be preferable to using the default?

Only the latter even breaks backwards compatibility, you could turn off inference for type arguments with defaults but it would be a symptom of baggage, not a desirable trait.

IMO inference should just be turned on, add it to TypeScript 5.0 so backwards compatibility will be less expected. It may break a few libraries, and these can be fixed, but it'll lead to a much nicer future for TypeScript.

Having to litter my code with = infer when it should be the default... not such a nice future. Bite the bullet.

I think the only desirable usecase for infer is as an argument in order to infer type arguments that are not as the end of the argument list.

jcalz commented

This is one of those issues that keeps. coming. up. in Stack Overflow questions, both directly ("how can I specify this type parameter and have the compiler infer the rest") and indirectly ("yes this solution works but isn't there some way to get rid of that weird seemingly no-op curried function call you're doing at the beginning?")

In the absence of just making <T, U>(u: U) => xxx work magically with when called like f<T>(u), it would still be so much nicer to be able to write <T, U=infer>(u: U) => xxx instead of the current <T,>() => <U,>(u: U) => xxx with f<T>()(u), or <T, U>(dummyT: T, u: U) => xxx with f(null! as T, u).

This is my proposal. Right now it's either all or nothing. But we can provide a default type which might prove useful. We might assign a special keyword (infer is the most logical here) as a default type which would tell the TS server that this type should be inferred.

I also believe it should be possible to infer an extended or non-extended type as seen below. Another important feature is type skipping which should work the same way as array destructuring (const [, , c] = arr) since types in a type generic are basically an array of types.

// Proposed syntax
const test = <
  A, // required
  B = infer, // inferred
  C extends string[], // required, extended
  D extends Record<string, number> = infer // inferred, extended
>(a: A, b: B, c: C, d: D) = { ... };

// Types
type AA = boolean;
type BB = "hello" | "ahoj";
type CC = ("one" | "two" | "three")[];
type DD = Record<"one" | "two" | "three", number>;

// Values
const a = true;
const b = "hello";
const c = ["one", "two"];
const d = { "one": 1, "two": 2 };

// Full inference
const x = test(a, b, c, d);

// Partial inference
const y = test<AA, , CC>(a, b, c, d);

// No inference
const z = test<AA, BB, CC, DD>(a, b, c, d);

I would prefer a solution without defining anything as on method call. I want to define it in the method declaration.

Something like:

class MyService {
  public showModal<R, T?, D?>(component: ComponentType<T>, data: DialogConfig<D>): DialogRef<T, R> {
    return this.foo(component, data);
  }
}

In this case I only want to set the return value type R. The other should resolved from args or unknown (default behavior).

// This is expected, but does not work. All 3 types are required.
// Optional generics are not wanted, because I want to infer from args.
const result = await this.showModal<string>(MyComp, MyData);

// With your proposal it would work. But is more to write on every call.
// `infer` or `*` or `?` whatever...
const result = await this.showModal<string, infer, infer>(MyComp, MyData);

I would suggest to just change the behavior. We could declare generics with T? but actually the generics already are optional when not set manually. And required generics are also not build-in supported. But works with something like <T = void> which forces to set types.

Therefore just still handle the remaining generics as currently work when no type is set. Infer from args or fallback to unknown. I see no breaking change here. ...

Anyway, I would suggest the other direction than described here. Define explicit optional generics which infer from args when not set. Like <A, B?, C?>. Instead of define types on every use like this.foo<number, infer, infer>().

I would love to see this happen. As was mentioned earlier - this comes up over and over again. I also agree that declarations should be able to opt into having an unspecified type parameter infer rather than blindly using some default (See also #16597 which this would maybe be a dupe of if that's the shape it took).

The flip side (which maybe deserves its own issue) would be making it an error to explicitly specify a parameter. I.e. function foo<T, private U = infer> which would be illegal to pass an explicit parameter to (or similar for type Foo<T, private U = T[]> which could memoize complex derived types without worrying that a user will accidentally clobber it).

I would prefer a solution without defining anything as on method call. I want to define it in the method declaration.

I would prefer to just fix the mistake in the language even though it'll break backwards compatibility in rare cases. Why force the user to type more at the call site or the definition site just because typescript inherited a bad decision from C#? Fix it instead. Lots of TypeScript changes are not backwards compatible anyway.

I would prefer a solution without defining anything as on method call. I want to define it in the method declaration.

I would prefer to just fix the mistake in the language even though it'll break backwards compatibility in rare cases.

I'm not convinced it's a mistake. There are benefits and use cases for the way it works currently, and even if it's strictly better it's still not clear to me that any potential benefit of changing it would be worth the effort it would take, or other problems it would cause.

In particular, there's value in requiring users to explicitly specify type arguments in certain cases, so we'd need a way to opt out of automatic inference as well. And the mental model of when a default is used vs. an overload vs. inference would be quite a bit more complicated. To compare with a different feature in a different language, I think most people agree that Kotlin's choice of "final by default, opt-in to mutability" is superior to Java's opposite choice, but we don't see people calling for Java to switch the default because the cost outweighs the benefit - you can still express the same thing in both languages - it's not quite as ergonomic, but it would be worse to try to change it at this point.

I would rather the syntax be a simple and intuitive as possible, even if it breaks compatibility. That compatibility break can be minimized with an opt-in flag. This isn't outside the norm for TypeScript, as we have flags like strictNullChecks, which does break code when you change the option.

That's assuming compatibility is broken as we need a working prototype and backing tests for it. Correct me if I'm wrong, but it's all been theory crafting up to this point. The issue is 4+ years old now.

I'm not convinced it's a mistake. There are benefits and use cases for the way it works currently

Maybe in terms of the compiler speed or simplifying the compiler's implementation, but for the user, I don't think there are.

In particular, there's value in requiring users to explicitly specify type arguments in certain cases

Absolutely, and you always have the choice to write them out explicitly when you feel you have one of those cases, but in this issue there are a great many examples where it's not only lacking any value, but harmful or impossible.

so we'd need a way to opt out of automatic inference as well.

We can't do that in the "nothing" case so why would we want to do it in the "partial" case? Opting out just makes the code fail to compile except in one case, when the latter arguments provide defaults, in which case the default is chosen over partial inference. But I think the cases where this is actually useful are maybe one in ten thousand at best, if they even exist, as I'm pretty sure in most cases where a default is used in later positions, partial argument inference isn't a viable compiler path. You could always disable partial argument inference for arguments that have defaults and then the impact to existing programs would be zero. This would be a much safer approach but I think it would also reduce utility.

And the mental model of when a default is used vs. an overload vs. inference would be quite a bit more complicated.

I'm not sure it would, inference in the "nothing" case isn't difficult to deal with, and making the distinction between "all" and "nothing" feel pretty arbitrary. C++ has partial template parameter inference and it enables a lot.

To compare with a different feature in a different language, I think most people agree that Kotlin's choice of "final by default, opt-in to mutability" is superior to Java's opposite choice, but we don't see people calling for Java to switch the default because the cost outweighs the benefit - you can still express the same thing in both languages - it's not quite as ergonomic, but it would be worse to try to change it at this point.

I don't think this is a good comparison though, that is a change which would affect every single program in existence. The change we're discussing above would only affect the extremely small number of programs that already compile, that rely on choosing the "default" above whatever partial argument inference would choose, and I described how low impact I think that would be above and how to remove the impact by disabling partial inference for arguments with defaults.

Not to be that guy, but the way rust does partial generic typing is probably the best out there IMO. Just stick an _ when you want the compiler to infer a parameter and if it can't it will fail to compile and tell you. No fluff, it just works.

Not to be that guy, but the way rust does partial generic typing is probably the best out there IMO. Just stick an _ when you want the compiler to infer a parameter and if it can't it will fail to compile and tell you. No fluff, it just works.

This.

It would not have problems of backwards compatibility and would allow using inference in parameters in the middle while defining the latter parameters explicitly (like <MyType, _, OtherType>).

@lucasbasquerotto:

would allow using inference in parameters in the middle while defining the latter parameters explicitly (like <MyType, _, OtherType>).

We need a way of naming them to be able to use them, tho, something like that:

const getFormFieldValue = <
  Form extends Record<string, unknown>,
  infer FieldName = keyof Form
>(fieldName: FieldName): Form[FieldName] => ...

and

const value = getFormFieldValue<AccountForm>('address')

so the FieldName would be inferred from use (from that argument I passed, so it would be FieldName: 'address'), and then the inferred type would be checked against keyof Form to produce an error if Form does not have that key.

Right now we are forced to do something like this:

const value = getFormFieldValue<AccountForm, 'address'>('address');

which makes sense, but forces to type 'address' twice, which feels kinda... uncomfortable - because declaration happens once and I'm perfectly fine with it being 'eloquent', but usage then happens many times and I prefer it to be as minimalist as possible...

Playing devils advocate here for a bit.

@andrienko Couldn't you write your example like this?

type AccountForm = {
    address: string
}

const getFormFieldValue = <
    Form extends Record<string, unknown>
>(fieldName: keyof Form) => { // optionally you can do `): Form[typeof fieldName] => {`
    const f = {} as Form // just added this to make func signature work since original was unspecified
    return f[fieldName]
}

const v = getFormFieldValue<AccountForm>("address") // v is string

I get it would be nice for it to be more explicit, but there is a way to make it work. A good case for omitting leading types though.

I think there are several use cases here. In one, where it's generally reasonable to either explicitly provide the type or to infer it, the Rust-like _ solution works well. In other cases, APIs can be designed where users should never be providing an explicit type for a particular template parameter, and it should always be inferred (in fact, I'd like to be able to make it an error to provide it explicitly). In that case, the call-site _ solution isn't so good, and a declaration-site solution is a much better fit.

One example of the latter use case is a testing library that specifically tests type inference. Making up some syntax (private to say it's not even allowed to specify explicitly, and satisfies to force a type error when the condition is not met (see #23689)), this might look like:

function assertType<T, private infer U satisfies SameType<T, U>>(expr: U): void {}
type SameType<T, U> = T extends U ? U extends T ? unknown : never : never;

Designing such an API where it's required to always call it as assertType<Foo, _>(new Foo()) seems a little yucky.

btoo commented

what use-case does partial type argument inference cover that isn't already covered by satisfies?

It's not clear to me what satisfies would do here at all? The intended usage is to write

assertType<Foo>(someExpression());

Without partial inference, you can either write assertType<Foo, Bar>(...) (or provide a default for U) in which case the actual type of the expression isn't taken into account (aside from ensuring assignability). Or you can write assertType(...) which infers U but doesn't allow specifying the first. But there's no way to both have the user specify T and have the type checker infer U at the same time.

btoo commented

If I'm understanding your syntax correctly,

function assertType<T, private infer U satisfies SameType<T, U>>(expr: U): void {}
type SameType<T, U> = T extends U ? U extends T ? unknown : never : never;
assertType<Foo>(someExpression() as Bar)

would fail to compile because SameType<Foo, Bar> evaluates to never and Bar doesn't satisfy never.

What's the point of inferring a second type parameter when just one would suffice?

function assertType<T>(expr: T): void {}
assertType<Foo>(someExpression() as Bar)
                ^
                Argument of type 'Bar' is not assignable to parameter of type 'Foo'.

Thinking about it more, does it even make sense to assert a compile-time construct like type in the runtime? Perhaps

someExpression() satisfies Foo;

would be more appropriate.

You're misunderstanding the point. This is a compile-time-only construct - it does nothing at runtime. It has two types involved: T is the type you're expecting (and therefore must be spelled out explicitly in the <>), and U is the actual type of someExpression(). This type must be inferred: if the user provides an explicit type here (either with an as assertion, or an explicit second argument in the <>) or if it blindly uses a non-inferred default, then it's not achieving its purpose. I'm sorry about writing any Bar at all in an earlier negative example, since it's a red herring - the user must not type it. You're correct that SameTime<...> evaluates to never, but note that this happens if either T or U doesn't extend the other: they have to be exactly the same type (or else a non-strict type like any is in the way somehow - I haven't figured out how to deal with that). This is (still) a stricter requirement than just assignability, which is what satisfies by itself is checking. As a library API designer, I often care about the exact type of something, not just whether it's assignable to something else, so this is useful, but I vaguely recall a number of other situations where it's come up as well.

Thinking more about this in light of satisfies, I suppose the following would also work

type Exactly<T> = (arg: T) => T;
function exactTypeOf<T>(arg: T): Exactly<T> { return null!; }
// ...
exactTypeOf(someExpression) satisfies Exactly<Foo>;

though I'd argue the readability and ergonomics may be slightly poorer here as compared to something like assertExactType<Expected>(actual) which would hopefully be nearly impossible to misunderstand or to misuse.

btoo commented

Sorry if i'm still misunderstanding you, but I think I do understand you. It's as you say

This is a compile-time-only construct - it does nothing at runtime.

So why would you choose to leave any runtime footprint (even if it's only a function call returning null) for something that ought to just be erased away completely and cleanly after compilation? This is what I meant by

does it even make sense to assert a compile-time construct like type in the runtime?

and it's precisely the reason satisfies was introduced:

The safest workaround is to have a dummy function, function up<T>(arg: T): T:

let a = up<boolean>(true);

which is unfortunate due to having unnecessary runtime impact.
Instead, we would presumably write

let p = { kind: "cat", meows: true } satisfies Animal;

Having said this, there's a small correction I need to make to your code, which I'll, in the same stroke, use to demonstrate why your Exactly and its runtime function exactTypeOf are obsolesced by satisfies (playground link)

const TMustExtendU = { a: 1, b: 2 } satisfies { a: 1 } // Type '{ a: 1; b: number; }' does not satisfy the expected type '{ a: 1; }'.
const UMustExtendT = { a: 1 } satisfies { a: 1, b: 2 } // Type '{ a: 1; }' does not satisfy the expected type '{ a: 1; b: 2; }'.
const exactlyWithoutAnyRuntimeFootprint = { a: 1, b: 2 } satisfies { a: 1, b: 2 } // ok

I feel like i'm creating a strawman argument here though, and I'm not trying to get this thread off-topic. Perhaps we can borrow from the other purported use cases in this thread:

  • #26242 (comment) and others try to provide only a single string type param to declare function foo<A,B,C>(): [A, B, C]; at any type param index and without providing any other type params, seeking to have the other type params inferred from the context. This can be solved using satisfies:
    const bar1 = foo() satisfies [string, any, any] // [string, any, any]
    const bar2 = foo() satisfies [any, string, any] // [any, string, any]
    const bar3 = foo() satisfies [any, any, string] // [any, any, string]
    but i think this would be an even better demonstration:
    declare function foo<A,B,C>(a: A, b: B, c: C): [A, B, C];
    const bar1 = foo('', 2, true) satisfies [string, any, any] // [string, number, boolean]
    const bar2 = foo(2, '', true) satisfies [any, string, any] // [number, string, boolean]
    const bar3 = foo(2, true, '') satisfies [any, any, string] // [number, boolean, string]
    or even more pedantically:
    declare function foo<A,B,C>(a: A, b: B, c: C): [A, B, C];
    const bar1 = foo('', 2, true) satisfies ReturnType<typeof foo<string, any, any>> // [string, number, boolean]
    const bar2 = foo(2, '', true) satisfies ReturnType<typeof foo<any, string, any>> // [number, string, boolean]
    const bar3 = foo(2, true, '') satisfies ReturnType<typeof foo<any, any, string>> // [number, boolean, string]
  • #26242 (comment) tries to get [number, null] from
    declare function foo<A, B = any>(b: B): [A, B];
    foo<number>(null);
    and even goes to add a default = any for type param B just to enable partial type parameterizing. However, with satisfies, we can now just rewrite this as
    declare function foo<A, B>(b: B): [A, B];
    const bar = foo(null) satisfies ReturnType<typeof foo<number, any>>
    // const bar: [number, null]

The big difference here is that instead of adding some new feature like empty ,s or a repurposed infer, we can just use any as a catch-all type parameter that TypeScript uses as a signal to instead use the more specific, inferred type.

I've already conceded that

not all

of this proposal's feature requests are resolved with satisfies, but it does seem like most of the comments here are, at this point, just asking for syntax sugar rather than any game-changing features.

So why would you choose to leave any runtime footprint (even if it's only a function call returning null) for something that ought to just be erased away completely and cleanly after compilation?

Two reasons: (1) this is in a test file, not production code, so there is no runtime footprint; and (2) any reasonable optimizer will inline the empty function call so that there's still no runtime footprint. I honestly can't imagine how someone can care about the runtime or codesize footprint of something and not be using a reasonable optimizer.

and it's precisely the reason satisfies was introduced:

The safest workaround is to have a dummy function, function up<T>(arg: T): T:

Full disclosure, I honestly agree with that sentiment - again, because I lean heavily on my optimizer and so dummy functions (e.g. no-op-in-production asserts arg is Type functions) are free for me. I'm still not really on board with why we need a language-level construct when a reasonable optimizer can do it in userland. But I think there are uses of satisfies that you can't do in userland, and I'd love to see those pursued.

Having said this, there's a small correction I need to make to your code, which I'll, in the same stroke, use to demonstrate why your Exactly and its runtime function exactTypeOf are obsolesced by satisfies (playground link)

const TMustExtendU = { a: 1, b: 2 } satisfies { a: 1 } // Type '{ a: 1; b: number; }' does not satisfy the expected type '{ a: 1; }'.
const UMustExtendT = { a: 1 } satisfies { a: 1, b: 2 } // Type '{ a: 1; }' does not satisfy the expected type '{ a: 1; b: 2; }'.
const exactlyWithoutAnyRuntimeFootprint = { a: 1, b: 2 } satisfies { a: 1, b: 2 } // ok

I think we're talking past each other here. I still don't understand what your example has to do with my use case. You're presupposing you know both types. What I have is an expression and an expected type, and I want to make sure that the typeof the expression is the same as (not just assignable to) the expected type. Your example has two values and two types. Here's the closest midpoint I can come up with between my use case and what you're trying to write:

const expr = someExpression() satisfies ExpectedType;
null! as ExpectedType satisfies typeof expr;

Note how there are only two inputs here. Yes, this isn't terrible to copy for various expressions and types, but it is pretty awkward. What I want is a one-liner. The expectTypeOf(someExpression()) satisfies Exactly<ExpectedType> does that, as does assertExactType<ExpectedType>(someExpression()), though the latter requires partial type argument inference at the very least.

I feel like i'm creating a strawman argument here though, and I'm not trying to get this thread off-topic. Perhaps we can borrow from the other purported use cases in this thread:

Agreed, I also do not want to get off-topic, but I don't know where else to move this discussion. It's still semi-relevant to the question of why do we need partial inference.

  • Proposal: Partial Type Argument Inference #26242 (comment) and others try to provide only a single string type param to declare function foo<A,B,C>(): [A, B, C]; at any type param index and without providing any other type params, seeking to have the other type params inferred from the context. This can be solved using satisfies:
    const bar1 = foo() satisfies [string, any, any] // [string, any, any]
    const bar2 = foo() satisfies [any, string, any] // [any, string, any]
    const bar3 = foo() satisfies [any, any, string] // [any, any, string]

I don't think this example makes any sense. The whole point of satisfies is that it doesn't actually affect inference. The whole point of partial type argument inference is that it does affect inference. These things are not compatible. You can't take a feature that doesn't affect inference and use it to change how type inference works? (see below)

but i think this would be an even better demonstration:

declare function foo<A,B,C>(a: A, b: B, c: C): [A, B, C];
const bar1 = foo('', 2, true) satisfies [string, any, any] // [string, number, boolean]
const bar2 = foo(2, '', true) satisfies [any, string, any] // [number, string, boolean]
const bar3 = foo(2, true, '') satisfies [any, any, string] // [number, boolean, string]

In this case, you've added arguments to foo and all you're doing is verifying that it inferred correctly. Because the arguments are mapping 1:1 with the type parameters, you don't really need the explicit type parameters at all. So this isn't really a use case. The point is to specify type parameters that can't be inferred, while still inferring those that can.

  • Proposal: Partial Type Argument Inference #26242 (comment) tries to get [number, null] from

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

    and even goes to add a default = any for type param B just to enable partial type parameterizing. However, with satisfies, we can now just rewrite this as

    declare function foo<A, B>(b: B): [A, B];
    const bar = foo(null) satisfies ReturnType<typeof foo<number, any>>
    // const bar: [number, null]

Okay, I'll admit that I misunderstood how satisfies affects inference (hence the crossed-out paragraph above). My previous mental model was that it had no effect, but merely verified the type of some expression without affecting how it was inferred. I see now that it actually does do reverse inference when the satisfying expression has some degrees of freedom. So a better model would be that expr() satisfies T is sugar for inserting a const expr1: T = expr() and then using expr1 instead.

Given that, it looks like many of the use cases for partial inference are indeed satisfied (p.i.) by satisfies. However, I feel like we're still talking past each other because I made the same argument for why satisfies was unnecessary: because it can basically do the same thing as what you could basically already do in userland with a few identity functions, provided we had partial inference:

function satisfies<T, infer U extends T>(arg: U): U { return arg; }
function upcast<T>(arg: T): T { return arg; }

You and others might object to this on the grounds that it has runtime impact, and as I said above, I don't find this argument at all compelling because optimizers exist. On the other hand, I find repurposing the new satisfies operator to obsolete the partial inference use cases here to be horribly unergonomic: you have to know the exact type and add a bunch of explicit anys in various places - it's a lot more repetitive and awkward.

I've already conceded that not all of this proposal's feature requests are resolved with satisfies, but it does seem like most of the comments here are, at this point, just asking for syntax sugar rather than any game-changing features.

So maybe this is just a case of "first mover wins". These are roughly isomorphic features in terms of what new functionality they unlock, but they have different specializations that speak to different people differently. I find satisfies pointless because of my optimizer-based presupposition that trivial function calls are free (and so to my mind, it's just syntax sugar and not game-changing), whereas you find partial inference pointless because most of what it can do is now possible with satisfies, albeit with a little more verbosity, which (to my mind) is pretty significant. For the lack of partial inference, I still end up jumping through weird hoops, like introducing extra (curried) calls, or duplicating type names - all of this is errorprone. If I'm looking to design with correctness in mind, I want to minimize the possible ways of holding my API wrong (e.g. in my original assertExactType example, I want to ensure a test doesn't pass when it's supposed to fail). The more boilerplate and redundancy you have, the more likely you are to use it incorrectly.

It's not super clear what satisfies is since I haven't dug into it. But I'll add my perspective to this. Given a function like this:

function someFunc<A, B>(a: A, B: B) {
 // ...
}

You can do either of these:

someFunc<Type1, Type2)(a, b)
// OR
someFunc(a, b)

But you can't do:

someFunc<Type1>(a, b)
someFunc<_ Type2>(a, b)

If you declare a default:

function someFunc<A, B = A>(a: A, B: B) {
 // ...
}

Then you can do:

someFunc<Type1>(a, b)

(This wasn't always the case. Support for optional defaults was added about 5 years ago)

The issues are someFunc(a, b) has the ability to infer the types from a and b, and someFunc<Type1>(a, b) should be able to do the same. But it's an all or nothing unless you specify a default.

There are cases where a or b isn't concrete (an object with properties) so it can't be interred, but the other type can.

I'm personally not a fan of the sigul thing with someFunc<_ Type2>(a, b). I would be happy just to have the ability to do someFunc<Type1>(a, b).

If satisfies somehow makes this work, cool. But the examples don't seem like it to me, especially with examples like foo(2, true, '') satisfies [any, any, string] . It feels like we are talking about completely different things.

The issues are someFunc(a, b) has the ability to infer the types from a and b, and someFunc<Type1>(a, b) should be able to do the same. But it's an all or nothing unless you specify a default.

This is slightly inaccurate. Specifying a default doesn't get you inference - for Type2, it just blindly fills in the default. I think the ask on this issue is that there be some way for the default to be "infer this". This should not break anything: it's opt-in at the definition, so all existing code would be fine. Now this naturally leads to the question of how you can infer (or use the default) for a middle parameter (similar to passing undefined in the value space), but I'm not sure we need to solve that right now, since the use case is much more limited.

If satisfies somehow makes this work, cool. But the examples don't seem like it to me, especially with examples like foo(2, true, '') satisfies [any, any, string] . It feels like we are talking about completely different things.

In many cases it does seem to, but it's not pretty.

Here's another example that just came up today. We were looking for a convenient way to make an object with a bunch of tagged-primitive elements:

type Bytes = number & {__tag__: 'bytes'};
const Sizes = withTags<Bytes>({
  Kilo: 1000,
  Kibi: 1024,
  Mega: 1000000,
  Mebi: 1048576,
} as const);

where Sizes would be typed as {Kilo: Bytes, Kibi: Bytes, Mega: Bytes, Mebi: Bytes}. With partial inference, I could write this as

type WithoutTag<T extends number|string> = T extends number ? number : string;
function withTags<T extends number|string, infer U extends Record<string, WithoutTag<T>>>(obj: U): {[K in keyof U]: T} {
  return obj as any;
}

Without partial inference, it's impossible to both specify the expected output type and get any inference on the argument (i.e. not need to repeat all the keys, etc). This seems like it should be doable with satisfies, but the inferencer seems to choke on it:

const Sizes = withTags({...} as const) satisfies Record<string, Bytes>;

(and it also just looks really weird and confusing).

@shicks it is not beautiful, but your case can be implemented like this:

const withTags = 
  <T extends number|string>() => 
    <U extends Record<string, WithoutTag<T>>>(obj: U): {[K in keyof U]: T} => 
      obj as any; 

TS Playground

The funny thing is, this shows TypeScript is capable of doing this; it just needs some concept of curried generics. That empty runtime call tricks TypeScript into doing what we want it to do.

That's right: it works, but the usage is terrible: withTags<Bytes>()({...}) isn't really acceptable, and would not pass the readability requirements for an API.

For public APIs, it's awful, I agree. But my main point was that TypeScript could handle this kind of logic easily; it just needs a nicer syntax that doesn't require runtime shenanigans.

We should NEVER be forced into changing our runtime just to satisfy the type system. NEVER.

I quite frequently bump my head against this. I want to provide one type parameter and infer another and I end up having to add currying which feels very inelegant and is an obvious workaround to accommodate typescript which I wouldn't have had if I were writing straight javascript. As others have mentioned that shouldn't happen.

I like many of the proposals in here, but I'd gladly take any choice over no choice for another 4 years. Please pick one and run with it. We'll be able to use it whichever you end up going for. Thank you! :)

As for which one to choose I also like reusing the infer keyword rather than introducing something completely new. That said I would also like to not have to specify anything at all. I understand that there are backward compatibility issues with that. What I've seen so far is this proposed solution:

const test = <
  A, // required
  B = infer, // inferred
  C extends string[], // required, extended
  D extends Record<string, number> = infer // inferred, extended
>(a: A, b: B, c: C, d: D) = { ... };

// Partial inference options:
const k = test<AA,, CC>(a, b, c, d);
const l = test<AA, *, CC>(a, b, c, d);
const m = test<AA, infer, CC>(a, b, c, d);
const n = test<AA, auto, CC>(a, b, c, d);

I would like to be able to specify that a type parameter should always be inferred by specifying inferonly which means that those typeparameters are stripped from the signatures and cannot be explicitly provided, like this:

const test = <
  A, // required
  B = inferonly, // inferred, cannot be specified
  C extends string[], // required, extended
  D extends Record<string, number> = inferonly // inferred, extended, cannot be specified
>(a: A, b: B, c: C, d: D) = { ... };

// Usage:
const x = test<AA, CC>(a, b, c, d);

// Invalid:
const y = test<AA, BB, CC>(a, b, c, d); // The B and D typeparams are stripped from the signature as they are always inferred.

I would also like to draw extra attention to @essenmitsosse's proposed solution which I found interesting. Though I do understand if that's too large of a change that it may not be considered.

Q: is _ a valid option for an elided type marker? It seems like effective markup in Rust

We've been discussing this issue for 2 years, what's the next move? Can something be introduced as experimental that we can enable in the tsconfig? Why isn't Partial Type Argument Inference available?

shicks commented

There are a few separate questions here, and I think also a few separate (but related) proposals. I tried to write this (and some other items on my wishlist) up in a gist.

But to summarize the relevant bits here:

  1. The inference could be opted-in at either declaration site or use site. I think this issue is talking more about declaration site, while I read #16597 as being more about use-site (though some of the above comments have addressed both, i.e. the test<AA,, CC>, test<AA, *, CC>, or test<AA, infer, CC> syntax).
  2. I like the = infer syntax, and treating it exactly the same as other default initializers. Thus, the example above where someone wrote <A, B = infer, C extends string[], D extends Record<string, number> = infer> would be illegal, since all optional parameters must go after all required parameters.
  3. The proposal of inferonly lines up neatly with what I'd write as private T = infer (see gist, requires the additional private keyword for internal type aliases).
  4. _ would not be a good placeholder, since it's a valid identifier and could be a type name in the enclosing scope. The same is probably true of infer, so I don't really know what the best syntax for use-site inference would be.

I would love to see some traction on any/all of the issues I bring up in the gist, but I feel like I've been talking to a brick wall.

  • The proposal of inferonly lines up neatly with what I'd write as private T = infer (see gist, requires the additional private keyword for internal type aliases).

Though I'd happily use the private prefix syntax I do prefer inferonly. I have a few reasons:

  1. In order to keep concepts separate. In my view, public, protected private lives in javascript land while inference lives in typescript land, and since this is a typescript problem we might be better served by keeping to typescript concepts.
  2. By using = infer or = inferonly we use a syntax which follows the idea of "what is the default?". The default is whatever is after the equals sign. It could be an explicit type or how to infer that type.
  3. I'm a bit of a stickler for lining things up, and having an optional prefix (private) messes with lining things up (when you line break on typeparameters), unlike an optional suffix (inferonly).

Though public, protected, and private are typescript concepts. JavaScript has # prefixes instead. So using private within a type context seems quite reasonable.

Dang, I didn't think that one through, did I? I bow my head in shame. Thanks for the correction @Nokel81

TL;DR

The current proposal addresses the original issue in a harmful manner while better and more elegant solutions exist.

Do NOT mess with DX

Many users have weigh in to share the importance of this feature.
As a library maintainer, I cannot stress enough how important it is to not put the burden on the end user/developer.

The current proposal - having the consumer of the type add some sigil to make inference happen - may be the kind of mistake TypeScript will never be able to get rid of. This will obliterate the DX throughout TypeScript entirely.

//////////////////////////////////////////
// Welcome to a developer's inner voice //
//////////////////////////////////////////

// I want to create an object of type `SuperLibraryObject` :
const libObj: SuperLibraryObject<string> = { foo: 'bar' };

// ... later in my code
// Hum, libObj does not behave as I expected, maybe I should add the infer sigil.

const libObj: SuperLibraryObject<string, _> = { foo: 'bar' };

// It still doesn't work. Maybe there is another one ?

const libObj: SuperLibraryObject<string, _, _> = { foo: 'bar' };

// Nope. I'll check in the library .d.ts file...
// So there is an infer type but I'm not sure if I should use it or not, I don't know the internal of this lib. 
// This lib is lame! It's 2023, why do I still have to deal with nonsense like that... Why can't I have a lib that just handle the logic by itself ?!

The default behavior MUST be handled in the type declaration.

Breaking BC

Breaking BC would be a big deal. However, this feature brings so many benefits that I would be ok with it, with a special version leap, like going from TS 5.x to TS 10.

However I think we can bring this feature without breaking BC. @shicks brought good solutions that would additionally solve other issues.

A Single Requirement

After so many posts, parties seem to have forgotten the requirement that gave birth to all of these proposals and PR.

Be able to infer an type parameter even if the user passes some type parameters

Solution: Private Types

Private types are easy to learn, have no surprising behavior, and do not break BC.

I'll put it in plain text here, but the idea originates from @shicks.
Introducing private types :

// private types cannot be set by the type consumer,  they are always inferred.
declare function fn<T, private U = BasedOn<T>>(arg0: U): void;

// private types, just like defaults, can only be defined at the end of the declaration.
declare function fn<T, private U = BasedOn<T>, V>(arg0: U): void; // illegal

// private types can be set by the type itself
type RecursiveType<
  T extends ReadonlyArray<unknown>, 
  private IsRoot extends boolean = false
> = T extends [infer Head, ...infer Rest] ? RecursiveType<Rest, true> : [];

Additionally, they avoid us to write boilerplate to make a type fool proof :

// Have you ever seen this ?

// Internal type
type InternalRecursiveType<
  T extends ReadonlyArray<unknown>,
  private IsRoot extends boolean = false
> = T extends [infer Head, ...infer Rest] ? InternalRecursiveType<Rest, true> : [];

// Public -exported- type
export type RecursiveType<T extends ReadonlyArray<unknown>> = InternalRecursiveType<T, false>;

// Now, instead.

// You can write the same thing with private types :
type RecursiveType<
  T extends ReadonlyArray<unknown>,
  private IsRoot extends boolean = false
> = T extends [infer Head, ...infer Rest] ? RecursiveType<Rest, true> : [];

It's 2023, why do I still have to deal with nonsense like that...

By the time I die, I hope to see flying cars, grandkids of mine, and a solution to this problem.

I have faith that I'll see flying cars and grandkids.

Is there any movement on this issue? Would love to see it!

Anything here? This would be extremely helpful to a lot of developers, I'm sure!

Would be great to see any progress on this

@weswigham Is there anything we can work on to make the study of this proposal easier for the team ?
Are there questions the team have that need to be answered, or some cases they would like to see laid out and its behavior described ?

It this hold up due to a limitation of the language, the lack of an implementation, or the indecisiveness of the syntax?

Any update?

https://github.com/microsoft/TypeScript/wiki/FAQ#any-updates (I'll delete this comment if you delete yours, @tomerh2001 )