#37398•Hard

All Except Never

Write a function that accepts every argument type except never. No parameter type can reject never, so the rejection has to move into the argument count.

The usual way to forbid a value is to narrow the parameter type. That move is useless here, because never is the bottom type: it is assignable to every type, including never itself. Whatever you write as the type of x, a never argument slips through.

So myFunc has to accept an argument of any type at all, void, unknown, any, never[], unions, object types, with exactly one exception. A value typed never must be a compile error at the call site.

declare const nothing: never
declare const name: string
 
myFunc(nothing) // expected to be a type error
myFunc(name) // expected to be fine

Challenge Instructions: All Except Never

Hard

Write function myFunc so that it can accept any argument type except type never.

View on GitHub: https://tsch.js.org/37398

Change the following code to make the test cases pass (no type check errors).

ChallengeSolution
/* _____________ Your Code Here _____________ */

function myFunc(x: any) {
  console.log(x)
}

/* _____________ Test Cases _____________ */
declare const x0: never
declare const x1: void
declare const x2: unknown
declare const x3: never[]
declare const x4: 1 | 2 | 3n
declare const x5: string
declare const x6: boolean | number
declare const x7: undefined | { a: 123 } | VoidFunction
declare const x8: string[]
declare const x9: {} | []
declare const x10: any

// @ts-expect-error
myFunc(x0)

myFunc(x1)
myFunc(x2)
myFunc(x3)
myFunc(x4)
myFunc(x5)
myFunc(x6)
myFunc(x7)
myFunc(x8)
myFunc(x9)
myFunc(

Pro Challenge

Get access to all 200+ challenges, including every medium, hard, and extreme one.

Monthly subscription.
Cancel anytime + 30-day money-back guarantee.

Detailed Explanation

The solution in full:

type RejectNever<T> = [T] extends [never] ? [never] : []
 
function myFunc<T>(x: T, ..._block: RejectNever<T>) {
  console.log(x)
}

One type parameter, one rest parameter, and the rejection happens on the rest parameter rather than on x.

The attempt that looks right and is not

The obvious first try is to compute the type of x from T:

[object Object]

Inference works fine. Call it with a never value and TypeScript really does infer T = never, so the parameter type resolves to never. The call is still accepted, because the argument is also never, and never is assignable to never.

declare const nothing: never
naive(nothing) // no error, and the return type is never

That is the lesson of this challenge. No type you can put on a parameter rejects a never argument, because every type accepts the bottom type. Any solution that only touches the type of x is dead on arrival.

Rejecting by arity instead

What TypeScript does check independently of assignability is how many arguments a call supplies. A rest parameter typed with a tuple controls that count, and the tuple can depend on T:

// RejectNever<string> = []      → myFunc takes 1 argument
// RejectNever<never>  = [never] → myFunc takes 2 arguments

T is inferred from x first, in the ordinary way, and only then does the rest parameter's tuple resolve. For every type other than never the tuple is empty and the signature is the one you want, myFunc(x: T).

For never, the tuple has one required element, so calling with a single argument fails:

// @ts-expect-error: Expected 2 arguments, but got 1. ts(2554)
myFunc(nothing)

There is no way to repair that call either. The missing parameter has type never, and no expression has type never, so the second argument cannot be produced. The rest parameter is pure type-level machinery: in a passing call it is empty, so nothing extra is ever passed at runtime.

Why T is wrapped in a tuple

RejectNever<T> asks [T] extends [never], not T extends never. A conditional type with a bare type parameter on the left distributes over unions, and never is the empty union. Distributing over zero union members produces zero results, so the whole conditional collapses to never before either branch is picked.

type Naked<T> = T extends never ? [never] : []
type A = Naked<never> // never, not [never]
type B = Naked<string> // []

Wrapping both sides in a one-element tuple turns off distribution and asks the question literally: is T itself never. The naked version happens to reject the test call as well, but for an accidental reason, and the error it reports is Argument of type '[]' is not assignable to parameter of type 'never', which tells a reader nothing. The bracketed form keeps the resolved type a real tuple and produces the honest arity error.

Edge cases the tests cover

This challenge is originally from here.

Share this challenge

Related Challenges

Learn the Concepts

Become a TypeScript Pro

Track your progress through 200+ hands-on challenges. Free, sign in with GitHub.

Or start solving right away: explore all TypeScript challenges