Open16

type-challenges、re-challenge

asann3asann3

type-challenges に挫折した者が再チャレンジする記録

asann3asann3

Hello World
level: warm-up

/* _____________ Your Code Here _____________ */

type HelloWorld = string // expected to be a string

/* _____________ Test Cases _____________ */
import type { Equal, Expect, NotAny } from '@type-challenges/utils'

type cases = [
  Expect<NotAny<HelloWorld>>,
  Expect<Equal<HelloWorld, string>>,
]
asann3asann3

Pick
level: easy

アプローチ
とりあえず、
type MyPick<T, K> = {'title': string} と書いて1つ目を通してみた
その後
type MyPick<T, K> = {[P in K]: T[P]} と書いて、うまくいったように見えるが型定義自体にエラーが残っている

  • Type 'K' is not assignable to type 'string | number | symbol'.(2322)
  • Type 'P' cannot be used to index type 'T'.(2536)

in と keyof を組み合わせて使う場合が存在するらしいが、使い分けがよくわからなかったので調べた

  • in の使いどころ(JSの役割(プロパティの存在真偽、ループのためのin)は省略)
    mapped typesで使う
    構文は、[P in K]([]がない状態で使われることはおそらくない
    インデックスシグネチャ(宣言済みオブジェクトに新たなプロパティを追加することができる)の概念が前提
    [P in K] と書けば、KからPを取り出す
  • keyof の使いどころ
    プロパティ名を取得する
type X = 'apple' | 'banana'

type NewObj = {[P in X]: string}
// type NewObj = {
//     apple: string;
//     banana: string;
// }

type keys = keyof NewObj
// 'apple' | 'banana'

type Obj2 =
{
  [P in keyof NewObj]: NewObj[P]
}
// type Obj2 = {
//     apple: string;
//     banana: string;
// }

色々調べてこう書いたが、
type MyPick<T, K> = {[P in keyof T]: T[P]}
別のエラーが残っている

  • Type 'false' does not satisfy the constraint 'true'.
  • Type 'false' does not satisfy the constraint 'true'.

keyof は T の全プロパティを取得するものだけど、全部必要ないことに気づいたので
type MyPick<T, K> = {[P in K]: T[P]}とした

Kに制約がない(KにTの要素じゃないものが入ってしまう)ことに気づいた

type MyPick<T, [K in T]> = {[P in K]: T[P]}
としてみたけど、文法が正しくない

type MyPick<T, K extends T> = {[P in K]: T[P]}
extends T だと、{}になってしまい、string を assign できない

type MyPick<T, K extends keyof T> = {[P in K]: T[P]}
とすることで解決

解答

/* _____________ Your Code Here _____________ */

type MyPick<T, K extends keyof T> = {[P in K]: T[P]}

/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type cases = [
  Expect<Equal<Expected1, MyPick<Todo, 'title'>>>,
  Expect<Equal<Expected2, MyPick<Todo, 'title' | 'completed'>>>,
  // @ts-expect-error
  MyPick<Todo, 'title' | 'completed' | 'invalid'>,
]

interface Todo {
  title: string
  description: string
  completed: boolean
}

interface Expected1 {
  title: string
}

interface Expected2 {
  title: string
  completed: boolean
}
asann3asann3

Readonly

level: easy

アプローチ

とりあえず、以下のようにしてエラーを回避

type MyReadonly<T> = {
  readonly title: string
  readonly description: string
  readonly completed: boolean
  readonly meta: {
    author: string
  }
}

ただ、これでは汎用性がないので、
type MyReadonly<T> = {readonly [K in keyof T]: T[K]}
こんな感じ?
エラーは消えたけど、authorがreadonlyになっているのか自信がない

const x: MyReadonly<Todo1> = {
  title: 'a',
  description: 'b',
  completed: true,
  meta: {
    author: 'c'
  }
}
x.meta.author = 'd'

代入できてしまった、、、
本家Readonlyもプロパティ直下のみreadonlyとしているので問題なさそう
readonly は、別のオブジェクトを介すことで書き換えができてしまうようなので注意が必要なよう

解答

/* _____________ Your Code Here _____________ */

type MyReadonly<T> = {readonly [K in keyof T]: T[K]}

/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type cases = [
  Expect<Equal<MyReadonly<Todo1>, Readonly<Todo1>>>,
]

interface Todo1 {
  title: string
  description: string
  completed: boolean
  meta: {
    author: string
  }
}
asann3asann3

Tuple to Object[WIP]

level: easy

アプローチ

インデックスアクセス型というものがあり、T[number]と書くことで配列の要素の型を参照できる
type TupleToObject<T extends readonly any[]> = {[typeof T[number]]: T}
と書いてみた
Tは配列型なのでオブジェクトのプロパティの型として使えない

type TupleToObject<T extends readonly any[]> = {T: T[number]}
T: と書くと'T'というプロパティとなってしまう

in keyof を使ってみる
type TupleToObject<T extends readonly any[]> = {[P in keyof T]: T[number]}
しかし、これは配列型なので、得られるのはindex(0, 1, 2, ...)である

そこでT[P]を使えば良いのではと思い
type TupleToObject<T extends readonly any[]> = {T[P in keyof T]: T[number]}
しかし、これは、T['tesla'] というようになってしまうためうまくいかない

T[number]とすれば良いことを思い出し、keyに書いてみるが
type TupleToObject<T extends readonly any[]> = {[P in T[number]]: [P in T[number]]}

type TupleToObject<T extends readonly any[]> = {[P in T[number]]: T[P]}
うまくいかなかった

Pが前半で列挙されているため、そのまま利用できる
type TupleToObject<T extends readonly any[]> = {[P in T[number]]: P}
として正解


2024/11/28 追記
上記のコードはtype error = TupleToObject<[[1, 2], {}]>が通るコードだったため修正が必要
any[] により、混合したタプルも受け入れてしまうようになっている
(string | number | symbol)[]に変更することで解決

解答

/* _____________ Your Code Here _____________ */

type TupleToObject<T extends readonly (string | number | symbol)[]> = {[P in T[number]]: P}

/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const
const tupleNumber = [1, 2, 3, 4] as const
const sym1 = Symbol(1)
const sym2 = Symbol(2)
const tupleSymbol = [sym1, sym2] as const
const tupleMix = [1, '2', 3, '4', sym1] as const

type cases = [
  Expect<Equal<TupleToObject<typeof tuple>, { 'tesla': 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y' }>>,
  Expect<Equal<TupleToObject<typeof tupleNumber>, { 1: 1, 2: 2, 3: 3, 4: 4 }>>,
  Expect<Equal<TupleToObject<typeof tupleSymbol>, { [sym1]: typeof sym1, [sym2]: typeof sym2 }>>,
  Expect<Equal<TupleToObject<typeof tupleMix>, { 1: 1, '2': '2', 3: 3, '4': '4', [sym1]: typeof sym1 }>>,
]

// @ts-expect-error
type error = TupleToObject<[[1, 2], {}]>

asann3asann3

First of Array

level: easy

アプローチ

type First<T extends any[]> = T[0]
とりあえず0番目を取得
このままだと、Expect<Equal<First<[]>, never>>が通らない

条件分岐ができるConditional Typesを検討してみる
type First<T extends any[]> = T[0] extends any[] ? never : T[0]
まだExpect<Equal<First<[]>, never>>だけ通らない
逆だとこれだけ通るので、First<[]> が any[]の部分型でないことはわかる

そもそも空配列なら、T[0]を持たないのでは
type First<T extends any[]> = T extends [] ? never : T[0]
と書いて正解

解答

/* _____________ Your Code Here _____________ */

type First<T extends any[]> = T extends [] ? never : T[0]

/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type cases = [
  Expect<Equal<First<[3, 2, 1]>, 3>>,
  Expect<Equal<First<[() => 123, { a: string }]>, () => 123>>,
  Expect<Equal<First<[]>, never>>,
  Expect<Equal<First<[undefined]>, undefined>>,
]

type errors = [
  // @ts-expect-error
  First<'notArray'>,
  // @ts-expect-error
  First<{ 0: 'arrayLike' }>,
]

asann3asann3

Length of Tuple

level: easy

アプローチ

type Length<T> = T['length']
型変数のlengthプロパティへアクセスすることができる
エラーは消えたが、型定義自体にエラーが発生した

  • Type '"length"' cannot be used to index type 'T'.
    これはおそらく、Tがlengthを持つかわからないため

type Length<T extends (string | number | symbol)[]> = T['length']
と書くと

  • Type 'readonly ["tesla", "model 3", "model X", "model Y"]' does not satisfy the constraint '(string | number | symbol)[]'.
    The type 'readonly ["tesla", "model 3", "model X", "model Y"]' is 'readonly' and cannot be assigned to the mutable type '(string | number | symbol)[]'.
  • Type 'readonly ["FALCON 9", "FALCON HEAVY", "DRAGON", "STARSHIP", "HUMAN SPACEFLIGHT"]' does not satisfy the constraint '(string | number | symbol)[]'.
    The type 'readonly ["FALCON 9", "FALCON HEAVY", "DRAGON", "STARSHIP", "HUMAN SPACEFLIGHT"]' is 'readonly' and cannot be assigned to the mutable type '(string | number | symbol)[]'.
    というようなエラーが出た
    これは変数定義でas constをつけているためreadonlyとなり、readonlyな型は変更可能な型への代入ができないためエラーとなっている

type Length<T extends readonly (string | number | symbol)[]> = T['length']
readonly を加えてあげることで解決
(numberとsymbol、特にsymbolは不要かもしれない)

解答

/* _____________ Your Code Here _____________ */

type Length<T extends readonly (string | number | symbol)[]> = T['length']

/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

const tesla = ['tesla', 'model 3', 'model X', 'model Y'] as const
const spaceX = ['FALCON 9', 'FALCON HEAVY', 'DRAGON', 'STARSHIP', 'HUMAN SPACEFLIGHT'] as const

type cases = [
  Expect<Equal<Length<typeof tesla>, 4>>,
  Expect<Equal<Length<typeof spaceX>, 5>>,
  // @ts-expect-error
  Length<5>,
  // @ts-expect-error
  Length<'hello world'>,
]

asann3asann3

Exclude

level: easy

アプローチ

extendsを使って分岐する
T('a' | 'b' | 'c')がU('a')に割り当て可能かどうかで判断し、
割り当てられるなら(取り除かれるため)返す型に含めず、割り当てられないなら(残すため)返す型に含める処理を書く
T extends U ? {返さない} : T
{}のところがわからなかったが、never を使えば良いらしい

never型は返す値がないときに使う型
null型やvoid型と違うところは、
null型やvoid型は型として残ってしまうところ
例えば'a' | neverとした際、残るのは'a'だが、'a' | null'a' | null'a' | void'a' | voidと残ってしまう
never型は情報数学(オートマトン)のεのような役割だなと思った

解答

/* _____________ Your Code Here _____________ */

type MyExclude<T, U> = T extends U ? never : T

/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type cases = [
  Expect<Equal<MyExclude<'a' | 'b' | 'c', 'a'>, 'b' | 'c'>>,
  Expect<Equal<MyExclude<'a' | 'b' | 'c', 'a' | 'b'>, 'c'>>,
  Expect<Equal<MyExclude<string | number | (() => void), Function>, string | number>>,
]
asann3asann3

Awaited

level: easy

アプローチ

Promise型の調査から
Promise はJSにおけるコールバック地獄を解消するために生まれたもの
値が返ってくるかエラーが返ってくるかを保証する
Promise型は型引数に指定した型を返す(指定しない場合はanyを返す)
PromiseでいうところのFullfilledにあたる

今回再帰的な型の取得が必要となる
infer を使えば良さそう
infer は推論を意味し、conditonal typeで条件分岐した型を表現できる
infer Rと書くとinferで推論して型変数Rに代入できる
ひとまずこんな感じに書いた
type MyAwaited<T> = T extends Promise<infer R> ? R : never
これで最初の2つのケースには対応できたが、再帰的なPromiseには対応できていない
根本的な解決になっていないかもしれないが2重に対応
type MyAwaited<T> = T extends Promise<infer R> ? (R extends Promise<infer S> ? S : R) : never
3重の場合
type MyAwaited<T> = T extends Promise<infer R> ? (R extends Promise<infer S> ? (S extends Promise<infer U> ? U : S): R) : never

type MyAwaited<T> = T extends Promise<infer R> ? (R extends Promise<infer S> ? (S extends Promise<infer U> ? U : S): R) : number
これでエラーは直るが、嘘解法

type MyAwaited<T> = T extends Promise<infer R> ? R : MyAwaited<R>
上記を書く前にこれを試したけど、以下のエラーが出てしまっていた

  • Type instantiation is excessively deep and possibly infinite.
  • Cannot find name 'R'.
    これは無限ループを起こしてしまう可能性があることと、型変数はfalse時に使えないことを意味している

type MyAwaited<T> = T extends Promise<infer R> ? MyAwaited<R> : T
と書くことで最後のケース以外をクリアできた

type MyAwaited<T> = T extends { then: (onfulfilled: (arg: infer X) => any) => any } ? X : (Promise<infer R> ? MyAwaited<R> : T)
と書いて条件分岐を分けてみた

  • 'MyAwaited' only refers to a type, but is being used as a value here.
  • Cannot find name 'R'.
  • 'T' only refers to a type, but is being used as a value here.
    エラーが出る
    後半の括弧内でextendsが抜けていた
    type MyAwaited<T> = T extends { then: (onfulfilled: (arg: infer X) => any) => any } ? X : (T extends Promise<infer R> ? MyAwaited<R> : T)
    文法エラーは消えた

type MyAwaited<T> = T extends { then: (onfulfilled: (arg: infer X) => any) => any } ? MyAwaited<X> : (T extends Promise<infer R> ? MyAwaited<R> : T)
再帰していく中で最終的には、MyAwaited<X>のX部分が返したい型となるので
type MyAwaited<T> = T extends { then: (onfulfilled: (arg: infer X) => any) => any } ? MyAwaited<X> : T
と書ける

Promiseの内部は
{ then: (onfulfilled: (arg: infer X) => any) => any }
の部分型となるため通る

他の答えを探していたら
PromiseLikeというものがあることを知った
T extends PromiseLike<infer R> ? MyAwaited<R> : T
こんな簡潔に書けるらしい

解答

/* _____________ Your Code Here _____________ */

type MyAwaited<T> = T extends { then: (onfulfilled: (arg: infer X) => any) => any } 
  ? MyAwaited<X>
  : T

/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type X = Promise<string>
type Y = Promise<{ field: number }>
type Z = Promise<Promise<string | number>>
type Z1 = Promise<Promise<Promise<string | boolean>>>
type T = { then: (onfulfilled: (arg: number) => any) => any }

type cases = [
  Expect<Equal<MyAwaited<X>, string>>,
  Expect<Equal<MyAwaited<Y>, { field: number }>>,
  Expect<Equal<MyAwaited<Z>, string | number>>,
  Expect<Equal<MyAwaited<Z1>, string | boolean>>,
  Expect<Equal<MyAwaited<T>, number>>,
]

asann3asann3

If

level: easy

アプローチ

第一引数がtruthyな値なら第二引数の形を、第一引数がfalsyな値なら第三引数の型を返す

truthy/falsy な値とは

true/false とみなされる値のこと
例えばnullもfalsyな値

今回Ctrue or false をとるためあんまり関係ない

type If<C, T, F> = C extends true ? T : F
通常のケースは通ったが、エラーのケースがエラーにならない
Cに型制約を課すことでうまくいく
type If<C extends boolean, T, F> = C extends true ? T : F
3番目のケースのbooleantrue | false をとるため、'a' | 2 となる

解答

/* _____________ Your Code Here _____________ */

type If<C extends boolean, T, F> = C extends true ? T : F

/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type cases = [
  Expect<Equal<If<true, 'a', 'b'>, 'a'>>,
  Expect<Equal<If<false, 'a', 2>, 2>>,
  Expect<Equal<If<boolean, 'a', 2>, 'a' | 2>>,
]

// @ts-expect-error
type error = If<null, 'a', 'b'>

asann3asann3

Concat

level: easy

アプローチ

一旦intersection型を考えた
&で結んだが、それでは最初のケースしか通らない
& は2つの型の共通をとり、| は2つの型をそれぞれとるから
今回はmergeが必要
JSに使われるspread構文(配列の値を展開するもの)がTypeScriptにもあるらしい
type Concat<T, U> = [...T, ...U]
と書くことでうまくいったように見えるが、エラーのケースがエラーになっていないのと、

  • A rest element type must be an array type.
    というエラーがある

Tが配列型であることを明示する必要がある
type Concat<T extends [], U extends []> = [...T, ...U]
extends を用いた定義によって、型定義のエラーとエラーケースの問題は解消できた
しかしextends []と書いたことによって、最初のケース以外がエラーとなってしまった

type Concat<T extends unknown[], U extends unknown[]> = [...T, ...U]
とすることで、空の場合と空でない場合の両者に対応できる
ちなみにT extends Array<T>だと値が入っていることが前提となる

エラーが1つ残っている

  • Type 'readonly [1]' does not satisfy the constraint 'unknown[]'.
    The type 'readonly [1]' is 'readonly' and cannot be assigned to the mutable type 'unknown[]'.

これは as const をつけることによってreadonlyとなっているためである

type Concat<T extends readonly unknown[], U extends readonly unknown[]> = [...T, ...U]
readonly をつけることによってエラーを解消した

解答

/* _____________ Your Code Here _____________ */

type Concat<T extends readonly unknown[], U extends readonly unknown[]> = [...T, ...U]

/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

const tuple = [1] as const

type cases = [
  Expect<Equal<Concat<[], []>, []>>,
  Expect<Equal<Concat<[], [1]>, [1]>>,
  Expect<Equal<Concat<typeof tuple, typeof tuple>, [1, 1]>>,
  Expect<Equal<Concat<[1, 2], [3, 4]>, [1, 2, 3, 4]>>,
  Expect<Equal<Concat<['1', 2, '3'], [false, boolean, '4']>, ['1', 2, '3', false, boolean, '4']>>,
]

// @ts-expect-error
type error = Concat<null, undefined>

asann3asann3

Includes

level: easy

アプローチ

type Includes<T extends readonly any[], U> = U extends T ? true : false
と書いてみた
一部エラーが残る
trueの場合がうまくいっていない
確かに最初のケースだと'Kars'が['Kars', 'Esidisi', 'Wamuu', 'Santana']の部分型とは言えないためfalseとなってしまう

type Includes<T extends readonly any[], U> = U extends typeof T ? true : false

  • 'T' only refers to a type, but is being used as a value here.
    おかしな型定義をしていた
    Tはそもそも型なのでtypeof Tをしても意味がない

type Includes<T extends readonly any[], U> = U extends T[number] ? true : false
厳密な比較ができていないためエラーが残る
例えば、falseはbooleanの部分型であるためtrueを返すが、false自体はincludeされていないためfalseを期待しているためエラーとなる

逆方向もやってみることにした
type Includes<T extends readonly any[], U> = U extends T[number] ? (T[number] extends U ? true : false) : false
エラーが増えた
これは
例えば (1 | 2 | 3) extends 2 は false となってしまうため

要素を1つずつ再帰的に確認してみる
type Includes<T extends readonly any[], U> = T extends [infer First, ...infer Rest] ? (First extends U ? true : Includes<Rest, U>) : false
良さそうに見えるが、エラーが残っていた
例えばIncludes<[1], 1 | 2>の際、1が1|2の部分型かどうかを比較するためtrueになってしまうが、2も左の配列に含まれていなければならないため、falseが期待されるべき

双方向チェックを導入してみる
type Includes<T extends readonly any[], U> = T extends [infer First, ...infer Rest] ? (First extends U ? (U extends First ? true: Includes<Rest, U>) : Includes<Rest, U>) : false

Union Distributionがある
type Includes<T extends readonly any[], U> = T extends [infer First, ...infer Rest] ? ([First] extends [U] ? ([U] extends [First] ? true: Includes<Rest, U>) : Includes<Rest, U>) : false
[]を加えることで分配されることを防ぐ
これによって
Expect<Equal<Includes<[{ a: 'A' }], { readonly a: 'A' }>, false>>,
Expect<Equal<Includes<[{ readonly a: 'A' }], { a: 'A' }>, false>>,
以外のケースは通る

他の回答を調べてみても、問題が改訂される前かEqualを使う方法ばかり
どうやらこのEqualないしEqualの実装を使うしかなさそう

Equal の型比較について extends よりも厳密なものとなっている
Equal の実装は https://github.com/type-challenges/type-challenges/blob/main/utils/index.d.ts にある

export type Equal<X, Y> =
  (<T>() => T extends X ? 1 : 2) extends
  (<T>() => T extends Y ? 1 : 2) ? true : false

extends が3つある式ということと、型引数TをとりTを返す関数がある
とりあえず1と2のところはX, Yを比較するためのものだから、被らないければ他の値でも問題なさそう
問題は<T>() => Tが何をしているか
理解力が足りないのでWIPということで

type Includes<T extends any[], U> = T extends [infer First, ...infer Rest] ? Equal<First, U> extends true ? true : Includes<Rest, U> : false

解答

/* _____________ Your Code Here _____________ */

// type Includes<T extends readonly any[], U> = T extends [infer First, ...infer Rest]
//   ? ([First] extends [U] ? ([U] extends [First] ? true: Includes<Rest, U>) : Includes<Rest, U>)
//   : false
type Includes<T extends any[], U> = T extends [infer First, ...infer Rest]
  ? Equal<First, U> extends true ? true : Includes<Rest, U>
  : false

/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type cases = [
  Expect<Equal<Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Kars'>, true>>,
  Expect<Equal<Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'>, false>>,
  Expect<Equal<Includes<[1, 2, 3, 5, 6, 7], 7>, true>>,
  Expect<Equal<Includes<[1, 2, 3, 5, 6, 7], 4>, false>>,
  Expect<Equal<Includes<[1, 2, 3], 2>, true>>,
  Expect<Equal<Includes<[1, 2, 3], 1>, true>>,
  Expect<Equal<Includes<[{}], { a: 'A' }>, false>>,
  Expect<Equal<Includes<[boolean, 2, 3, 5, 6, 7], false>, false>>,
  Expect<Equal<Includes<[true, 2, 3, 5, 6, 7], boolean>, false>>,
  Expect<Equal<Includes<[false, 2, 3, 5, 6, 7], false>, true>>,
  Expect<Equal<Includes<[{ a: 'A' }], { readonly a: 'A' }>, false>>,
  Expect<Equal<Includes<[{ readonly a: 'A' }], { a: 'A' }>, false>>,
  Expect<Equal<Includes<[1], 1 | 2>, false>>,
  Expect<Equal<Includes<[1 | 2], 1>, false>>,
  Expect<Equal<Includes<[null], undefined>, false>>,
  Expect<Equal<Includes<[undefined], null>, false>>,
]

asann3asann3

Push

level: easy

アプローチ

type Push<T, U> = [...T, ...U]と書いた
第二引数は配列じゃないので
type Push<T extends [], U> = [...T, U]と書き、extendsを加えた
[1, 2] などは[]の部分型ではないので
any[]を加えて終了
type Push<T extends any[], U> = [...T, U]

解答

/* _____________ Your Code Here _____________ */

type Push<T extends any[], U> = [...T, U]

/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type cases = [
  Expect<Equal<Push<[], 1>, [1]>>,
  Expect<Equal<Push<[1, 2], '3'>, [1, 2, '3']>>,
  Expect<Equal<Push<['1', 2, '3'], boolean>, ['1', 2, '3', boolean]>>,
]

asann3asann3

Unshift

level: easy

アプローチ

特になし
unshift とは、指定要素を配列の先頭に追加するもの
追加時は配列の長さを返すらしい
ref: https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift

解答

/* _____________ Your Code Here _____________ */

type Unshift<T extends any[], U> = [U, ...T]

/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type cases = [
  Expect<Equal<Unshift<[], 1>, [1]>>,
  Expect<Equal<Unshift<[1, 2], 0>, [0, 1, 2]>>,
  Expect<Equal<Unshift<['1', 2, '3'], boolean>, [boolean, '1', 2, '3']>>,
]

asann3asann3

Parameters

level: easy

アプローチ

関数型の引数の型を取得したい
infer を使えば良さそう
type MyParameters<T extends (...args: any[]) => any> = T extends (...args: infer R) => any ? R : never
[]にならないかと思ったが、any[]をinfer Rしているので[]の形で返る

解答

/* _____________ Your Code Here _____________ */

type MyParameters<T extends (...args: any[]) => any> = T extends (...args: infer R) => any ? R : never

/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

function foo(arg1: string, arg2: number): void {}
function bar(arg1: boolean, arg2: { a: 'A' }): void {}
function baz(): void {}

type cases = [
  Expect<Equal<MyParameters<typeof foo>, [string, number]>>,
  Expect<Equal<MyParameters<typeof bar>, [boolean, { a: 'A' }]>>,
  Expect<Equal<MyParameters<typeof baz>, []>>,
]

asann3asann3

Get Return Type

level: medium

アプローチ

とりあえず
type MyReturnType<T> = T extends () => infer R ? R : never
こんな感じ

  • Expect<Equal<1 | 2, MyReturnType<typeof fn>>>,
  • Expect<Equal<1 | 2, MyReturnType<typeof fn1>>>,
    この2つが通らない

type MyReturnType<T> = T extends (arg: any) => infer R ? R : never
引数を任意で持たせるようにしたら、上の方は通った
複数引数にも対応させる必要がある

type MyReturnType<T> = T extends (arg1: any, arg2: any) => infer R ? R : never
これは嘘解法

type MyReturnType<T> = T extends (...args: never[]) => infer R ? R : never
これで良さそう
この記事が参考になった
unknown型とnever型は対をなす型
unknown型はanyと似た雰囲気を持つが、型の絞り込みをしないと値を扱えないのが利点

解答

/* _____________ Your Code Here _____________ */

type MyReturnType<T> = T extends (...args: never[]) => infer R ? R : never

/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type cases = [
  Expect<Equal<string, MyReturnType<() => string>>>,
  Expect<Equal<123, MyReturnType<() => 123>>>,
  Expect<Equal<ComplexObject, MyReturnType<() => ComplexObject>>>,
  Expect<Equal<Promise<boolean>, MyReturnType<() => Promise<boolean>>>>,
  Expect<Equal<() => 'foo', MyReturnType<() => () => 'foo'>>>,
  Expect<Equal<1 | 2, MyReturnType<typeof fn>>>,
  Expect<Equal<1 | 2, MyReturnType<typeof fn1>>>,
]

type ComplexObject = {
  a: [12, 'foo']
  bar: 'hello'
  prev(): number
}

const fn = (v: boolean) => v ? 1 : 2
const fn1 = (v: boolean, w: any) => v ? 1 : 2