src: https://stackoverflow.com/a/61625296/18328461 ## any 型の分配法則 [[TypeScript 条件型の分配法則|Distributive Conditional Type]] において、[[TypeScript any型|any型]] は特殊な挙動を取り、条件型判定による両方のブランチの型の [[TypeScript ユニオン型|ユニオン型]] を生成する。 ```ts type IsAssignable<Sub, Super> = Sub extends Super ? true : false; // 真偽両方のブランチの型のユニオンを生成 type A1 = IsAssignable<any, string>; // ^^: boolean ``` この挙動と [[TypeScript never型|never型]] の「**すべての型の部分型である**」という [[Bottom typeとは|Bottom type]] の性質を利用することで、`any` 型以外は `false` になるという振る舞いを利用して `any` 型があるかどうかの検証をすることが可能。 ```ts type AnyCheck<T> = T extends never ? true : false; type A1 = AnyCheck<any>; // => boolean type A2 = AnyCheck<42 | "st" | any>; // => boolean type A3 = AnyCheck<42 & any>; // => boolean type A4 = AnyCheck<unknown>; // => flase type A5 = AnyCheck<never>; // => never ``` `AnyCheck<T>` は型引数 `T` が `any` であるときのみ、真偽値のユニオン型 `boolean` を生成する。 `AnyCheck<never>` で `never` が発生するのは、[[TypeScript 条件型の分配法則|Distributive Conditional Type]] において `never` が空のユニオン型として判断され、イテレーションが発生しないことによる仕様であり、直接判定すれば `true` になる。 ```ts type A5 = AnyCheck<never>; // => never type A6 = never extends never ? true : false; // => true ``` `never` 以外は `never` に割当できない一方で、分配条件型では `never` も `true` にはならず、`any` だけを `true` の判定に持ち込める。 ## 簡易的な any 型の検証 これと [[TypeScriptの型の同一性|型の同一性チェック]] を利用して型が `any` 型の影響を受けている (型が `any` 型をもつかどうか) を検証できるはず。 ```ts type Identical<Fst, Snd> = (<T>() => T extends Fst ? 1 : 2) extends (<T>() => T extends Snd ? 1 : 2) ? true : false; type AnyCheck<T> = T extends never ? true : false; type HasAny<T> = Identical<AnyCheck<T>, boolean>; ``` `AnyCheck<T>` で生成される型が `boolean` というユニオン型と同一であるかを検証している。** ```ts type H1 = HasAny<any>; // => true type H2 = HasAny<2 | 3 | any>; // => true type H3 = HasAny<2 & any>; // => true type H4 = HasAny<unknown>; // => false type H5 = HasAny<never>; // => false ``` ただし、このチェックで可能なのはシンプルなケースのみで、`{ fst: any }` などオブジェクトのプロパティの型が `any` であるときなどはより複雑な計算が必要となる。 ### よりシンプルな検証 [[TypeScript 型チャレンジ MOC|type challenge]] の [utils](https://github.com/type-challenges/type-challenges/blob/e77262dba62e9254451f661cb4fe5517ffd1d933/utils/index.d.ts#L13C1-L13C56) にあった型。 ```ts type IsAny<T> = 0 extends (1 & T) ? true : false; ``` 元ネタ https://stackoverflow.com/questions/49927523/disallow-call-with-any/49928360#49928360 `1 & T` で型引数 `T` に `any` 型が渡された場合にのみ `1 & T` は `any` 型に簡約されるため、`0 extends any` は真となる。`1 & never` は `0 extends never` となり偽であり、`1 & unknown` は `0 extends 1` となり偽なので、`any` 型のみが真となる条件判定になっている。 ```ts type I0 = IsAny<any>; // => true type I1 = IsAny<never>; // => false type I2 = IsAny<unknown>; // => false type I3 = IsAny<null>; // => false type I4 = IsAny<1>; // => false type I5 = IsAny<0>; // => false ``` ## オブジェクト型が any 型のプロパティを持つかどうかも含めた検証 まずは、オブジェクト型のプロパティの型を取得するための手段として、[[TypeScript インデックスアクセス型|インデックスアクセス型]] を使って以下のような型を定義する。 ```ts type PropType<T> = T[keyof T]; type ObjectPropsTypes<T> = T extends object ? T extends unknown ? PropType<T> : PropType<T> : never; ``` プリミティブ型を型引数に取った時に、[[JavaScriptのプロトタイプオブジェクト|プロトタイプメソッド]] などの型が取得されてしまうのを防ぐために、`ObjectPropsType<T>` で [[TypeScript Object型とobject型と{}型|object型]] との互換性をまずは検証している。 ```ts type P1 = ObjectPropsTypes<string>; // => never ``` ただし、これだと [[TypeScriptのプリミティブ型とオブジェクト型のインターセクション|branded primitive]] については考慮しきれないため、後でそれを防ぐための方法を外挿する。 ```ts type Br = string & { __brand: 1 }; type P2 = ObjectPropsTypes<Br>; // string | number | ((() => string) & (() => string)) | ((() => string) & (() => Object)) | (() => IterableIterator<string>) | ((pos: number) => string) | ((index: number) => number) | ... 41 more ... | ((regexp: RegExp) => IterableIterator<...>) ``` `ObjectPropsTypes<T>` の `T extends unknown` の箇所では、[[TypeScript 条件型の分配法則|ユニオン型の分配]] を利用して `T` が複数のプロパティを持つときにも対応できるようにしている。 ```ts type D1 = { fst1: { snd1: string; }; fst2: { snd2: number; }; }; type P1 = ObjectPropsTypes<D1>; // { snd1: string } | { snd2: number } type P2 = ObjectPropsTypes<P1>; // string | number ``` `HasAny<T>` と組み合わせて、再帰的に型構築子 `ObjectPropsTypes<T>` を利用することで、 プロパティの型に `any` がふくまれるかを検証できる。 以下、再帰的にオブジェクトのプロパティに `any` 型がふくまれるかを検証する型構築子 `HasAnyDeeply<T>` を定義する。 ```ts type HasAnyDeeply<T> = HasAny<T> extends true ? true : HasAny<ObjectPropsTypes<T>> extends true ? true : [ObjectPropsTypes<T>] extends [never] // ブランド化されたプリミティブ型だと分配がこの箇所で起きるので回避するためにタプル化 ? false : HasAnyDeeply<ObjectPropsTypes<T>>; ``` この型を使うことで、以下のようなプロパティに `any` が含まれるオブジェクト型を検証できる。 ```ts type D2 = { fst1: { snd1a: string; snd1b: number; }, fst2: { snd: { trd: any; }; } } type H1 = HasAnyDeeply<D2>; // => true type H2 = HasAnyDeeply<string>; // => false ``` [[TypeScriptのプリミティブ型とオブジェクト型のインターセクション|branded primitive]] についての考慮として、`[ObjectPropsTypes<T>] extends [never]` の箇所で [[TypeScript 条件型の分配法則|Distributive Conditional Type]] の回避方法を利用しており、そのような型が来た場合でも対応できる。 ```ts type Br1 = string & { __brand: 1 }; type H3 = HasAnyDeeply<Br1>; // => false type Br2 = string & { __brand: any }; type H4 = HasAnyDeeply<Br2>; // => true ```