11

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>({ ...})

3 Answers 3

4

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.

Sign up to request clarification or add additional context in comments.

Comments

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.

Comments

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.