0

My root problem is following, I need to detect that a type is Array<any> (not any other Array type).

I can detect this with 2 combined conditions : T extends Array<any> and Array<any> extends

How could I write a conditional type without having to resort to a double ternary like following:

type foo<T> = T extends Array<any> ? Array<any> extends T ? T : 'not any array' : 'not any array';
kelsny
  • 23,009
  • 3
  • 19
  • 48
Matthieu Riegler
  • 31,918
  • 20
  • 95
  • 134
  • Why do you need id to be "fixed" `Array <= T <= Array`? – Dimava Mar 29 '23 at 11:47
  • I need to handle a case with `Exclude` but `Exclude, string[]> = never`. – Matthieu Riegler Mar 29 '23 at 11:52
  • Please show the use case, seems like XY problem to me – Dimava Mar 29 '23 at 11:54
  • [Playground](https://www.typescriptlang.org/play?#code/KYDwDg9gTgLgBDAnmYcDCEB2MoQDYaYBmAlgOYA8AKgHxwC8cA2lQD4DOOJmZTAuq0wBXALYAjYFH58A3ACg5oSLATJUAcWAwq1OowCiIAMZ4hAE2DUANHE5RuvAcPGT+NeQqQo4RCBAZwmtoUAIJQUACGiBQA3gC+NO5yAPTJcOkAegD8nmpwYhFQAUE6YZHREZiIifKp6XDZckA). But please assume I know what I'm doing. – Matthieu Riegler Mar 29 '23 at 12:05

2 Answers2

1

Here is a method that works using only 1 conditional type, borrowing the trick from this answer:

type foo<T> = [1[] & T, any[]] extends [0[], T] ? T : "not any array";

There's two checks going on here: one two check for any[], and the other two disallow never[]. To check for any[], we're using the same principle as the linked answer: any allows us to do some crazy things, like assigning 1[] to 0[]. This however, also allows never[] to slip by.

To address never[], we use another check. Because never is the bottom type, nothing is assignable to it,. This means that checking if any[] is assignable to T is all we need.

Example tests:

type T01 = foo<any[]>
//   ^? any[]
type T02 = foo<never[]>
//   ^? "not any array"
type T03 = foo<number[]>
//   ^? "not any array"
type T04 = foo<{ bar: any }>
//   ^? "not any array"
type T05 = foo<{ 1: any }>;
//   ^? "not any array"

Playground

kelsny
  • 23,009
  • 3
  • 19
  • 48
0

Here's how it is possible to do what I assume you want to do (please correct me in comments if needed)

type IfAny<T, Y, N> = 0 extends (1 & T) ? Y : N;
type IfAnyArray<T, Y, N> = T extends ReadonlyArray<infer V> ? IfAny<V, Y, N> : N;
type IsAnyArray<T> = IfAnyArray<T, 'is any[]', 'is not any[]'>


type test_fooz = IsAnyArray<(number | string)[]>
//   ^?
type test_foo0 = IsAnyArray<number[] | string[]>
//   ^?
type test_foo1 = IsAnyArray<string[]>
//   ^?
type test_foo2 = IsAnyArray<any[]>
//   ^?
type test_foo3 = IsAnyArray<readonly string[]>
//   ^?
type test_foo4 = IsAnyArray<readonly any[]>
//   ^?
type test_foo5 = IsAnyArray<MyArray<string>>
//   ^?
type test_foo6 = IsAnyArray<MyArray<any>>
//   ^?
type test_excb = IsAnyArray<(string & {brand: 'myBrand'})[]>
//   ^?
type test_excB = IsAnyArray<(string & {brand?: 'myBrand'})[]>
//   ^?


class MyArray<T> extends Array<T> {
    x!: number;
}

And this is if you want Exclude:

type ExcludeArrayOf<T, E> = T extends ReadonlyArray<infer V extends E> ? IfAny<V, T, never> : T;
 
type test_excz = ExcludeArrayOf<(number | string)[], string>
//   ^?
type test_exc0 = ExcludeArrayOf<number[] | string[], string>
//   ^?
type test_exc1 = ExcludeArrayOf<string[], string>
//   ^?
type test_exc2 = ExcludeArrayOf<any[], string>
//   ^?
type test_exc3 = ExcludeArrayOf<readonly string[], string>
//   ^?
type test_exc4 = ExcludeArrayOf<readonly any[], string>
//   ^?
type test_exc5 = ExcludeArrayOf<MyArray<string>, string>
//   ^?
type test_exc6 = ExcludeArrayOf<MyArray<any>, string>
//   ^?
type test_excb = ExcludeArrayOf<(string & {brand: 'myBrand'})[], string>
//   ^?
type test_excB = ExcludeArrayOf<(string & {brand?: 'myBrand'})[], string>
//   ^?
Dimava
  • 7,654
  • 1
  • 9
  • 24