I’m trying to create a utility type that ensures a function has no optional parameters so that I can reliably check its .length
at runtime. I have this working for specific function signatures, but can’t figure out how to create a general type that allows any number of required parameters.
Here’s what I have so far:
type NoOptionalArgs<F> = F extends (...args: infer P) => any
? undefined extends P[number]
? never
: F
: F;
// These work as expected:
type fn0 = NoOptionalArgs<() => any>; // OK - no args
type fn1 = NoOptionalArgs<(x: string) => any>; // OK - one required arg
type fn2 = NoOptionalArgs<(x?: string) => any>; // never - optional arg
// But I can't figure out how to create a general type:
type fnType = NoOptionalArgs<(...args: any[]) => any>; // becomes never
I want to create a fnType
that:
- Allows functions with any number of required parameters
- Disallows functions with any optional parameters
- Works with any parameter types
For example:
const good: fnType = (x: string) => 'x'; // OK
const bad: fnType = (x?: string) => 'x'; // Should error
Is this possible?
Other approaches that don’t work:
type RequiredArray<T> = T extends any[]
? undefined extends T[number]
? never
: T
: never;
type fnType = <T extends any[]>(...args: T extends RequiredArray<T> ? T : never) => any;
type fnType = <T extends readonly any[]>(
...args: { [K in keyof T]: Exclude<T[K], undefined> }
) => any;
Anon A. Mouse is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
1
Below is a approach to creating a type, that may accepts any function with no optional parameters, and rejects any function with optional parameters. Key challenge is to detect optional parameters in a generic (type) level manner. Optional parameters in TypeScript functions become union types incldung undefined (x?: sring is x:string
| unedfined
).
Reasoning:
-
We start with a helper type that checks a tuple type of parameters
and ensures no element is optional. An optional parameter is
reflected as a type that includes undefined (likeT | undefined
). To
detect that one, we can use a conditional type that returnsnever
if
the parameter is optional. -
We go recrusive trough the parameters list to ensure none of them become optional. If a parameter is optional, the entire type shoud end up as
never
. -
Once that filtered out, we can apply generic function type. The parameters which are “generic type” can adapt our checks of ensuring that the attempt to declare a optional parameter results in to a
type error
.
Approach / Implementation:
// This helper type checks a tuple of parameters `P` and returns a new tuple
// that is the same as `P` if no parameters are optional. If any parameter is
// optional, it returns `never`.
type EnsureNoOptional<P extends any[]> = P extends [infer Head, ...infer Tail]
? // Check if the current head is optional by seeing if `undefined` is assignable
// to it. If yes, return `never`; otherwise, continue recursively.
undefined extends Head
? never
: [Head, ...EnsureNoOptional<Tail>]
: []; // For an empty tuple, we're fine, so just return it.
// `NoOptionalArgsFunction` is a generic function type that takes two type parameters:
// - `Args` for the parameter list
// - `R` for the return type
//
// It uses `EnsureNoOptional<Args>` to ensure that if `Args` contain any optional parameters,
// the entire type collapses to `never`, causing type errors if you try to assign a function
// with optional parameters to this type.
type NoOptionalArgsFunction<Args extends unknown[] = [], R = unknown> =
(...args: EnsureNoOptional<Args>) => R;
// Now we define a general `fnType` that can represent any function with no optional parameters.
// Since `fnType` is generic, it can adapt to any arity and parameter types, as long as none are optional.
type fnType = <Args extends unknown[], R = unknown>(...args: EnsureNoOptional<Args>) => R;
// Usage examples:
const good: fnType = (x: string, y: number) => `${x}-${y}`; // OK
// `Args` inferred as `[string, number]`.
// `EnsureNoOptional<[string, number]>` returns `[string, number]` which is valid.
const none: fnType = () => 'no args'; // OK
// `Args` inferred as `[]`, `EnsureNoOptional<[]>` returns `[]`.
const bad: fnType = (x?: string) => x || 'default';
// Error: `Args` inferred as `[string | undefined]`.
// `EnsureNoOptional<[string|undefined]>` returns `never` -> type error.
In summary the fnType
is now a type that you can use to ensure any function is assigned to contain strictly required parameters, no optional ones. It should now handle functions with any arrangement of input paramaters.
1