The straightforward approach is to write it your first way, and just let the compiler infer the generic type argument. The additional constraint that the type argument must represent a particular function of the types you care about necessarily complicates things; it's difficult/impossible to have a single type argument work one way with explicit specification and another way with type inference.
One way to get things to work as you request is to add a second type parameter so that you use the first one only for explicit specification and the second one for type inference:
function createTypePredicateForArrayA<
A extends any[],
T = A[number]
>(item: T): MyTypePredicate<T[]> {
return ((value: any) => /* Real code here */ true) as MyTypePredicate<T[]>
}
Let's test it:
const a1 = createTypePredicateForArrayA<number[]>(42)
// const a1: MyTypePredicate<number[]>
const a2 = createTypePredicateForArrayA(42)
// const a2: MyTypePredicate<number[]>
In the a1 case you are explicitly specifying A as number[]. Since TypeScript does not currently support partial type argument inference (as requested in ms/TS#26242), then the T type argument is also specified and not inferred. As such, it ends up falling back to its default of A[number], which means that T is number and you are forced to pass in a number argument for item and you get number[] out.
In the a2 case you are letting the compiler infer A and T. There is no inference site for A, so that just defaults to any[]. But item is an inference site for T, so that defaults to number, and thus the return type is number[].
Another way to do it is to give your function two call signatures; one for type argument inference, and one for manual specification. That is, make it an overloaded function:
function createTypePredicateForArrayA<A extends any[] = never>(
item: A[number]): MyTypePredicate<A>; // manual
function createTypePredicateForArrayA<T>(item: T): MyTypePredicate<T[]>; // infer
Let's test it:
const a1 = createTypePredicateForArrayA<number[]>(42)
// const a1: MyTypePredicate<number[]>
const a2 = createTypePredicateForArrayA(42)
// const a2: MyTypePredicate<number[]>
In the a1 case you manually specify A as number[], which makes the compiler select the first overload, which behaves as you expect. In the a2 case you do not specify the type argument; the first overload fails because there is no inference site for A, and thus it falls back to the never default, and 42 does not match... so it tries the second overload, and now you get the straightforward inference behavior for T as you expect.
So there you go; two relatively complicated approaches to the relatively complicated problem of struggling against the intended behavior of TypeScript's inference with generic functions.
Playground link to code
X | any, since I understand it can distract this problem.