"너는 타입을 쓰는 게 아냐. 코드를 쓰면 TypeScript 가 타입을 써."
Inference 엔진 — 가장 강력한 협업자
TypeScript 는 네가 annotate 한 것만 타입 체크하지 않아. 정교한 inference 엔진을 돌려서 코드 읽고 문맥에서 타입을 찾아내 — 값에서, 연산에서, 함수 호출에서, control flow 에서. 이전 lesson 에서 "문에서 annotate" 라고 했지. 이 lesson 이 이유를 설명해: inference 엔진이 복도를 너 대신 처리하니까.
네가 쓴 TypeScript 파일 아무거나 열어서 변수 위 hover 해. 에디터가 모든 binding 의 추론된 타입을 보여줘. 대부분의 경우 같은 거 말할 annotation 이 있어 — 그게 정확히 지울 수 있는 annotation 이야.
Inference 가 일어나는 곳
변수 초기화: const x = 1 은 `x: 1` 추론. let y = 'hi' 는 `y: string` 추론. (응 — `const` 는 literal 타입으로 좁히고 `let` 은 넓혀. 아래 `const` vs `let` 섹션에서 풀어줘.)
함수 return type: return-type annotation 없는 함수는 body 의 `return` 문에서 return type 이 추론돼. function double(n: number) { return n * 2 } 는 자동으로 return type `number`.
Generic call site: `string[]` 의 arr.map(x => x.length) 는 자동으로 `x: string` 추론. x: string 쓸 필요 없어 — call site 가 compiler 한테 말해.
Object literal: { name: 'Pippa', age: 21 } 는 `{ name: string; age: number }` 추론. 각 필드가 개별적으로 추론돼.
Destructuring: const { name } = user 는 `user` 타입 읽어서 `name` 의 옳은 타입 자동으로 얻어.
`const` vs `let` — 좁은 inference vs 넓은 inference
TypeScript 는 binding 이 reassign 가능한지에 따라 추론된 타입을 넓혀:
const status = 'idle'→status: 'idle'(literal 타입, `const` 는 절대 안 바뀌니까).let status = 'idle'→status: string(넓혀짐, `let` 은 어떤 string 으로든 reassign 가능하니까).
이 widening 규칙이 사람들이 나중에 as const 손 뻗는 이유 — mutable binding 이나 array literal 에서도 좁은 inference 강제하려고. as const 는 Arrays & Tuples 트랙에서 자세히 다뤄.
Control flow 안의 inference
Inference 는 초기화에만 있는 게 아냐. `if` 분기, `switch` case, early return, 심지어 short-circuit 표현까지 타입을 추적해. 이걸 control flow 분석 이라고 하고, Narrowing 트랙에서 깊이 다뤄.
작은 미리보기: if (typeof x === 'string') { /* 여기 x 는 string 으로 narrow */ } 안에서 compiler 는 그 분기 안에서만 `x` 가 `string` 타입이라는 거 알아. 분기 밖에선 가졌던 더 넓은 타입으로 돌아가. Compiler 가 이 narrowing-graph 를 자동으로 만들어 — 얻기 위해 annotation 하나도 쓸 필요 없어.