Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions docs/SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,23 @@ All in `PM/commonTest`, so they run wherever the interpreter loads.
The Gradle task declares exact inputs and outputs so up-to-date checks and the build cache apply.
Tests: changing one function body recompiles that module only; an unchanged rebuild spends ~0 s in
the compile step. `Status: planned`.
- **N-11** TypedPython fixed-layout classes (M2b, 2026-11-21; design §4.3.3). A class compiles to fixed
field access only when its layout is fixed **in CPython too**: it declares `__slots__` listing exactly
its annotated instance fields, has no base but `object`, no metaclass, no decorator but `@compiled`,
and (gate, compiled mode) nothing in the project subclasses it or assigns its class attributes. The
compiler never adds `__slots__` itself — that would change what CPython does (`__dict__`, `vars()`,
adding attributes). What is compiled, keeping CPython's results:
- field reads/writes on a value guarded `type(x) is C` go straight to the slot (the member descriptor's
offset, captured and checked at module init); an unset slot raises CPython's AttributeError; values
are objects (no unboxing of fields in impure code);
- a slot read is not an effect (no user code runs: the attribute is a member descriptor, checked at
init, and the exact-type guard rules out subclasses), so a function that only reads fields can stay pure;
- `C(args)` whose `__init__` only assigns each slot once from its parameters is lowered to allocate and
fill the slots directly (the same observable result as running that `__init__`); any other `__init__`
is called as an object;
- methods compile like functions with `self` guarded `type(self) is C`; anything else on the class stays
interpreted.
A class outside these rules stays interpreted with a reason. `Status: planned`.

---

Expand Down
18 changes: 18 additions & 0 deletions docs/design/typedpython.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,24 @@ API 안정성을 확인한 뒤로 미룬다.
인터페이스 해시가 그대로라 의존 모듈은 다시 컴파일하지 않는다. C 오브젝트는 모듈별로 캐시한다.
Gradle 태스크는 입력·출력을 정확히 선언해 up-to-date 판정과 빌드 캐시가 함께 작동한다.

### 4.3.3 고정 레이아웃 클래스 (SPEC N-11, M2b)

**레이아웃이 CPython 에서도 고정일 때만** 고정 접근으로 컴파일한다 — 사용자가 `__slots__` 를 직접 쓴 클래스.
컴파일러가 `__slots__` 를 몰래 더하면 `__dict__`·`vars()`·속성 추가가 CPython 과 달라지므로("같은 결과" 원칙
위반) 하지 않는다. binary-trees 벤치의 `Node` 는 그래서 `__slots__ = ("left", "right")` 를 소스에 적어야
컴파일되고, 그 변경은 모든 언어 열에 공평하게 적용되도록 벤치 소스 자체에 넣는다(CPython 기준선 재측정 필요).

- **필드 접근:** `type(x) is C` 로 가드된 값의 필드는 member descriptor 의 오프셋으로 바로 읽고 쓴다. 오프셋은
모듈 초기화 때 클래스 사전의 descriptor 가 정말 member descriptor 인지 확인하고 잡는다. 값은 객체 그대로다.
- **순수성:** 슬롯 읽기는 사용자 코드를 실행하지 않으므로 효과가 아니다 — `check(node)` 같은 재귀 순회가
순수 함수로 남아 i64 경로를 쓸 수 있다.
- **생성:** `__init__` 이 매개변수를 각 슬롯에 한 번씩 대입하기만 하면 `C(a, b)` 를 할당 + 슬롯 채우기로
내린다. 그 밖의 `__init__` 은 객체 호출로 남는다.
- **메서드:** `self` 를 `type(self) is C` 로 가드한 함수로 컴파일한다.
- **IR 확장(예정):** `FieldGet(obj, cls, field)`, `FieldSet(obj, cls, field, value)`, `New(cls, args)`,
그리고 정확한 타입 가드 `IsExact(obj, cls)` 조건. 검증기는 가드 없는 필드 접근, 슬롯 밖 필드, 생성자 규칙
위반을 거부한다.

검토하고 기각한 경로:

- **Cython** — 사용자 결정으로 폐기(위).
Expand Down
Loading