In order to do that, we need to infer each element from the list and check whether p1 and p2 have same types.
To infer each element from list we need to use variadic tuple types. Apart from that , we need to iterate through each element an check p1 and p2.
See this:
type BothEqual<T extends Base<any>> =
T['p1'] extends T['p2']
? (T['p2'] extends T['p1']
? T
: never)
: never
type Validation<
Arr extends Array<unknown>,
Result extends Array<unknown> = []
> =
Arr extends []
? []
: (Arr extends [infer H extends Base<any>]
? [...Result, BothEqual<H>]
: (Arr extends [infer Head extends Base<any>, ...infer Tail]
? Validation<[...Tail], [...Result, BothEqual<Head>]>
: Readonly<Result>)
)
BothEqual just checks whether p1 and p2 have same type.
Validation - iterates through eack element in the list and calls BothEqual on it.
Hence, if elem is valid it returns this element otherwise it returns never:
interface Base<T> {
p1: T,
p2: T
};
type BothEqual<T extends Base<any>> =
T['p1'] extends T['p2']
? (T['p2'] extends T['p1']
? T
: never)
: never
type Validation<
Arr extends Array<unknown>,
Result extends Array<unknown> = []
> =
/**
* If Arr is empty tuple, return empty result
*/
Arr extends []
? []
/**
* If Arr is a tuple with one element, stop recursion.
* See TS 4.7 https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#extends-constraints-on-infer-type-variables docs
* for [infer H extends Base<any>] syntax
*
*/
: (Arr extends [infer H extends Base<any>]
? [...Result, BothEqual<H>]
/**
* If Arr has more than one element - call it recursively
*/
: (Arr extends [infer Head extends Base<any>, ...infer Tail]
? Validation<[...Tail], [...Result, BothEqual<Head>]>
: Readonly<Result>)
)
function f<T, List extends Base<T>[]>(a: [...List] & Validation<[...List]>) {
console.log(a)
}
f([
{ p1: 1, p2: 2 },
{ p1: 3, p2: '42' } // error
]);
Playground
As you might have noticed, second element in provided argument is highlighted.
Also, you can prioritize p1 over p2. It mean that p1 is the source of true. Then, if p2 is invalid only p2 will be highlighted.
Try this:
type BothEqual<T extends Base<any>> =
T['p2'] extends T['p1']
? T
: Omit<T, 'p2'> & { p2: never }
You can simplify Validation type:
type Validation<
Arr extends Array<unknown>,
> =
{
[Elem in keyof Arr]: Arr[Elem] extends Base<any> ? BothEqual<Arr[Elem]> : never
}
But then all elements in the array will be highlighted which might be hard to understand what went wrong.
If you are interested in TS function argument validation and tuple iteration you can check these articles from my blog:
Otype expectsp1andp2but then you usep3andp4. Do you want to make each object with increased by 1 suffix ?