Partial Type Inference

Typescript partial type inference

Type inference works based on an all or nothing principle. In the first case:

fn({ a: 1 })

has no generic type parameters provided so it will infer both:

  • B will be inferred as { a: number } based on the function argument;
  • and A will be inferred as unknown since it's not used anywhere in the function.

In the second case:

fn<Fixed>({ a: 1 })

you have specified one of the generic types and that unfortunately means that type inference will not be used for the rest of the type parameters – therefore:

  • A is specified as Fixed;
  • B is not given so instead of inferring it it will default to {}.

As annoying as this is, it is just how TypeScript works. Your second example with two function calls is the usual workaround to this issue.

Related issue on GitHub.

Partial type inference

You can split t into a generic method on a generic type:

class Foo<TOuter> {
public static void Bar<TInner>(TInner arg) {...}
}
...
int x = 1;
Foo<string>.Bar(x);

Here the int is inferred but the string is explicit.

Partial generic type inference possible in C#?

If you have only two specific types of registration (which seems to be the case in your question), you could simply implement two extension methods:

public static DelegateRegistration Parameter<T>( 
this DelegateRegistration p, string name, T value);

public static ConcreteTypeRegistration Parameter<T>(
this ConcreteTypeRegistration p, string name, T value);

Then you wouldn't need to specify the type argument, so the type inference would work in the example you mentioned. Note that you can implement both of the extension methods just by delegation to a single generic extension method with two type parameters (the one in your question).


In general, C# doesn't support anything like o.Foo<int, ?>(..) to infer only the second type parameter (it would be nice feature - F# has it and it's quite useful :-)). You could probably implement a workaround that would allow you to write this (basically, by separating the call into two method calls, to get two places where the type inferrence can be applied):

FooTrick<int>().Apply(); // where Apply is a generic method

Here is a pseudo-code to demonstrate the structure:

// in the original object
FooImmediateWrapper<T> FooTrick<T>() {
return new FooImmediateWrapper<T> { InvokeOn = this; }
}
// in the FooImmediateWrapper<T> class
(...) Apply<R>(arguments) {
this.InvokeOn.Foo<T, R>(arguments);
}

Typescript: infer type of generic after optional first generic

