It has come to my attention that utilizing const
type parameters may not always align seamlessly with the usual strategies I employ to guide type inference effectively. For more details, refer to microsoft/TypeScript#53813.
While I cannot claim full comprehension of its inner workings, my typical approach is to simplify things for the compiler to infer as intended. In light of your code snippet, I might consider refactoring it to a version similar to this:
function optionalIfFoo<
K extends string,
const U extends (K extends "foo" ? readonly [] : readonly [unknown])
>(foo: K, ...rest: U): U[0] {
return rest[0]
}
In this updated structure, U
assumes the role previously held by [T]
, and to access T
, we utilize U[0]
. The conditional type has been shifted from the parameter type of rest
to a constraint on U
. Additionally, it embraces the use of conditional types constrained within generics. Moreover, employing readonly
tuples ensures compatibility with the const
type parameter, crucial due to potential idiosyncrasies associated with mutable constraints.
Subsequently, rest
can assume the type of U
, a basic type parameter that typically aids in optimal type argument inference by minimizing the workload for the compiler. This means inferring T
from a value of type K extends 'foo' ? []: [bar: T]
may or may not be feasible, but deriving U
from a value of type U
should ideally prove straightforward.
Lets test the functionality:
const v = optionalIfFoo('notFoo', { a: 42 });
// K: "notFoo", U: readonly [{ readonly a: 42; }]
// const v: { readonly a: 42; }
const w = optionalIfFoo('foo');
// K: "foo", U: readonly []
// const w: undefined
The outcomes appear promising. The compiler deduces U
in the conventional const
manner, ensuring the returned type aligns with expectations.
Playground link featuring the revised code