Save 37% off PRO during our Black Friday Sale! »

any禁止 絶対に型付けを諦めないための便利なユーティリティ関数 / techstand6

2bedb1eb8f841cd3c3ae584600b016e0?s=47 OKUNOKENTARO
November 10, 2021

any禁止 絶対に型付けを諦めないための便利なユーティリティ関数 / techstand6

2021/11/10 TECH STAND #6 TypeScriptにて発表した資料です。

2bedb1eb8f841cd3c3ae584600b016e0?s=128

OKUNOKENTARO

November 10, 2021
Tweet

Transcript

  1. BOZېࢭ ઈରʹܕ෇͚ΛఘΊͳ͍ͨΊͷ ศརͳϢʔςΟ ϦςΟؔ਺ /PW TUBOEGN5&$)45"/%5ZQF4DSJQU !PLVOPLFOUBSP

  2. ୭ w Ԟ໺ݡଠ࿠!PLVOPLFOUBSP w ΫϨε΢ΣΞגࣜձࣾ w 5ZQF4DSJQUྺ೥

  3. ܕɺ ͪΌΜͱ෇͚ͯ·͔͢ const res = await fetch('/api/users', {method: 'GET'}); const

    users = await res.json();
  4. ͜ΕͰຬ଍ͯ͠ͳ͍ ʁ const res = await fetch('/api/users', {method: 'GET'}); const

    users = await res.json() as User[];
  5. anyܕʹas TΛࢦఆ͢Δͷ͸ةݥ wʮanyܕ͸ةݥʯ 
 ʜͱ͍͏࿩୊͸ཧղ͍ͯͯ͠΋ɺ as TͳΒฏؾͰ࢖ͬͯ͠· ͏ݱ৔͸গͳ͘ͳ͍ w Ͱ΋

    ʮany as T΋ةݥʯ  w anyܕͷ஋ʹas T ʢas User[]ͳͲʣ Λ෇͚ͯ΋ίϯύΠϧΤϥʔʹͳΒͳ͍
  6. ͳʹ͕ةݥͳͷ͔ w ͍͘ ΒͰ΋ӕ͕͚ͭΔ w ίϯύΠϧ͸௨͍ͬͯͯ΋ɺ ࣮ࡍʹͦ͜ʹྲྀΕͯ͘ Δ஋ͷߏ଄͸ҟͳΔঢ়گ w as

    T͸ ʮίϯύΠ ϥΛ͍͍ٗͯΔ͚ͩʯ Ͱ͋Δ
  7. ͜ͷίʔ υ͸ΤϥʔʹͳΒͳ͍ const res1 = await fetch('/api/users', {method: 'GET'}); const

    users = await res1.json() as User[]; const res2 = await fetch('/api/items', {method: 'GET'}); const items = await res2.json() as User[]; as Item[] Ͱ͸ʁʁ
  8. asΛ࢖Θͣ҆શʹܕ෇͚Λ͢Δ w anyېࢭ asېࢭ w 5ZQFQSFEJDBUFTΛ࢖͏ 
 IUUQTXXXUZQFTDSJQUMBOHPSHEPDTIBOECPPLOBSSPXJOHIUNMVTJOHUZQFQSFEJDBUFT  w

    function isSomething(v: unknown): v is Something { ... 
 Έ͍ͨͳ΍ͭ w JG৚݅ࣜʹͯisSomething(v)Λ࢖ͬͨΒɺ 
 ͦͷJGϒϩοΫ಺Ͱ͸v͕Somethingܕͱͯ͠ѻ͑Δ
  9. type User = { id: number; name: string; }; function

    hasId(v: unknown): v is { id: unknown } { if (typeof v !== "object" || v === null) { return false; } return "id" in v; } function hasName(v: unknown): v is { name: unknown } { if (typeof v !== "object" || v === null) { return false; } return "name" in v; } function isUser(v: unknown): v is User { if (!hasId(v) || !hasName(v)) { return false; } if (typeof v.id !== "number") { return false; } if (typeof v.name !== "string") { return false; } return true; }
  10. ׂͱखؒ w asΛઈ໓ͤ͞Α ͏ ͱͨ͠Βɺ 5ZQFQSFEJDBUFTؔ਺͕ͨ͘ ͞Μඞཁ w JGϒϩοΫͷதͰ͔͠ਪ࿦͞Εͳ͍ͷͰɺ ίʔ

    υ͕ෳࡶʹͳΓ͕ͪ w JGϒϩοΫ͕ඞਢͳͷ͸໘౗
  11. JGจΛ࢖Θͳ͍ w "TTFSUJPOGVODUJPOTΛ࢖͏ 
 IUUQTXXXUZQFTDSJQUMBOHPSHEPDTIBOECPPLSFMFBTFOPUFTUZQFTDSJQUIUNMBTTFSUJPOGVODUJPOT  w function assertSomething( 


    v: unknown): asserts v is Something { ... 
 Έ͍ͨͳ΍ͭ w assertSomething(v)Λ࢖ͬͨΒɺ ͦͷؔ਺͕ྫ֎Λ౤͛ͳ͍ݶΓ 
 ಉҰείʔϓͷҎ߱ͷεςʔ τϝϯ τͰ͸v͕Somethingܕͱͯ͠ѻ͑Δ
  12. type User = { id: number; name: string; }; function

    assertId(v: unknown): asserts v is { id: unknown } { if (typeof v !== "object" || v === null) { throw new Error("Value should be non-null object"); } if (!("id" in v)) { throw new Error('Value should have the property "id"'); } // noop } function assertName(v: unknown): asserts v is { name: unknown } { if (typeof v !== "object" || v === null) { throw new Error("Value should be non-null object"); } if (!("name" in v)) { throw new Error('Value should have the property "name"'); } // noop } function assertUser(v: unknown): asserts v is User { assertId(v); assertName(v); if (typeof v.id !== "number") { throw new Error('"id" should be a number'); } if (typeof v.name !== "string") { throw new Error('"name" should be a string'); } // noop }
  13. ΍ͬͺΓखؒ w asΛઈ໓ͤ͞ΔͨΊʹ͜͜·Ͱ͢Δ ʁ  w ҰͭͷΦϒδΣΫ τʹ͍ͭͯݕূ͢Δ͚ͩͰؔ਺ଟ͗͢͡Όͳ͍ ʁ 

    w ΋͏asΛ࢖ͬͯ΋͍͍Μ͡Όʜ ʁ  w ઈରʹఘΊͳ͍
  14. ศརؔ਺Λ࡞ͬͯΈͨ w ϓϩύςΟ͕ੜ͍͑ͯΔ͜ͱΛϥϯλΠϜͰݕূͭͭ͠ w ܕ΋ͪΌΜͱ෇͍ͯ͘Εͯ w ͔͠΋ܕఆٛΛमਖ਼ͨ͠ΒϥϯλΠϜͰ 
 ݕূ͞ΕΔϓϩύςΟ ΋௥ैͯ͠΄͍͠

  15. type UnionToIntersection<U> = (U extends any ? (k: U) =>

    void : never) extends ( k: infer I ) => void ? I : never; type LastOf<T> = UnionToIntersection< T extends any ? () => T : never > extends () => infer R ? R : never; type Push<T extends any[], V> = [...T, V]; type UnionToTuple< T, L = LastOf<T>, N = [T] extends [never] ? true : false > = true extends N ? [] : Push<UnionToTuple<Exclude<T, L>>, L>; type Tuple<TItem, TLength> = [TItem, ...TItem[]] & { length: TLength; }; /** * Tuple<string, 3> ͸ [string, string, string] Λฦ͢ * ͦΕΛԠ༻ͯ͠ 'a' | 'b' Λ ['a' | 'b', 'a' | 'b'] ʹ͢Δ * * 'a' | 'b' * => ['a' | 'b', 'a' | 'b'] * 'a' | 'b' | 'c' * => ['a' | 'b' | 'c', 'a' | 'b' | 'c', 'a' | 'b' | 'c'] */ type UnionToUnionTuple<T> = Tuple<T, UnionToTuple<T>["length"]>; type TypeEq<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false; type RemoveNeverProperties<T> = Pick< T, { [P in keyof T]: [T[P]] extends [never] ? never : P; }[keyof T] >; type AnyAs0<T> = { [P in keyof T]: TypeEq<T[P], any> extends true ? 0 : T[P]; }; type UnknownAs0<T> = { [P in keyof T]: TypeEq<T[P], unknown> extends true ? 0 : T[P]; }; type OptionalAsUndefined<T> = { [P in keyof T]: T[P] extends NonNullable<T[P]> ? T[P] : undefined; }; type UndefinedAsNever<T> = { [P in keyof T]-?: T[P] extends undefined ? never : T[P]; }; /** * { a?: string; b: number; c?: any; d: any } Λ * { b: number; d: any } ͚ͩʹ͢Δɻ * * AnyAs0<T> ͕ͳ͍ͱ ?: any ΁ͷରԠ͕࿙ΕΔͷͰ஫ҙɻ */ type RemoveOptionalProperties<T> = { [P in keyof RemoveNeverProperties< UndefinedAsNever<OptionalAsUndefined<UnknownAs0<AnyAs0<T>>>> >]: T[P]; }; function isObject(v: unknown): v is Record<string, unknown> { if (typeof v !== "object") { return false; } return v !== null; } function assertObject( v: unknown, target = "" ): asserts v is Record<string, unknown> { if (!isObject(v)) { throw new Error(`${target} should be object`.trim()); } } function isMatchedType<T extends object>( v: unknown, props: UnionToUnionTuple<keyof RemoveOptionalProperties<T>>, errorPropsRef: string[], target = "" ): v is T { assertObject(v, target); if (new Set(props).size !== props.length) { throw new Error("Invalid props"); } return props .map((prop) => { if (typeof prop !== "string") { throw new Error("Invalid prop"); } const within = prop in v; if (!within) { errorPropsRef.push(prop); // mutate } return within; }) .every((flag) => flag); } export function assertMatchedType<T extends object>( v: unknown, props: UnionToUnionTuple<keyof RemoveOptionalProperties<T>>, target = "" ): asserts v is T { const errorPropsRef: string[] = []; // ࢠʹ஋Λ֨ೲͤ͞ΔͨΊͷۭ഑ྻࢀর if (!isMatchedType(v, props, errorPropsRef, target)) { // ͜͜ʹ֘౰͢Δͱ͖ errorPropsRef ഑ྻͷத਎ʹ͸ΤϥʔՕॴ͕٧·͍ͬͯΔɻ throw new Error( `${target} should be aligned type. ${ 0 < errorPropsRef.length ? `[${errorPropsRef.join(", ")}]` : "" }`.trim() ); } }
  16. .map((prop) => { if (typeof prop !== "string") { throw

    new Error("Invalid prop"); } const within = prop in v; if (!within) { errorPropsRef.push(prop); // mutate } return within; }) .every((flag) => flag); } export function assertMatchedType<T extends object>( v: unknown, props: UnionToUnionTuple<keyof RemoveOptionalProperties<T>>, target = "" ): asserts v is T { const errorPropsRef: string[] = []; // ࢠʹ஋Λ֨ೲͤ͞ΔͨΊͷۭ഑ྻࢀর if (!isMatchedType(v, props, errorPropsRef, target)) { // ͜͜ʹ֘౰͢Δͱ͖ errorPropsRef ഑ྻͷத਎ʹ͸ΤϥʔՕॴ͕٧·͍ͬͯΔɻ throw new Error( `${target} should be aligned type. ${ 0 < errorPropsRef.length ? `[${errorPropsRef.join(", ")}]` : "" }`.trim() ); } } ͳʹΛ࡞͔ͬͨ w assertMatchedType()ؔ਺Λ࣮૷ͨ͠ w ୈҾ਺ʹݕূ͍ͨ͠ΦϒδΣΫ τ w ୈҾ਺ʹݕূ͍ͨ͠ϓϩύςΟ໊ͷ഑ྻ w ܕύϥϝʔλʹཧ૝ͷܕఆٛΛॻ͘
  17. .map((prop) => { if (typeof prop !== "string") { throw

    new Error("Invalid prop"); } const within = prop in v; if (!within) { errorPropsRef.push(prop); // mutate } return within; }) .every((flag) => flag); } export function assertMatchedType<T extends object>( v: unknown, props: UnionToUnionTuple<keyof RemoveOptionalProperties<T>>, target = "" ): asserts v is T { const errorPropsRef: string[] = []; // ࢠʹ஋Λ֨ೲͤ͞ΔͨΊͷۭ഑ྻࢀর if (!isMatchedType(v, props, errorPropsRef, target)) { // ͜͜ʹ֘౰͢Δͱ͖ errorPropsRef ഑ྻͷத਎ʹ͸ΤϥʔՕॴ͕٧·͍ͬͯΔɻ throw new Error( `${target} should be aligned type. ${ 0 < errorPropsRef.length ? `[${errorPropsRef.join(", ")}]` : "" }`.trim() ); } } ͳʹΛ࡞͔ͬͨ w assertMatchedType()ؔ਺Λ࣮૷ͨ͠ w ୈҾ਺ʹݕূ͍ͨ͠ΦϒδΣΫ τ w ୈҾ਺ʹݕূ͍ͨ͠ϓϩύςΟ໊ͷ഑ྻ w ܕύϥϝʔλʹཧ૝ͷܕఆٛΛॻ͘ type User = { id: number; name: string; }; const obj: unknown = { id: 1, name: "foo" }; assertMatchedType<User>(obj, ["id", "name"]); console.log(obj);
  18. .map((prop) => { if (typeof prop !== "string") { throw

    new Error("Invalid prop"); } const within = prop in v; if (!within) { errorPropsRef.push(prop); // mutate } return within; }) .every((flag) => flag); } export function assertMatchedType<T extends object>( v: unknown, props: UnionToUnionTuple<keyof RemoveOptionalProperties<T>>, target = "" ): asserts v is T { const errorPropsRef: string[] = []; // ࢠʹ஋Λ֨ೲͤ͞ΔͨΊͷۭ഑ྻࢀর if (!isMatchedType(v, props, errorPropsRef, target)) { // ͜͜ʹ֘౰͢Δͱ͖ errorPropsRef ഑ྻͷத਎ʹ͸ΤϥʔՕॴ͕٧·͍ͬͯΔɻ throw new Error( `${target} should be aligned type. ${ 0 < errorPropsRef.length ? `[${errorPropsRef.join(", ")}]` : "" }`.trim() ); } } ศརͳͱ͜Ζ w ܕఆ͕ٛมΘΔͱɺ ୈҾ਺ͰٻΊΒΕΔ 
 Ҿ਺ͷܕ͕มΘΔ w ܕఆ͚ٛͩ௚ͯ͠ɺ 
 ϥϯλΠϜଆ͕௚ͬͯͳ͍ঢ়گΛ๷͙ type User = { id: number; name: string; email: string; }; const obj: unknown = { id: 1, name: "foo" }; assertMatchedType<User>(obj, ["id", "name"]); console.log(obj); Argument of type '["id", "name"]' is not assignable to parameter of type 'UnionToUnionTuple<"id" | "name" | "email">'.
  19. function assertObject( v: unknown, target = "" ): asserts v

    is Record<string, unknown> { if (!isObject(v)) { throw new Error(`${target} should be object`.trim()); } } function isMatchedType<T extends object>( v: unknown, props: UnionToUnionTuple<keyof RemoveOptionalProperties<T>>, errorPropsRef: string[], target = "" ): v is T { assertObject(v, target); if (new Set(props).size !== props.length) { throw new Error("Invalid props"); } return props .map((prop) => { if (typeof prop !== "string") { throw new Error("Invalid prop"); } const within = prop in v; if (!within) { errorPropsRef.push(prop); // mutate } return within; }) .every((flag) => flag); } export function assertMatchedType<T extends object>( v: unknown, props: UnionToUnionTuple<keyof RemoveOptionalProperties<T>>, target = "" ): asserts v is T { const errorPropsRef: string[] = []; // ࢠʹ஋Λ֨ೲͤ͞ΔͨΊͷۭ഑ྻࢀর w ศརؔ਺ΛassertObject()Λࣄલʹ 
 ࡞͓ͬͯ͘ w ϓϩύςΟࢦఆ͸͢΂ͯϢχʔΫͰ͋Δ͜ͱΛ 
 อূ w ͻͱͭͻͱͭinΛ࢖ͬͯ֬ೝ w ͳ͚Ε͹Τϥʔࢀরʹه࿥ ϥϯλΠϜଆͷ࢓૊Έ
  20. * { a?: string; b: number; c?: any; d: any

    } Λ * { b: number; d: any } ͚ͩʹ͢Δɻ * * AnyAs0<T> ͕ͳ͍ͱ ?: any ΁ͷରԠ͕࿙ΕΔͷͰ஫ҙɻ */ type RemoveOptionalProperties<T> = { [P in keyof RemoveNeverProperties< UndefinedAsNever<OptionalAsUndefined<UnknownAs0<AnyAs0<T>>>> >]: T[P]; }; function isObject(v: unknown): v is Record<string, unknown> { if (typeof v !== "object") { return false; } return v !== null; } function assertObject( v: unknown, target = "" ): asserts v is Record<string, unknown> { if (!isObject(v)) { throw new Error(`${target} should be object`.trim()); } } function isMatchedType<T extends object>( v: unknown, props: UnionToUnionTuple<keyof RemoveOptionalProperties<T>>, errorPropsRef: string[], target = "" ): v is T { assertObject(v, target); if (new Set(props).size !== props.length) { throw new Error("Invalid props"); } return props .map((prop) => { w ศརؔ਺assertObject()͸ 
 "TTFSUJPOGVODUJPOTͷ 
 ͓खຊͷΑ ͏ͳؔ਺ assertObject()
  21. w ͔͜͜Β5ZQF4DSJQUͷຊؾ w ͢΂ͯͷϓϩύςΟ Λྻڍ͍ͨ͠ w PQUJPOBMͳϓϩύςΟ͸ݕূͨ͘͠ͳ͍ w PQUJPOBMͳϓϩύςΟ͸ॻ͔ͳ͘ ͯ΋

    
 ίϯύΠϧΤϥʔʹͨ͘͠ͳ͍ w ͦΕΛ͢ΔͨΊͷܕύζϧ ܕύζϧ : false; type RemoveNeverProperties<T> = Pick< T, { [P in keyof T]: [T[P]] extends [never] ? never : P; }[keyof T] >; type AnyAs0<T> = { [P in keyof T]: TypeEq<T[P], any> extends true ? 0 : T[P]; }; type UnknownAs0<T> = { [P in keyof T]: TypeEq<T[P], unknown> extends true ? 0 : T[P]; }; type OptionalAsUndefined<T> = { [P in keyof T]: T[P] extends NonNullable<T[P]> ? T[P] : undefined; }; type UndefinedAsNever<T> = { [P in keyof T]-?: T[P] extends undefined ? never : T[P]; }; /** * { a?: string; b: number; c?: any; d: any } Λ * { b: number; d: any } ͚ͩʹ͢Δɻ * * AnyAs0<T> ͕ͳ͍ͱ ?: any ΁ͷରԠ͕࿙ΕΔͷͰ஫ҙɻ */ type RemoveOptionalProperties<T> = { [P in keyof RemoveNeverProperties< UndefinedAsNever<OptionalAsUndefined<UnknownAs0<AnyAs0<T>>>> >]: T[P]; }; function isObject(v: unknown): v is Record<string, unknown> {
  22. : false; type RemoveNeverProperties<T> = Pick< T, { [P in

    keyof T]: [T[P]] extends [never] ? never : P; }[keyof T] >; type AnyAs0<T> = { [P in keyof T]: TypeEq<T[P], any> extends true ? 0 : T[P]; }; type UnknownAs0<T> = { [P in keyof T]: TypeEq<T[P], unknown> extends true ? 0 : T[P]; }; type OptionalAsUndefined<T> = { [P in keyof T]: T[P] extends NonNullable<T[P]> ? T[P] : undefined; }; type UndefinedAsNever<T> = { [P in keyof T]-?: T[P] extends undefined ? never : T[P]; }; /** * { a?: string; b: number; c?: any; d: any } Λ * { b: number; d: any } ͚ͩʹ͢Δɻ * * AnyAs0<T> ͕ͳ͍ͱ ?: any ΁ͷରԠ͕࿙ΕΔͷͰ஫ҙɻ */ type RemoveOptionalProperties<T> = { [P in keyof RemoveNeverProperties< UndefinedAsNever<OptionalAsUndefined<UnknownAs0<AnyAs0<T>>>> >]: T[P]; }; function isObject(v: unknown): v is Record<string, unknown> { Ṗͷຐ๏ w AnyAs0ͱUnknownAs0Ͱɺ anyܕͱ 
 unknownܕΛҰ୴͢΂ͯ 
 0ܕ ʢ਺஋Ϧςϥϧܕʣ ʹஔ͖׵͑Δ w PQUJPOBMͰ͋Ε͹ 
 ͢΂ͯundefinedʹ͢Δ w 0ܕʹஔ͖׵͓͔͑ͯͳ͍ͱ 
 PQUJPOBManyͱPQUJPOBMunknown͕ 
 ਖ਼͘͠ॲཧ͞Εͳ͍
  23. : false; type RemoveNeverProperties<T> = Pick< T, { [P in

    keyof T]: [T[P]] extends [never] ? never : P; }[keyof T] >; type AnyAs0<T> = { [P in keyof T]: TypeEq<T[P], any> extends true ? 0 : T[P]; }; type UnknownAs0<T> = { [P in keyof T]: TypeEq<T[P], unknown> extends true ? 0 : T[P]; }; type OptionalAsUndefined<T> = { [P in keyof T]: T[P] extends NonNullable<T[P]> ? T[P] : undefined; }; type UndefinedAsNever<T> = { [P in keyof T]-?: T[P] extends undefined ? never : T[P]; }; /** * { a?: string; b: number; c?: any; d: any } Λ * { b: number; d: any } ͚ͩʹ͢Δɻ * * AnyAs0<T> ͕ͳ͍ͱ ?: any ΁ͷରԠ͕࿙ΕΔͷͰ஫ҙɻ */ type RemoveOptionalProperties<T> = { [P in keyof RemoveNeverProperties< UndefinedAsNever<OptionalAsUndefined<UnknownAs0<AnyAs0<T>>>> >]: T[P]; }; function isObject(v: unknown): v is Record<string, unknown> { Ṗͷຐ๏ w undefinedΛ͢΂ͯPQUJPOBMΛ֎ͭͭ͠ 
 neverʹஔ͖׵͑Δ w neverͰ͋Ε͹ɺ ͦΕΒΛ͢΂ͯ 
 Pick<T, U>Λ࢖ͬͯআڈ͢Δ w ͜Ε͸undefinedΛؚΊଞͷܕͰ͸ 
 ͜ͷΑ ͏ͳॲཧ͕࣮ݱͰ͖ͣ 
 neverͷΈআڈͰ͖Δ
  24. ܕύζϧͷ൪ਓ w ܕύζϧΛॻ͍͍ͯΔͱ͖͸ݕূ͕ࠔ೉ w TypeEqʻA, BʼΛ࢖ͬͯ 
 ܕςε τΛॻ͘ ͱΑ͍

    w AnyAs0 UnknownAs0Ͱ΋࢖༻ > = true extends N ? [] : Push<UnionToTuple<Exclude<T, L>>, L>; type Tuple<TItem, TLength> = [TItem, ...TItem[]] & { length: TLength; }; /** * Tuple<string, 3> ͸ [string, string, string] Λฦ͢ * ͦΕΛԠ༻ͯ͠ 'a' | 'b' Λ ['a' | 'b', 'a' | 'b'] ʹ͢Δ * * 'a' | 'b' * => ['a' | 'b', 'a' | 'b'] * 'a' | 'b' | 'c' * => ['a' | 'b' | 'c', 'a' | 'b' | 'c', 'a' | 'b' | 'c'] */ type UnionToUnionTuple<T> = Tuple<T, UnionToTuple<T>["length"]>; type TypeEq<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false; type RemoveNeverProperties<T> = Pick< T, { [P in keyof T]: [T[P]] extends [never] ? never : P; }[keyof T] >; type AnyAs0<T> = { [P in keyof T]: TypeEq<T[P], any> extends true ? 0 : T[P]; }; type UnknownAs0<T> = { [P in keyof T]: TypeEq<T[P], unknown> extends true ? 0 : T[P]; }; type OptionalAsUndefined<T> = { https://qiita.com/kgtkr/items/2a8290d1b1314063a524
  25. type UnionToIntersection<U> = (U extends any ? (k: U) =>

    void : never) extends ( k: infer I ) => void ? I : never; type LastOf<T> = UnionToIntersection< T extends any ? () => T : never > extends () => infer R ? R : never; type Push<T extends any[], V> = [...T, V]; type UnionToTuple< T, L = LastOf<T>, N = [T] extends [never] ? true : false > = true extends N ? [] : Push<UnionToTuple<Exclude<T, L>>, L>; type Tuple<TItem, TLength> = [TItem, ...TItem[]] & { length: TLength; }; /** * Tuple<string, 3> ͸ [string, string, string] Λฦ͢ * ͦΕΛԠ༻ͯ͠ 'a' | 'b' Λ ['a' | 'b', 'a' | 'b'] ʹ͢Δ * * 'a' | 'b' * => ['a' | 'b', 'a' | 'b'] * 'a' | 'b' | 'c' * => ['a' | 'b' | 'c', 'a' | 'b' | 'c', 'a' | 'b' | 'c'] */ type UnionToUnionTuple<T> = Tuple<T, UnionToTuple<T>["length"]>; Ṗͷຐ๏ w PQUJPOBMͳϓϩύςΟ Λ͢΂ͯ 
 আڈͨ͠ͷͰɺ ࢒ΓͷඞਢϓϩύςΟ Λ 
 ྻڍ͠λϓϧͰड͚औΓ͍ͨ w UnionToTupleΛ࢖͑͹keyof Tͷ 
 ݁ՌΛλϓϧͱͯ͠ѻ͑Δ w ͔͠͠ॱෆಉͰ͋Δ ʢఆ͕ٛهड़ॱʹ 
 ͳΒͳ͍ʣ ͜ͱͰ௚ײతͰ͸ͳ͍ͨΊ 
 UnionToUnionTupleͰهड़ॱΛ 
 ࣗ༝ʹ͢Δ w ϥϯλΠϜͰnew Set()Λͨ͠ͷ͸ 
 ͜Εରࡦ
  26. ͜Ε͕࣮ݱͰ͖Δ w ܕఆ͕ٛมΘΔͱɺ ୈҾ਺ͰٻΊΒΕΔ 
 Ҿ਺ͷܕ͕มΘΔ w ܕఆ͚ٛͩ௚ͯ͠ɺ 
 ϥϯλΠϜଆ͕௚ͬͯͳ͍ঢ়گΛ๷͙

    .map((prop) => { if (typeof prop !== "string") { throw new Error("Invalid prop"); } const within = prop in v; if (!within) { errorPropsRef.push(prop); // mutate } return within; }) .every((flag) => flag); } export function assertMatchedType<T extends object>( v: unknown, props: UnionToUnionTuple<keyof RemoveOptionalProperties<T>>, target = "" ): asserts v is T { const errorPropsRef: string[] = []; // ࢠʹ஋Λ֨ೲͤ͞ΔͨΊͷۭ഑ྻࢀর if (!isMatchedType(v, props, errorPropsRef, target)) { // ͜͜ʹ֘౰͢Δͱ͖ errorPropsRef ഑ྻͷத਎ʹ͸ΤϥʔՕॴ͕٧·͍ͬͯΔɻ throw new Error( `${target} should be aligned type. ${ 0 < errorPropsRef.length ? `[${errorPropsRef.join(", ")}]` : "" }`.trim() ); } } type User = { id: number; name: string; email: string; }; const obj: unknown = { id: 1, name: "foo" }; assertMatchedType<User>(obj, ["id", "name"]); console.log(obj); Argument of type '["id", "name"]' is not assignable to parameter of type 'UnionToUnionTuple<"id" | "name" | "email">'.
  27. .map((prop) => { if (typeof prop !== "string") { throw

    new Error("Invalid prop"); } const within = prop in v; if (!within) { errorPropsRef.push(prop); // mutate } return within; }) .every((flag) => flag); } export function assertMatchedType<T extends object>( v: unknown, props: UnionToUnionTuple<keyof RemoveOptionalProperties<T>>, target = "" ): asserts v is T { const errorPropsRef: string[] = []; // ࢠʹ஋Λ֨ೲͤ͞ΔͨΊͷۭ഑ྻࢀর if (!isMatchedType(v, props, errorPropsRef, target)) { // ͜͜ʹ֘౰͢Δͱ͖ errorPropsRef ഑ྻͷத਎ʹ͸ΤϥʔՕॴ͕٧·͍ͬͯΔɻ throw new Error( `${target} should be aligned type. ${ 0 < errorPropsRef.length ? `[${errorPropsRef.join(", ")}]` : "" }`.trim() ); } } ࿙Εʹ͍ͭͯ w id͕number name͕stringͰ͋Δ͜ͱ 
 ·Ͱ͸Έͯ͘Εͳ͍ w Α Γ҆શʹ͍ͨ͠ͳΒ͢΂ͯͷϓϩύςΟͷܕΛ 
 unknownܕʹ͢Δͷ͕Α͍ w ͦͷ৔߹͸assertString()  assertNumber()ͳͲͷؔ਺ͱ૊Έ߹ΘͤΔ type User = { id: unknown; name: unknown; email: unknown; }; const obj: unknown = { id: 1, name: "foo" }; assertMatchedType<User>(obj, ["id", "name"]); console.log(obj);
  28. $POEJUJPOBM5ZQFTΛۃΊΔͱָ͍͠ w $POEJUJPOBM5ZQFT͸࣮͸͔ͳΓͷදݱྗ͕͋Δ 
 IUUQTXXXUZQFTDSJQUMBOHPSHEPDTIBOECPPLDPOEJUJPOBMUZQFTIUNM  w Ұݟݫͦ͠͏ͳܕਪ࿦Ͱ΋࣮͸ॻ͚ͨΓ͢Δ w $POEJUJPOBM5ZQFTΛۃΊΑ

    ͏ w as͸ېࢭͰ͖Δ
  29. 5IBOLZPV https://gist.github.com/okunokentaro/74361c2683bd8e0f349d21ddebe189cf