You want something like partial type parameter inference, which is not currently a feature of TypeScript (see microsoft/TypeScript#26242). Right now you either have to specify all type parameters manually or let the compiler infer all type parameters; there's no partial inference. As you've noticed, generic type parameter defaults do not scratch this itch; a default turns off inference.

So there are workarounds. The ones that work consistently but are also somewhat annoying to use are either currying or "dummying". Currying here means you split the single multi-type-argument function into multiple single-type-argument functions:

type Obj = Record<string, any>; // save keystrokes later

declare const createTaskCurry:
<I extends Obj = {}>() => <O extends Obj>(t: TaskFunction<I, O>) => Task<I, O>;

createTaskCurry()(a => ({ foo: "" }));
// I is {}, O is {foo: string}
createTaskCurry<{ bar: number }>()(a => ({ foo: "" }));
// I is {bar: number}, O is {foo: string}
createTaskCurry<{ bar: number }>()<{ foo: string, baz?: number }>(a => ({ foo: "" }));
// I is {bar: number}, O is {foo: string, baz?: number}

You have the exact behavior you want with respect to your I and O types, but there's this annoying deferred function call in the way.


Dummying here means that you give the function a dummy parameter of the types you'd like to manually specify, and let inference take the place of manual specification:

declare const createTaskDummy:
<O extends Obj, I extends Obj = {}>(t: TaskFunction<I, O & {}>,
i?: I, o?: O) => Task<I, O>;

createTaskDummy(a => ({ foo: "" }));
// I is {}, O is {foo: string}
createTaskDummy(a => ({ foo: "" }), null! as { bar: number });
// I is {bar: number}, O is {foo: string}
createTaskDummy(a => ({ foo: "" }), null! as { bar: number },
null! as { foo: string, baz?: number });
// I is {bar: number}, O is {foo: string, baz?: number}

Again, you have the behavior you want, but you are passing in nonsense/dummy values to the function.

Of course, if you already have parameters of the right types, you shouldn't need to add a "dummy" parameter. In your case, you certainly can provide enough information in the task parameter for the compiler to infer I and O, by annotating or otherwise specifying the types inside your task parameter:

declare const createTaskAnnotate: 
<O extends Obj, I extends Obj = {}>(t: TaskFunction<I, O>) => Task<I, O>;

createTaskAnnotate(a => ({ foo: "" }));
// I is {}, O is {foo: string}
createTaskAnnotate((a: { bar: number }) => ({ foo: "" }));
// I is {bar: number}, O is {foo: string}
createTaskAnnotate((a: { bar: number }): { foo: string, baz?: number } => ({ foo: "" }));
// I is {bar: number}, O is {foo: string, baz?: number}

This is probably the solution I'd recommend here, and is in effect the same as the other answer posted. So all this answer is doing is painstakingly explaining why what you want to do isn't currently possible and why the available workarounds lead you away from it. Oh well!


Okay, hope that helps make sense of the situation. Good luck!

Playground link to code

Inferring partial type parameter list to generic value parameter

In theory, a language could implement the described functionality. It's just that Scala currently doesn't.

First of all, what you're describing simply isn't part of the Scala syntax. When specifying type arguments, it's an all-or-nothing syntax. You either need all of the type arguments, or let the compiler infer them.

Second of all, remember that type inference is not perfect. There are plenty of situations in which a type argument can be inferred by hand, but the compiler cannot figure it out. The Scala compiler is quite impressive at type disambiguation and deduction, but it's not magic!

Finally, this can be expressed using Scala with an intermediate closure class (can be an anonymous class) that holds the first type parameter:

object Foo {
def inferring[A](aa: A => Boolean) = new {
def apply[B, C](bbcc: (B, C)) = bbcc
}
}

And then you can do:

Foo.inferring[Int](_ > 3)("string" -> 123)

As desired.

Automatically infer second generic argument from first one

Now what I want is to only provide the type of the rows (TRow), and
let TypeScript automatically infer the type of the field (TField)
based on the value in the field property.

This looks very similar to partial type inference which is not currently supported in typescript, and it's not clear if it will ever be supported for this particular use case.

But there is one thing you can do - you can have one function that accepts explicit type parameter for TRow, and returns another function that will infer TField from its parameter. The syntax is a bit unwieldy, but it works:

function columnDefinition<TRow>(): 
<TField extends keyof TRow>(def: IColumnDefinition<TRow, TField>) =>
IColumnDefinition<TRow, TField> {
return <TField extends keyof TRow>(def: IColumnDefinition<TRow, TField>) => def
}

export interface IColumnDefinition<TRow, TField extends keyof TRow> {
field: TField;
label?: string;
formatter?: (value: TRow[TField], row: TRow) => string;
}

interface User {
name: string;
birthDate: number;
}

To use it, you call columnDefinition<User>() without any parameters, and then immediately call returned function with column definition object as parameter:

const birthDateColumnDefinition = columnDefinition<User>()({
field: 'birthDate',
formatter: value => new Date(value).toDateString(),
// value inferred as (parameter) value: number
});

Is is possible to make a generic method with two type arguments only infer one of them?

Yes, you always need to specify both types. Type inference only works when you specify all the type arguments.

Yes, the C# compiler cannot infer types based on constraints. It can only infer the types based on the types you pass in as arguments.

On a side note, there is an open issue about improving type inference in this regard, however it doesn't seem to have high priority.

How to infer type at key from types at other keys via function signature?

I found two issues which cause lack of inference.

First, the B extends BigObj<NumMap, StrMap> generic is problematic because specifying NumMap/StrMap directly will simply use those types directly, resulting in number/string instead of inferred keys. Instead, each Map should be its own generic so that each one can be inferred independently.

function makeBigObj<N extends NumMap, S extends StrMap, B extends BigObj<N, S>>(

Secondly, for some reason having the parameter opts: Partial<B> refer to the B generic causes the compiler to expand its type to BigObj<NumMap, StrMap>. This can be worked around by not using the generic for the parameter but rather referring to BigObj directly:

function makeBigObj<N extends NumMap, S extends StrMap>(
opts: Partial<BigObj<N, S>>
): BigObj<N, S> {
return {
nums: {} as NumMap,
strs: {} as StrMap,
init: (() => ({})) as InitFn<NumMap, StrMap>,
...opts
} as BigObj<N, S>;
}

This correctly makes the line with from.x produce an error. The return type for init is also correctly inferred, but the compiler doesn't produce any errors for it. I'd guess this is because Typescript has no exact object types.



Related Topics



Leave a reply



Submit