오버로드는 ReturnType·infer 로 하나를 고를 수 없다 — 마지막 시그니처만 본다. 시그니처를 알면 할당 가능한지로 고른다.

T extends R 는 “R 처럼 불러도 T 가 받아내나?“를 묻는다. 오버로드된 T 는 시그니처 중 하나만 받아내면 된다. 시그니처 하나가 받아내는 조건1은 셋이다.

  1. R 이 넘기는 인자보다 더 요구하지 않는다 — 남는 인자는 버리면 된다
  2. 받는 인자의 타입이 맞는다
  3. 돌려주는 값이 R 의 반환 자리에 들어간다
type Year = {
  year(): number
  year(u: string): string
}

// 추출이 아니라 할당 검사: 받아내는 시그니처가 있으면 R 을 그대로 돌려준다
type ReturnTo<T, R> = T extends R ? R : never

type GetYear = ReturnTo<Year['year'], () => number> // () => number
type SetYear = ReturnTo<Year['year'], (u: string) => string> // (u: string) => string
type Loose = ReturnTo<Year['year'], (u: number) => number> // 통과 — year() 가 u 를 버리고 받아낸다 (1)

type A = ReturnType<Year['year']> // string — 마지막만. 순서를 바꾸면 number
type B = Year['year'] extends () => infer R ? R : never // never

infer 는 묻지 않고 꺼낸다 — 마지막 시그니처에서 R 을 정하고, 인자 목록으로 오버로드를 고르는 건 불가능하다2. B 는 R = string 을 꺼낸 뒤 () => string 을 받아내는 시그니처가 없어서 never 다.

직접 정의하는 쪽이면 오버로드 대신 제네릭 하나로 — year<U extends [] | [string]>(...a: U): U extends [] ? number : string 이면 ReturnType<typeof y.year<[]>> 로 꺼낸다.


Footnotes

  1. Type Compatibility — Comparing two functions. 오버로드: “each overload in the target type must be matched by a compatible signature on the source type”. 인자 버리기: “ignoring extra function parameters is actually quite common in JavaScript”. ↩

  2. Conditional Types — Inferring Within Conditional Types: “inferences are made from the last signature … It is not possible to perform overload resolution based on a list of argument types.” ↩

`#345`