How to prevent inferring generic type arguments with TypeScript?
Asked Answered
B

3

10

I have an API/function that is intended to be used only with generic type arguments (it enforces the shape of an argument based on a generic parameter). I want to prevent calling the API without a generic parameter (thus inferring the type from the arguments), because it defeats the purpose of my function and will be confusing to users of the API. I would rather the compiler just enforce that a generic type argument is always required. For example:

function foo<T>(arg: Config<T>) { ... } 

How can I ensure the type argument T is always specified by the caller? i.e. foo<Bar>({ ...})

Bise answered 2/3, 2019 at 4:50 Comment(0)
A
2

You can do this by creating a curried function, so that you first call a generic function that has no way of inferring your generic type, and that creates a function with the given type built-in which you can then pass your data to.

For example:

// Just defining this so the example works
type Config<T> = T;

function foo<T = never>(): (arg: Config<T>) => void {
  return (arg: Config<T>) => {
    // Do Something here
    console.log(arg);
  };
}

interface IExample {
  foo: string;
}

// Error - the generic type of `foo` has defaulted to `never`
foo()({ foo: 'bar' });

// No error
foo<IExample>()({ foo: 'bar' });

TypeScript Playground

Unfortunately it does make the function a little less tidy, but as far as I'm aware currying functions in this way is the only way to get a greater degree of control over how TypeScript attempts to infer (or require) generic types.

Alliterative answered 7/7, 2023 at 0:55 Comment(0)
N
1

It doesn't appears to be a functionality yet. That is all that the docs says on generics and inference:

function identity<T>(arg: T): T { return arg; }
let output = identity<string>("myString");  // type of output will be 'string'
let output = identity("myString");  // type of output will be 'string'

Notice that we didn’t have to explicitly pass the type in the angle brackets (<>); the compiler just looked at the value "myString", and set T to its type.

And that's basically it... No hint on how to write it so that identity("myString") throws an error but identity<string>("myString") doesn't.

Nonah answered 2/3, 2019 at 12:20 Comment(0)
H
0

I had the same question just now, and still not able to rapidly google up the answer, I come up with this option:

function foo<Enabled=false,T=unknown>(arg: Enabled extends false ? never : T) {
  // Implementation.
}

This way foo("value") fails (with default generic arguments arg type evaluates never, thus forbids any assigment to it); foo<1>("value") works (with any value, but false, passed into the first generic argument, T is auto-resolved based on the type of given arg value); and also one can do foo<1,string>("value") to force-check whatever type of arg which is needed.


Perhaps, a better variant:

function foo<
  Enabled extends 1 | 0 = 0,
  T = never,
>(arg: Enabled extends 0 ? never : T) {
  // Implementation.
}

This way:

  • First generic argument is restricted to 1 or 0, thus if ones forget that the first generic argument is just a switch, not the target arg type, it will be immeditely clear, as foo<string>('value') fails.
  • When T defaults never, this will also fail: foo<1>('value') — even with the function "enabled" by the first generic argument, the arg inference does not happen, forcing to specify its expected type via the second generic argument, i.e. foo<1, string>('value').
Hager answered 6/7, 2023 at 19:37 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.