Skip to main content

Command Palette

Search for a command to run...

Til: 타입스크립트의 이상한 동작

Updated
3 min read

삽질하다가 뭐가 문제인지 깨닫고 잠깐 쉴 겸 쓰는 글이다.

stcAwaited 타입 지원을 추가하기 위해 작업하고 있었다.

//@strict: true

export type Foo = Awaited<Promise<{ x: number }>>;

declare var foo: Foo;
var foo = { x: 1 };

위와 같은 간단한 테스트 케이스를 만들고 디버깅을 했다. 그런데 디버깅을 하면 할수록 뭔가 이상했다. 최종 결과엔 분명 오류가 있는데 각 함수 반환 값을 찍어보면 다 정상적이었다. 그래서 테스트를 잘게 쪼개기 시작했다.

export type T2 = Promise<{ x: number }> extends object & {
  then(onfulfilled: infer F, ...args: infer _): any;
} // `await` only unwraps object types with a callable `then`. Non-object types are not unwrapped
  ? F extends (value: infer V, ...args: infer _) => any // if the argument to `then` is callable, extracts the first argument
    ? V
    : never // the argument to `then` was not callable
  : Promise<{ x: number }>; // non-object or non-thenable:

declare var foo: T2;
declare var foo: { x: number };

그리고 이게 stc 가 실패하는 테스트 중 하나였다. extends가 2개 있어서 디버깅이 힘드니 이것도 쪼개기로 했다. 겸사겸사 F의 타입도 알아낼 겸 아래와 같은 테스트를 작성했다.

export type T2 = Promise<{ x: number }> extends object & {
  then(onfulfilled: infer F, ...args: infer _): any;
}
  ? F
  : never;

declare var foo: T2;
declare var foo: ((value: { x: number }) => unknown) | null | undefined;

tsc가 이 테스트 케이스에 대해 성공하고, extends가 참인 것은 자명하므로, F의 타입은 ((value: { x: number }) => unknown) | null | undefined;이다. 이제 이 타입을 복사해서 인라이닝하면 뭐가 문제인지 나오겠지싶었다.

export type T2 = Promise<{ x: number }> extends object & {
  then(onfulfilled: infer F, ...args: infer _): any;
} // `await` only unwraps object types with a callable `then`. Non-object types are not unwrapped
  ? ((value: { x: number }) => unknown) | null | undefined extends (
      value: infer V,
      ...args: infer _
    ) => any // if the argument to `then` is callable, extracts the first argument
    ? V
    : never // the argument to `then` was not callable
  : Promise<{ x: number }>; // non-object or non-thenable:

declare var foo: T2;
declare var foo: { x: number };

위의 파일이 F를 인라이닝한 값인데, 이상한 결과가 나왔다. tsc가 실패하더라. 그래서 잠깐 고민하다가 | null | undefined를 지워봤다.

export type T2 = Promise<{ x: number }> extends object & {
  then(onfulfilled: infer F, ...args: infer _): any;
} // `await` only unwraps object types with a callable `then`. Non-object types are not unwrapped
  ? ((value: { x: number }) => unknown) extends (
      value: infer V,
      ...args: infer _
    ) => any // if the argument to `then` is callable, extracts the first argument
    ? V
    : never // the argument to `then` was not callable
  : Promise<{ x: number }>; // non-object or non-thenable:

declare var foo: T2;
declare var foo: { x: number };

이 코드는 타입 검사를 성공하더라. 이건... infer F로 추론된 타입이랑 사용자가 직접 입력한 타입이랑 extends 키워드의 동작이 다르다는 소리다. 타입 시스템이 이런 식으로 동작한다는 게 어지럽긴 한데 해결은 쉬우니까 이만 줄이겠다.

More from this blog

한국의 학벌에 대한 생각

내 블로그의 제목이 kdy1: The way I think 인만큼 앞으로는 내 생각을 더 자주 올리려고 한다. 한국 기준으로, 학벌은 사람을 볼 때 꽤나 유용한 지표이지만, 절대적이지는 않다. 경험적인 얘기일 뿐이지만, 성균관대학교 자퇴생으로서 느낀 것들이 몇 가지 있다. 대학까지 간 사람의 학벌은 학습 능력 x 성실함 에 대체로 비례한다. 그래서 의미가

Apr 3, 20261 min read

인간 지능에 대한 메모장

최종 업데이트: 2026/03/15 지능의 유전 현재 인류 기준으로, 고지능자는 고지능 유전자가 많이 겹친 사람이다. 지능의 유전엔 X 염색체가 매우 중요한 역할을 한다. 그리고 이게 남자와 여자의 지능 분포 차이를 만든다. 극상위권에 여자가 거의 없는 이유가 이것이다. 고지능 X 염색체가 여자한테서 발현되려면 2개가 있어야 한다. 이는 인간의 생

Mar 15, 20262 min read

Ai 코딩 팁 2 (한국어)

발표 자료: https://gamma.app/docs/AI--2a52e7tk3eb1ch1 AI 활용법 관련해서 간단하게 발표를 했다. 발표 자료 앞쪽은 전에 블로그에 올린 글이랑 같은 내용이다. 이 글에서는 기존 글에서 다루지 않은 내용들을 다루겠다. 에러 메시지 및 로깅 구체적 타입 및 스키마 활용 any 타입은 사람에게도 위험하지만, AI에게는 더 위험하다. 마찬가지로, JSON.parse처럼 아무 제약 없는 파싱 느슨한 인터페이스 ...

Jan 30, 20265 min read

kdy1: The way I think

233 posts