17

I was hoping that giving a default value to a generic will take precedence over type inference, but it doesn't:

// placeholder for a real express response.send
const sendResponse =  (x) => console.log(x);

function sendResult<T = never>(send: any, result: T) {
  send(result);
}

// I want to use this always with a type
type Num = { x: number };
sendResult<Num>(sendResponse, { x: 1 });
sendResult<Num>(sendResponse, { x: 'sss' }); // correctly showing error

// When I don't supply type, I'd like an error.
// But, T gets inferred instead of defaulting to never... so, no error :-(
sendResult(sendResponse, {x: 1})

See demo

Is there a way to make sure an error is thrown if generic is not provided?

Typescript version: 3.5.2

2
  • What is the use case for this ? Why don't you want inference to happen ? {x: 1} and Num are equivalent types. Commented Jun 20, 2019 at 14:01
  • I want to force anybody who uses sendResult to provide a type they expect to send. Inference causes anything to pass IF a <type> is not provided. I want more type safety ;-) Commented Jun 20, 2019 at 14:07

2 Answers 2

42

I don't understand the use case, since disabling behavior that TypeScript users expect is probably going to cause confusion. But you're the boss.

There is an existing GitHub suggestion at microsoft/TypeScript#14829 to allow a developer to indicate that a particular use of a generic type parameter should not be used for inference. There is no official support for this, but I think we can simulate it by taking advantage of the compiler's behavior when looking at conditional types that depend on an unresolved generic type parameter:

type NoInfer<T> = [T][T extends any ? 0 : never];

That is essentially a no-op on T, since any value you plug in (say string) will come out the other side: [string][string extends any ? 0 : never] becomes [string][0] becomes string. But the compiler sees T extends any and decides to defer the calculation until after T gets resolved. In particular, it can't use the value to infer T from that.

So then we can try

function sendResult<T = never>(send: any, result: NoInfer<T>) {
  send(result);
}

And see how it behaves:

type Num = { x: number };
sendResult<Num>(sendResponse, { x: 1 }); // okay
sendResult<Num>(sendResponse, { x: "sss" }); // error

sendResult(sendResponse, { x: 1 }); // error

The first two cases work as you want because specifying Num gives T a definite value, and NoInfer<Num> is just Num. In the last case, the compiler really has no viable inference site for T and thus it has to fall back to the default never, and you get your desired error.

Playground link to code

So that works as you want, but remember that you might want to rethink this idea, since people who get that error might be confused about what the heck they're supposed to do to make the error go away. People rely on type inference, so you'll need some great documentation if you proceed with this.

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

6 Comments

I wonder though without any official support for something like this how much it can be relied upon. It is conceivable that in the future the compiler might try to be smarter about reasoning about conditional types, and be able to work out that NoInfer<T> is just T. I was thinking about using function currying (somthing like sendResult()(sendResponse, {x: 1}) where T is on sendResult) but then I remembered Anders mentioning that they might try to infer T in such cases as well in the future. Great solution though 😊
Presumably there will always be some way to tell the compiler to defer resolution since some things do depend on it, but I agree that details could change so that this particular NoInfer will stop working and we'd have to do something else to address it. 🤷‍♂️
Wow, smart answer, thanks @jcalz! 1. I think that type inference is not something people always want or expect. 2. I wonder what is the point of being able to provide defaults to generics (like <T = never>) - when they get overridden by inference anyway. Do the defaults do something?
The documentation for this indicates that the default is chosen when the type can't be inferred. That would happen with optional function parameters like function foo<T=U>(x?: T): void where you call foo(). I've more often used defaults in generic types like type Foo<T=U> = {x: T} where just Foo means Foo<U>.
They also kick in when you have multiple type parameters and only specify some (no partial type inference, at least not yet) ex: typescript-play.js.org/#code/…
|
5

Updated answer

The utility type NoInfer is now implemented as an intrinsic type in Typescript 5.4. Yay! 🥳🥳


Old answer

The most modern solution to this appears to be the following utility type:

type NoInfer<T> = T extends infer U ? U : never;

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.