From a59096f40bf5cdd7f89d6889b353001518a1db03 Mon Sep 17 00:00:00 2001
From: BREW <81044362+b-re-w@users.noreply.github.com>
Date: Fri, 30 Oct 2026 04:54:13 +0900
Subject: [PATCH 1/2] TypedPython: @compiled is the only mark; drop the '#
typedpython: compiled' module pragma
The maintainer decided (2026-10-30) to keep only the builtin @compiled. The pragma is now an ordinary comment; tests, the Kotlin-bridge fixture, benchmarks and the release example decorate the functions the pragma used to cover.
---
.../2026-10-17-buffer-atomic-kernels.py | 4 +-
benchmarks/typedpython/run.py | 16 +++-
docs/SPEC.md | 2 +-
docs/design/typedpython.md | 6 +-
docs/release/desktop/typedpython/kernels.py | 3 +-
.../typedpython/tp_kotlin_bridge.py | 3 +-
.../src/main/python/typedpython/__main__.py | 4 +-
.../src/main/python/typedpython/frontend.py | 26 ++-----
.../src/test/python/conftest.py | 20 +++++
.../src/test/python/test_bitwise.py | 27 ++++++-
.../src/test/python/test_buffer_arrays.py | 24 +++++-
.../src/test/python/test_buffer_memoryview.py | 11 ++-
.../src/test/python/test_buffer_numpy.py | 76 +++++++++++++++++-
.../src/test/python/test_class_methods.py | 20 +++--
.../src/test/python/test_cost_rule.py | 19 +++--
.../src/test/python/test_eval_breaker.py | 19 ++++-
.../src/test/python/test_fast_entry.py | 13 +++-
.../src/test/python/test_free_threaded.py | 20 ++++-
.../src/test/python/test_frontend.py | 29 +++++--
.../src/test/python/test_frontend_classes.py | 9 +--
.../src/test/python/test_interval_proofs.py | 17 +++-
.../src/test/python/test_module_report.py | 29 +++----
.../src/test/python/test_native_int_impure.py | 35 +++++++--
.../src/test/python/test_osr_exit.py | 4 +-
.../src/test/python/test_parallel_loops.py | 78 +++++++++++++++++--
.../src/test/python/test_pipeline.py | 16 ++--
.../test/python/test_pipeline_incremental.py | 19 +++--
.../src/test/python/test_recursion.py | 8 +-
.../src/test/python/test_regions.py | 13 +++-
.../src/test/python/test_resume.py | 4 +-
.../src/test/python/test_str_dict.py | 38 ++++++++-
.../src/test/python/test_tracing.py | 5 +-
32 files changed, 498 insertions(+), 119 deletions(-)
diff --git a/benchmarks/typedpython/results/2026-10-17-buffer-atomic-kernels.py b/benchmarks/typedpython/results/2026-10-17-buffer-atomic-kernels.py
index a0cd59c56..53a917186 100644
--- a/benchmarks/typedpython/results/2026-10-17-buffer-atomic-kernels.py
+++ b/benchmarks/typedpython/results/2026-10-17-buffer-atomic-kernels.py
@@ -1,7 +1,7 @@
-# typedpython: compiled
import array
+@compiled
def dot_arr(a: array.array[float], b: array.array[float]) -> float:
s: float = 0.0
for i in range(len(a)):
@@ -9,11 +9,13 @@ def dot_arr(a: array.array[float], b: array.array[float]) -> float:
return s
+@compiled
def scale_arr(a: array.array[float], k: float) -> None:
for i in range(len(a)):
a[i] = a[i] * k
+@compiled
def dot_list(a: list[float], b: list[float]) -> float:
s: float = 0.0
for i in range(len(a)):
diff --git a/benchmarks/typedpython/run.py b/benchmarks/typedpython/run.py
index 104ad572d..c6c3aaa47 100644
--- a/benchmarks/typedpython/run.py
+++ b/benchmarks/typedpython/run.py
@@ -10,7 +10,7 @@
The "TypedPython @compiled" column runs only with `--typedpython
` (the directory that holds
the `typedpython` package, python-multiplatform-ksp/src/main/python) and `--typedpython-python` (an
interpreter with the gate's dependencies, e.g. that module's .venv): each benchmark is copied with
-the `# typedpython: compiled` marker, compiled through `typedpython.pipeline`, and its `main()` is
+`@compiled` above every top-level def and class, compiled through `typedpython.pipeline`, and its `main()` is
run from the built extension. Without the option the column reports n/a.
binary-trees declares `__slots__` (TypedPython compiles fixed-layout classes only, SPEC N-11). The
@@ -18,6 +18,7 @@
with the `__slots__` line removed, so what adding the slots changes in CPython is recorded too.
"""
import argparse
+import ast
import datetime
import json
import os
@@ -87,6 +88,17 @@ def build() -> None:
"""
+def with_compiled(source: str) -> str:
+ """`source` with `@compiled` above every top-level def and class (above their other decorators)."""
+ tree = ast.parse(source)
+ lines = source.split("\n")
+ firsts = [min([n.lineno] + [d.lineno for d in n.decorator_list]) for n in tree.body
+ if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef))]
+ for first in reversed(firsts):
+ lines.insert(first - 1, "@compiled")
+ return "\n".join(lines)
+
+
def build_compiled(tp_dir: Path, tp_python: str) -> dict[str, tuple[str, list[str]] | str]:
"""Compile each benchmark with TypedPython; name -> (extension, compiled functions) or a reason."""
built: dict[str, tuple[str, list[str]] | str] = {}
@@ -95,7 +107,7 @@ def build_compiled(tp_dir: Path, tp_python: str) -> dict[str, tuple[str, list[st
work = TP_BUILD / name
work.mkdir(parents=True, exist_ok=True)
source = work / py
- source.write_text("# typedpython: compiled\n" + (ROOT / "py" / py).read_text())
+ source.write_text(with_compiled((ROOT / "py" / py).read_text()))
result = subprocess.run([tp_python, "-c", COMPILE_SNIPPET, str(source), str(work / "out")],
capture_output=True, text=True, env=env)
if result.returncode != 0:
diff --git a/docs/SPEC.md b/docs/SPEC.md
index 1ba12bbc1..61c454c2f 100644
--- a/docs/SPEC.md
+++ b/docs/SPEC.md
@@ -724,7 +724,7 @@ All in `PM/commonTest`, so they run wherever the interpreter loads.
with `.diagnostics`) before the module runs. Results are cached in memory by file content hash, failures
included. `Status: implemented`, `python-multiplatform-ksp/src/test/python/test_importgate.py`.
- **N-8** TypedPython compilation to C (INTENT §1.4; design §4.3; #41). `@compiled` (a builtin name, no
- import, #42) and a first-line `# typedpython: compiled` mark functions; the compiler lowers them
+ import, #42) is the only mark for functions (the first-line module pragma was removed, #353); the compiler lowers them
through the typed IR (`typedpython/ir.py`) to C against the CPython C API and builds an extension with
the platform C compiler. Compiled functions return what CPython returns (IR contract: checked i64
with deopt to the interpreted function, Python floor division/modulo, exact int/float comparison,
diff --git a/docs/design/typedpython.md b/docs/design/typedpython.md
index 1ae91c1c2..991207854 100644
--- a/docs/design/typedpython.md
+++ b/docs/design/typedpython.md
@@ -62,7 +62,7 @@ TypedPython 의 목표는 **새 언어를 만들지 않고, 파이썬 문법 그
|---|---|---|---|
| 0 | 서드파티 패키지 (의존성) | 없음, 검사하지 않는다 | 바이트코드 (pypackpack 이 받은 그대로) |
| 1 `checked` | **사용자 프로젝트의 파이썬 코드 전부, 기본값** | 빌드 타임 타입 오류, `Any` 유출 0 (§4.2) | 바이트코드 |
-| 1 `compiled` | `@compiled` (내장 이름, import 없음) 또는 모듈 선언 `# typedpython: compiled` | `checked` + §3.1 금지 목록 | C 확장 모듈 (NEON · AVX 벡터화, §5.3) |
+| 1 `compiled` | `@compiled` (내장 이름, import 없음) | `checked` + §3.1 금지 목록 | C 확장 모듈 (NEON · AVX 벡터화, §5.3) |
| 2 `kernel` | `@kernel` | 객체·힙 할당 없음, 고정 dtype | SPIR-V / MSL (직접) / WGSL / PTX, 빌드 타임 (§5) |
- **사용자 코드는 표시하지 않아도 검사된다.** 타입 오류가 있으면 Kotlin 컴파일 에러처럼 빌드가
@@ -685,9 +685,7 @@ upstream 대비 수치 오차와 커널 시간을 기준선으로 박아 둔다.
인터프리터 시작 시 `builtins.compiled` 를 설치하고(#42), 일반 CPython 에서는 `typedpython` wheel 의
`typedpython_builtins.pth` 가 `builtins` 에 없을 때만 항등 함수를 설치한다(런타임이 넣은 것을 덮어쓰지
않는다). 타입 검사기에는 `__builtins__.pyi` 로 알린다, Pyrefly 1.3.2 는 검색 경로의 그 파일을 추가
- 내장으로 읽는다(실측). 모듈 단위 표기는 `# typedpython: compiled` 주석을 유지한다: `# pyright: strict` 와 같은
- 도구 pragma 형태이고, 실행 시 이름 조회가 필요 없어 일반 CPython 에서 부작용이 없으며, 데코레이터 없이
- 모듈 전체를 지정할 수 있다.
+ 내장으로 읽는다(실측). 모듈 단위 pragma(`# typedpython: compiled`)는 2026-10-30 메인테이너 결정으로 제거했다(#353). `@compiled` 가 컴파일 대상을 표시하는 유일한 수단이다.
2. ~~고정폭 정수의 오버플로 의미~~, **결정됨:** `int` 는 i64 로 컴파일하고 오버플로 시 인터프리터 재실행으로 큰 정수로 승격(의미 보존).
3. ~~pypackpack Cython 슬롯 인터페이스 소유~~, **결정됨:** pypackpack 이 정한다. TypedPython 은 생성한 C 소스, 빌드 플래그, C 를 낸 모듈 목록을 넘긴다. 확장이 원본 소스를 품으므로 별도 `.py` 폴백은 없다(Cython 폐기 후 갱신).
4. ~~GPU 착지를 torchnative 커스텀 op 로 할 것인가~~, **결정됨 (2026-10-04, 메인테이너):** 커널은
diff --git a/docs/release/desktop/typedpython/kernels.py b/docs/release/desktop/typedpython/kernels.py
index 81cc9f952..3fb98914e 100644
--- a/docs/release/desktop/typedpython/kernels.py
+++ b/docs/release/desktop/typedpython/kernels.py
@@ -1,7 +1,7 @@
-# typedpython: compiled
import sys
+@compiled
def dot(n: int) -> float:
s = 0.0
for i in range(n):
@@ -10,6 +10,7 @@ def dot(n: int) -> float:
return s
+@compiled
def collatz_steps(limit: int) -> int:
total = 0
for start in range(1, limit):
diff --git a/python-multiplatform-ksp/fixtures/app/src/desktopTest/typedpython/tp_kotlin_bridge.py b/python-multiplatform-ksp/fixtures/app/src/desktopTest/typedpython/tp_kotlin_bridge.py
index 7677eca9c..311eb2f5b 100644
--- a/python-multiplatform-ksp/fixtures/app/src/desktopTest/typedpython/tp_kotlin_bridge.py
+++ b/python-multiplatform-ksp/fixtures/app/src/desktopTest/typedpython/tp_kotlin_bridge.py
@@ -1,4 +1,3 @@
-# typedpython: compiled
"""Issue #45: compiled TypedPython code calling Kotlin through the binder.
Every name imported below is a binder proxy (`fixture/app/TypedPythonBridge.kt`, published by
@@ -21,6 +20,7 @@
from fixture.app import TpSample, tpAccumulate, tpCheck, tpTouch
+@compiled
def drive(n: int, scale: float, token: object) -> float:
"""Per step: a Kotlin constructor call that returns a typed proxy, a method call on that proxy,
the proxy handed back to a Kotlin function, and a PyObject handed to Kotlin -- inside native
@@ -35,6 +35,7 @@ def drive(n: int, scale: float, token: object) -> float:
return total
+@compiled
def checked(n: int, limit: float) -> float:
"""`tpCheck` throws in Kotlin once `acc` passes `limit`; the caller sees what Python sees."""
acc: float = 0.0
diff --git a/python-multiplatform-ksp/src/main/python/typedpython/__main__.py b/python-multiplatform-ksp/src/main/python/typedpython/__main__.py
index bd220e713..a9cda63e2 100644
--- a/python-multiplatform-ksp/src/main/python/typedpython/__main__.py
+++ b/python-multiplatform-ksp/src/main/python/typedpython/__main__.py
@@ -84,7 +84,7 @@ def _build(paths: list[Path], cache: Path, out: Path | None) -> int:
print(f"{m.status:7} {m.name}: {what}{why}")
rel = m.path.relative_to(root).as_posix()
if m.kind == "not_marked":
- print(f" {rel}: not marked for compilation (no `# typedpython: compiled`, no @compiled)")
+ print(f" {rel}: not marked for compilation (no @compiled)")
for item in m.skipped_items:
where = f"{item.file}:{item.line}" if item.line is not None else item.file
print(f" {where}: {item.name}: {item.message}")
@@ -112,6 +112,8 @@ def _demo(module: Path, module_argv: list[str], out: Path) -> int:
return EXIT_FAILURE
def interpreted() -> object:
+ import builtins # the module's `@compiled` is a builtin name; typedpython_builtins.pth installs it
+ builtins.compiled = getattr(builtins, "compiled", lambda f, /: f)
spec = importlib.util.spec_from_file_location(module.stem + "_interpreted", module)
assert spec is not None and spec.loader is not None
mod = importlib.util.module_from_spec(spec)
diff --git a/python-multiplatform-ksp/src/main/python/typedpython/frontend.py b/python-multiplatform-ksp/src/main/python/typedpython/frontend.py
index 0da894f10..6e0b75aa6 100644
--- a/python-multiplatform-ksp/src/main/python/typedpython/frontend.py
+++ b/python-multiplatform-ksp/src/main/python/typedpython/frontend.py
@@ -3,8 +3,7 @@
`lower(path)` reads one module and returns an `ir.Module`:
- Which functions: module-level `def`s decorated with the builtin `@compiled` (a bare name, no
- import, #42), or every module-level `def` when the first non-empty line is
- `# typedpython: compiled`.
+ import, #42). That decorator is the only mark; there is no module-level pragma (#353).
- A function is lowered only when **all** of it is expressible in the IR; otherwise it is left out
and `Module.skipped[name]` says why, with the line. There is no partial lowering.
- Types: parameters and returns from their annotations (`int`/`float`/`bool`, `list[float]` and
@@ -80,8 +79,6 @@
from typedpython import gate, ir, pyrefly, rebinding, resume, taint
from typedpython.ir import Type
-MARKER = "# typedpython: compiled"
-
I64_MIN, I64_MAX = -2**63, 2**63 - 1
TOP = (I64_MIN, I64_MAX)
EXACT_INT_FLOAT = 2**53 # |n| <= this: int/int true division is exact-rounded in C (ir.BinOp)
@@ -249,7 +246,7 @@ def lower(path: Path) -> ir.Module:
def _parallel_marked(tree: ast.Module, source: str) -> set[str]:
"""Module-level functions the user marked for SPEC N-15: the comment line `# typedpython: parallel`
- directly above the `def` (above its decorators if it has any). A comment, like the module marker,
+ directly above the `def` (above its decorators if it has any). A comment,
so the interpreted module is unchanged and needs no runtime name."""
lines = source.splitlines()
out: set[str] = set()
@@ -434,17 +431,10 @@ def _binding_targets(stmt: ast.stmt) -> list[ast.expr]:
SLOT_ACCESS_DUNDERS = ("__getattribute__", "__getattr__", "__setattr__", "__delattr__", "__new__")
-def _whole_module(source: str) -> bool:
- return next((line.strip() for line in source.splitlines() if line.strip()), "") == MARKER
-
-
def is_opted_in(path: Path) -> bool:
- """Whether the front end considers the module for compilation at all: the `# typedpython: compiled`
- marker as its first non-empty line, or a module-level `def` / `class` decorated `@compiled`
- (the same tests `_targets` and `_classes` apply)."""
+ """Whether the front end considers the module for compilation at all: a module-level `def` / `class`
+ decorated `@compiled` (the same test `_targets` and `_classes` apply)."""
source = Path(path).read_text()
- if _whole_module(source):
- return True
return any(
isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef))
and any(isinstance(d, ast.Name) and d.id == "compiled" for d in node.decorator_list)
@@ -454,14 +444,13 @@ def is_opted_in(path: Path) -> bool:
def _classes(
tree: ast.Module, source: str, info: _ModuleInfo,
) -> tuple[dict[str, ir.ClassDecl], dict[str, str]]:
- whole = _whole_module(source)
decls: dict[str, ir.ClassDecl] = {}
skipped: dict[str, str] = {}
for node in tree.body:
if not isinstance(node, ast.ClassDef):
continue
marked = any(isinstance(d, ast.Name) and d.id == "compiled" for d in node.decorator_list)
- if not (whole or marked):
+ if not marked:
continue
result = _class_decl(node, tree, info)
if isinstance(result, str):
@@ -608,13 +597,12 @@ def _class_of(kind: str, classes: dict[str, ir.ClassDecl]) -> tuple[str, bool] |
def _targets(
tree: ast.Module, source: str, info: _ModuleInfo,
) -> list[tuple[ast.FunctionDef | ast.AsyncFunctionDef, str | None]]:
- whole_module = _whole_module(source)
found: list[tuple[ast.FunctionDef | ast.AsyncFunctionDef, str | None]] = []
for node in tree.body:
if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
continue
marked = [d for d in node.decorator_list if isinstance(d, ast.Name) and d.id == "compiled"]
- if not (whole_module or marked):
+ if not marked:
continue
reason = None
if marked and info.bound("compiled"):
@@ -2499,4 +2487,4 @@ def _breaks(body: tuple[ir.Stmt, ...]) -> bool:
return False
-__all__ = ["lower", "MARKER"]
+__all__ = ["lower"]
diff --git a/python-multiplatform-ksp/src/test/python/conftest.py b/python-multiplatform-ksp/src/test/python/conftest.py
index 452a4e613..669816bf4 100644
--- a/python-multiplatform-ksp/src/test/python/conftest.py
+++ b/python-multiplatform-ksp/src/test/python/conftest.py
@@ -1,12 +1,32 @@
+import ast
+import builtins
import sys
from pathlib import Path
import textwrap
import pytest
+# The `typedpython_builtins.pth` of an installed wheel; the test interpreter has no such install.
+builtins.compiled = getattr(builtins, "compiled", lambda f, /: f)
+
FIXTURES = Path(__file__).parent / "fixtures"
+def decorate_compiled(source: str) -> str:
+ """`source` with `@compiled` put above every module-level def / class (above its other decorators),
+ the set a whole-module mark used to cover. A source that does not parse is returned unchanged."""
+ try:
+ tree = ast.parse(source)
+ except SyntaxError:
+ return source
+ lines = source.split("\n")
+ firsts = [min([n.lineno] + [d.lineno for d in n.decorator_list]) for n in tree.body
+ if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef))]
+ for first in reversed(firsts):
+ lines.insert(first - 1, "@compiled")
+ return "\n".join(lines)
+
+
@pytest.fixture
def write(tmp_path):
"""Write a dedented source file under tmp_path and return its path."""
diff --git a/python-multiplatform-ksp/src/test/python/test_bitwise.py b/python-multiplatform-ksp/src/test/python/test_bitwise.py
index 0bd844034..c0d48b3d4 100644
--- a/python-multiplatform-ksp/src/test/python/test_bitwise.py
+++ b/python-multiplatform-ksp/src/test/python/test_bitwise.py
@@ -20,33 +20,39 @@
from test_native_int_impure import build_source
SOURCE = textwrap.dedent('''
- # typedpython: compiled
+ @compiled
def p_and(a: int, b: int) -> int:
return a & b
+ @compiled
def p_or(a: int, b: int) -> int:
return a | b
+ @compiled
def p_xor(a: int, b: int) -> int:
return a ^ b
+ @compiled
def p_inv(a: int) -> int:
return ~a
+ @compiled
def p_shl(a: int, b: int) -> int:
return a << b
+ @compiled
def p_shr(a: int, b: int) -> int:
return a >> b
+ @compiled
def p_aug(a: int, b: int) -> int:
x: int = a
x ^= b
@@ -55,22 +61,26 @@ def p_aug(a: int, b: int) -> int:
return x
+ @compiled
def p_augshl(a: int, b: int) -> int:
x: int = a
x <<= b
return x
+ @compiled
def p_augshr(a: int, b: int) -> int:
x: int = a
x >>= b
return x
+ @compiled
def p_mix(a: int, b: int) -> int:
return (((a & 255) << 3) | (b >> 2)) ^ ~a
+ @compiled
def p_mask_loop(n: int) -> int:
h: int = 0
for i in range(n):
@@ -78,6 +88,7 @@ def p_mask_loop(n: int) -> int:
return h
+ @compiled
def p_array(xs: list[int]) -> None:
for i in range(len(xs)):
xs[i] &= 0xFF
@@ -85,10 +96,12 @@ def p_array(xs: list[int]) -> None:
xs[i] ^= i
+ @compiled
def p_iv(a: int, b: int) -> int:
return (a & 0xFFFF) * (b & 0xFFFF) + ~(a & 7)
+ @compiled
def i_and(log: list[object], a: int, b: int) -> object:
log.append("start")
r: int = a & b
@@ -96,6 +109,7 @@ def i_and(log: list[object], a: int, b: int) -> object:
return r
+ @compiled
def i_or(log: list[object], a: int, b: int) -> object:
log.append("start")
r: int = a | b
@@ -103,6 +117,7 @@ def i_or(log: list[object], a: int, b: int) -> object:
return r
+ @compiled
def i_xor(log: list[object], a: int, b: int) -> object:
log.append("start")
r: int = a ^ b
@@ -110,6 +125,7 @@ def i_xor(log: list[object], a: int, b: int) -> object:
return r
+ @compiled
def i_inv(log: list[object], a: int) -> object:
log.append("start")
r: int = ~a
@@ -117,6 +133,7 @@ def i_inv(log: list[object], a: int) -> object:
return r
+ @compiled
def i_shl(log: list[object], a: int, b: int) -> object:
log.append("start")
r: int = a << b
@@ -124,6 +141,7 @@ def i_shl(log: list[object], a: int, b: int) -> object:
return r
+ @compiled
def i_shr(log: list[object], a: int, b: int) -> object:
log.append("start")
r: int = a >> b
@@ -131,6 +149,7 @@ def i_shr(log: list[object], a: int, b: int) -> object:
return r
+ @compiled
def i_aug(log: list[object], a: int, b: int) -> object:
log.append("start")
x: int = a
@@ -143,6 +162,7 @@ def i_aug(log: list[object], a: int, b: int) -> object:
return x
+ @compiled
def i_big(log: list[object], a: int, b: int, c: int) -> object:
log.append("start")
r: int = (a * b) & c
@@ -156,6 +176,7 @@ def i_big(log: list[object], a: int, b: int, c: int) -> object:
return r
+ @compiled
def o_ops(log: list[object], x: object, y: object) -> object:
log.append("start")
r: object = x | y
@@ -164,21 +185,25 @@ def o_ops(log: list[object], x: object, y: object) -> object:
return r
+ @compiled
def o_inplace(log: list[object], x: object, y: object) -> object:
log.append("start")
x |= y
return x
+ @compiled
def f_and(log: list[object], a: float, b: int) -> object:
log.append("start")
return a & b
+ @compiled
def b_and(a: bool, b: bool) -> bool:
return a & b
+ @compiled
def b_inv(a: bool) -> int:
return ~a
''')
diff --git a/python-multiplatform-ksp/src/test/python/test_buffer_arrays.py b/python-multiplatform-ksp/src/test/python/test_buffer_arrays.py
index 71d95f0c0..724f6759e 100644
--- a/python-multiplatform-ksp/src/test/python/test_buffer_arrays.py
+++ b/python-multiplatform-ksp/src/test/python/test_buffer_arrays.py
@@ -29,10 +29,10 @@
from typedpython import cbuild, cgen, frontend, ir, verify
SOURCE = textwrap.dedent('''
- # typedpython: compiled
import array
+ @compiled
def sum_ad(a: array.array[float]) -> float:
s = 0.0
for i in range(len(a)):
@@ -40,6 +40,7 @@ def sum_ad(a: array.array[float]) -> float:
return s
+ @compiled
def sum_aq(a: array.array[int]) -> int:
s = 0
for i in range(len(a)):
@@ -47,70 +48,85 @@ def sum_aq(a: array.array[int]) -> int:
return s
+ @compiled
def get_ad(a: array.array[float], i: int) -> float:
return a[i]
+ @compiled
def get_aq(a: array.array[int], i: int) -> int:
return a[i]
+ @compiled
def set_ad(a: array.array[float], i: int, v: float) -> None:
a[i] = v
+ @compiled
def set_aq(a: array.array[int], i: int, v: int) -> None:
a[i] = v
+ @compiled
def length_ad(a: array.array[float]) -> int:
return len(a)
+ @compiled
def scale_ad(a: array.array[float], k: float) -> None:
for i in range(len(a)):
a[i] = a[i] * k
+ @compiled
def iota_aq(a: array.array[int]) -> None:
for i in range(len(a)):
a[i] = i
+ @compiled
def copy_ad(dst: array.array[float], src: array.array[float]) -> None:
for i in range(len(dst)):
dst[i] = src[i]
+ @compiled
def cascade_ad(a: array.array[float], b: array.array[float]) -> None:
for i in range(len(a)):
a[i] = b[i] + 1.0
+ @compiled
def fill_n(a: array.array[float], n: int) -> None:
for i in range(n):
a[i] = 1.0
+ @compiled
def mixed(l: list[float], a: array.array[float]) -> None:
for i in range(len(a)):
a[i] = l[i]
+ @compiled
def four(a: array.array[float], b: array.array[float], c: array.array[float],
d: array.array[float]) -> None:
for i in range(len(a)):
a[i] = b[i] + c[i] + d[i]
+ @compiled
def div_ad(a: array.array[float]) -> float:
return a[0] / a[1]
+ @compiled
def open_ad(a: array.array[float], o: object) -> float:
return a[0] + float(o.v)
+ @compiled
def rebind_view(a: array.array[float]) -> None:
y = a[:]
y[0] = 5.0
@@ -505,30 +521,34 @@ def many(n, buffer):
# --- vectorised element-wise store loops (#226) ---------------------------------------------------
VEC_SOURCE = textwrap.dedent('''
- # typedpython: compiled
import array
+ @compiled
def vscale(a: array.array[float], k: float) -> None:
for i in range(len(a)):
a[i] = a[i] * k
+ @compiled
def vmix(a: array.array[float], k: float) -> None:
for i in range(len(a)):
a[i] = -(a[i] - k) * 2.5 + k
+ @compiled
def vtwo(a: array.array[float], b: array.array[float]) -> None:
for i in range(len(a)):
a[i] = a[i] + b[i]
+ @compiled
def vfill(a: array.array[float], k: float) -> None:
for i in range(len(a)):
a[i] = k
+ @compiled
def vdiv(a: array.array[float], k: float) -> None:
for i in range(len(a)):
a[i] = a[i] / k
diff --git a/python-multiplatform-ksp/src/test/python/test_buffer_memoryview.py b/python-multiplatform-ksp/src/test/python/test_buffer_memoryview.py
index 1766b3300..8a616abc8 100644
--- a/python-multiplatform-ksp/src/test/python/test_buffer_memoryview.py
+++ b/python-multiplatform-ksp/src/test/python/test_buffer_memoryview.py
@@ -23,10 +23,10 @@
same_value)
SOURCE = textwrap.dedent('''
- # typedpython: compiled
import array
+ @compiled
def sum_mf(m: memoryview[float]) -> float:
s = 0.0
for i in range(len(m)):
@@ -34,6 +34,7 @@ def sum_mf(m: memoryview[float]) -> float:
return s
+ @compiled
def sum_mi(m: memoryview[int]) -> int:
s = 0
for i in range(len(m)):
@@ -41,31 +42,38 @@ def sum_mi(m: memoryview[int]) -> int:
return s
+ @compiled
def get_mf(m: memoryview[float], i: int) -> float:
return m[i]
+ @compiled
def get_mi(m: memoryview[int], i: int) -> int:
return m[i]
+ @compiled
def set_mf(m: memoryview[float], i: int, v: float) -> None:
m[i] = v
+ @compiled
def set_mi(m: memoryview[int], i: int, v: int) -> None:
m[i] = v
+ @compiled
def length_mf(m: memoryview[float]) -> int:
return len(m)
+ @compiled
def scale_mf(m: memoryview[float], k: float) -> None:
for i in range(len(m)):
m[i] = m[i] * k
+ @compiled
def spin_mf(m: memoryview[float], n: int) -> float:
s = 0.0
L = len(m)
@@ -74,6 +82,7 @@ def spin_mf(m: memoryview[float], n: int) -> float:
return s
+ @compiled
def spin_af(a: array.array[float], n: int) -> float:
s = 0.0
L = len(a)
diff --git a/python-multiplatform-ksp/src/test/python/test_buffer_numpy.py b/python-multiplatform-ksp/src/test/python/test_buffer_numpy.py
index 582e4b094..f14a32c00 100644
--- a/python-multiplatform-ksp/src/test/python/test_buffer_numpy.py
+++ b/python-multiplatform-ksp/src/test/python/test_buffer_numpy.py
@@ -21,13 +21,13 @@
from test_buffer_arrays import build_source, load_so # noqa: E402
SOURCE = textwrap.dedent('''
- # typedpython: compiled
import array
import math
import numpy as np
import numpy.typing as npt
+ @compiled
def sum_ad(a: array.array[float]) -> float:
s = 0.0
for i in range(len(a)):
@@ -35,18 +35,22 @@ def sum_ad(a: array.array[float]) -> float:
return s
+ @compiled
def nd_first(x: npt.NDArray[np.float64]) -> object:
return x[0]
+ @compiled
def nd_first_f(x: npt.NDArray[np.float64]) -> float:
return x[0]
+ @compiled
def nd_first_i(x: npt.NDArray[np.int64]) -> int:
return x[0]
+ @compiled
def nd_sum(x: npt.NDArray[np.float64]) -> float:
s = 0.0
for i in range(len(x)):
@@ -54,6 +58,7 @@ def nd_sum(x: npt.NDArray[np.float64]) -> float:
return s
+ @compiled
def nd_sum_from_first(x: npt.NDArray[np.float64]) -> float:
s = x[0]
for i in range(1, len(x)):
@@ -61,6 +66,7 @@ def nd_sum_from_first(x: npt.NDArray[np.float64]) -> float:
return s
+ @compiled
def nd_isum(x: npt.NDArray[np.int64]) -> int:
s = 0
for i in range(len(x)):
@@ -68,64 +74,79 @@ def nd_isum(x: npt.NDArray[np.int64]) -> int:
return s
+ @compiled
def nd_gt(x: npt.NDArray[np.float64]) -> object:
return x[0] > x[1]
+ @compiled
def nd_flag(x: npt.NDArray[np.float64]) -> object:
b = x[0] > 0.0
return b
+ @compiled
def nd_plain(x: npt.NDArray[np.float64]) -> object:
return float(x[0])
+ @compiled
def nd_mixed(x: npt.NDArray[np.float64], k: float) -> object:
return k + 1.0
+ @compiled
def nd_sqrt(x: npt.NDArray[np.float64]) -> object:
return math.sqrt(x[0] * x[0])
+ @compiled
def nd_neg(x: npt.NDArray[np.float64]) -> object:
return -x[1]
+ @compiled
def nd_len(x: npt.NDArray[np.float64]) -> int:
return len(x)
+ @compiled
def nd_set(x: npt.NDArray[np.float64], i: int, v: float) -> None:
x[i] = v
+ @compiled
def nd_copy(dst: npt.NDArray[np.float64], src: npt.NDArray[np.float64]) -> None:
for i in range(len(dst)):
dst[i] = src[i]
+ @compiled
def nd_iset(x: npt.NDArray[np.int64], i: int, v: int) -> None:
x[i] = v
+ @compiled
def nd_get(x: npt.NDArray[np.float64], i: int) -> object:
return x[i]
+ @compiled
def nd_overflow(x: npt.NDArray[np.float64]) -> object:
return x[0] * x[1]
+ @compiled
def nd_invalid(x: npt.NDArray[np.float64]) -> object:
return x[0] - x[1]
+ @compiled
def nd_iover(x: npt.NDArray[np.int64]) -> object:
return x[0] + x[1]
+ @compiled
def nd_energy(x: npt.NDArray[np.float64], y: npt.NDArray[np.float64], z: npt.NDArray[np.float64],
vx: npt.NDArray[np.float64], vy: npt.NDArray[np.float64],
vz: npt.NDArray[np.float64], m: npt.NDArray[np.float64]) -> float:
@@ -135,6 +156,7 @@ def nd_energy(x: npt.NDArray[np.float64], y: npt.NDArray[np.float64], z: npt.NDA
return e
+ @compiled
def nd_copy7(x: npt.NDArray[np.float64], y: npt.NDArray[np.float64], z: npt.NDArray[np.float64],
vx: npt.NDArray[np.float64], vy: npt.NDArray[np.float64],
vz: npt.NDArray[np.float64], m: npt.NDArray[np.float64]) -> None:
@@ -145,20 +167,24 @@ def nd_copy7(x: npt.NDArray[np.float64], y: npt.NDArray[np.float64], z: npt.NDAr
m[i] = x[i]
+ @compiled
def nd_div(x: npt.NDArray[np.float64]) -> object:
return x[0] / x[1]
+ @compiled
def nd_idiv(x: npt.NDArray[np.int64]) -> object:
return x[0] // x[1]
+ @compiled
def nd_param(x: npt.NDArray[np.float64], k: float, use: bool) -> object:
if use:
k = x[0]
return k
+ @compiled
def nd_branch(x: npt.NDArray[np.float64], use: bool) -> object:
if use:
v = x[0]
@@ -167,36 +193,44 @@ def nd_branch(x: npt.NDArray[np.float64], use: bool) -> object:
return v
+ @compiled
def nd_tuple(x: npt.NDArray[np.int64]) -> tuple[int, int, bool]:
return (x[0], 3, x[1] > 0)
+ @compiled
def nd_abs(x: npt.NDArray[np.float64]) -> object:
return abs(x[1])
+ @compiled
def nd_to_list(x: npt.NDArray[np.float64], dst: list[float]) -> None:
dst[0] = x[0]
+ @compiled
def nd_mixed_cmp(x: npt.NDArray[np.int64], f: float) -> object:
if x[0] < f:
return 1
return 0
+ @compiled
def whole_sum(x: npt.NDArray[np.float64]) -> object:
return np.sum(x)
+ @compiled
def whole_attr(x: npt.NDArray[np.float64]) -> object:
return x.size
+ @compiled
def whole_pass(x: npt.NDArray[np.float64]) -> object:
return np.add(x, 1.0).sum()
+ @compiled
def nd_int_impure(x: npt.NDArray[np.int64]) -> object:
s = 0
for i in range(len(x)):
@@ -205,83 +239,103 @@ def nd_int_impure(x: npt.NDArray[np.int64]) -> object:
return s
+ @compiled
def nd_scale(x: npt.NDArray[np.float64], k: float) -> None:
for i in range(len(x)):
x[i] = x[i] * k
+ @compiled
def p_div(x: npt.NDArray[np.float64]) -> object:
return x[0] / x[1]
+ @compiled
def p_fdiv(x: npt.NDArray[np.float64]) -> object:
return x[0] // x[1]
+ @compiled
def p_mod(x: npt.NDArray[np.float64]) -> object:
return x[0] % x[1]
+ @compiled
def p_add(x: npt.NDArray[np.float64]) -> object:
return x[0] + x[1]
+ @compiled
def p_sub(x: npt.NDArray[np.float64]) -> object:
return x[0] - x[1]
+ @compiled
def p_mul(x: npt.NDArray[np.float64]) -> object:
return x[0] * x[1]
+ @compiled
def p_pdiv(x: npt.NDArray[np.float64], k: float) -> object:
return k / x[0]
+ @compiled
def p_idiv(x: npt.NDArray[np.int64]) -> object:
return x[0] / x[1]
+ @compiled
def p_ifdiv(x: npt.NDArray[np.int64]) -> object:
return x[0] // x[1]
+ @compiled
def p_imod(x: npt.NDArray[np.int64]) -> object:
return x[0] % x[1]
+ @compiled
def p_imixed(x: npt.NDArray[np.int64]) -> object:
return x[0] // 3 + x[1] % 4
+ @compiled
def i_div(x: npt.NDArray[np.float64]) -> None:
x[2] = x[0] / x[1]
+ @compiled
def i_fdiv(x: npt.NDArray[np.float64]) -> None:
x[2] = x[0] // x[1]
+ @compiled
def i_mod(x: npt.NDArray[np.float64]) -> None:
x[2] = x[0] % x[1]
+ @compiled
def i_add(x: npt.NDArray[np.float64]) -> None:
x[2] = x[0] + x[1]
+ @compiled
def i_sub(x: npt.NDArray[np.float64]) -> None:
x[2] = x[0] - x[1]
+ @compiled
def i_mul(x: npt.NDArray[np.float64]) -> None:
x[2] = x[0] * x[1]
+ @compiled
def i_plain(x: npt.NDArray[np.float64], k: float) -> None:
x[2] = k / x[1]
x[3] = k * 2.0
+ @compiled
def i_multiline(x: npt.NDArray[np.float64]) -> None:
x[2] = (x[0] *
x[1])
@@ -290,12 +344,14 @@ def i_multiline(x: npt.NDArray[np.float64]) -> None:
+ x[0] * x[1])
+ @compiled
def i_aug(x: npt.NDArray[np.float64]) -> None:
x[0] *= x[1]
x[1] /= x[2]
x[2] += x[3]
+ @compiled
def i_loop(x: npt.NDArray[np.float64], y: npt.NDArray[np.float64]) -> float:
s = 0.0
for i in range(len(x)):
@@ -304,21 +360,25 @@ def i_loop(x: npt.NDArray[np.float64], y: npt.NDArray[np.float64]) -> float:
return s
+ @compiled
def i_mixed(x: npt.NDArray[np.float64], n: int) -> None:
x[0] = n / x[1]
x[2] = x[3] * n
+ @compiled
def i_ret(x: npt.NDArray[np.float64]) -> object:
x[2] = x[0] * x[1]
return x[0] / x[1]
+ @compiled
def i_ifdiv(x: npt.NDArray[np.int64]) -> object:
x[2] = x[0] // x[1]
return x[2]
+ @compiled
def nd_advance(dt: float, steps: int, x: npt.NDArray[np.float64], y: npt.NDArray[np.float64],
z: npt.NDArray[np.float64], vx: npt.NDArray[np.float64],
vy: npt.NDArray[np.float64], vz: npt.NDArray[np.float64],
@@ -343,6 +403,7 @@ def nd_advance(dt: float, steps: int, x: npt.NDArray[np.float64], y: npt.NDArray
z[i] += dt * vz[i]
+ @compiled
def nd_energy_full(x: npt.NDArray[np.float64], y: npt.NDArray[np.float64],
z: npt.NDArray[np.float64], vx: npt.NDArray[np.float64],
vy: npt.NDArray[np.float64], vz: npt.NDArray[np.float64],
@@ -357,10 +418,12 @@ def nd_energy_full(x: npt.NDArray[np.float64], y: npt.NDArray[np.float64],
e -= mass[i] * mass[j] / math.sqrt(dx * dx + dy * dy + dz * dz)
return e
# #261: np.bool_ arrays (1-byte items, format "?")
+ @compiled
def nd_b_get(x: npt.NDArray[np.bool_], i: int) -> object:
return x[i]
+ @compiled
def nd_b_any(x: npt.NDArray[np.bool_]) -> bool:
for i in range(len(x)):
if x[i]:
@@ -368,6 +431,7 @@ def nd_b_any(x: npt.NDArray[np.bool_]) -> bool:
return False
+ @compiled
def nd_b_count(x: npt.NDArray[np.bool_]) -> int:
c = 0
for i in range(len(x)):
@@ -376,45 +440,55 @@ def nd_b_count(x: npt.NDArray[np.bool_]) -> int:
return c
+ @compiled
def nd_b_set(x: npt.NDArray[np.bool_], i: int, v: bool) -> None:
x[i] = v
+ @compiled
def nd_b_flip(x: npt.NDArray[np.bool_]) -> None:
for i in range(len(x)):
x[i] = not x[i]
+ @compiled
def nd_b_copy(a: npt.NDArray[np.bool_], b: npt.NDArray[np.bool_]) -> None:
for i in range(len(a)):
b[i] = a[i]
+ @compiled
def nd_b_eq(x: npt.NDArray[np.bool_], y: npt.NDArray[np.bool_], i: int) -> object:
return x[i] == y[i]
+ @compiled
def nd_b_gt(x: npt.NDArray[np.float64], m: npt.NDArray[np.bool_]) -> None:
for i in range(len(x)):
m[i] = x[i] > 2.0
+ @compiled
def nd_b_len(x: npt.NDArray[np.bool_]) -> int:
return len(x)
+ @compiled
def nd_b_store_int(x: npt.NDArray[np.bool_], i: int, v: int) -> None:
x[i] = v
+ @compiled
def nd_b_store_float(x: npt.NDArray[np.bool_], i: int, v: float) -> None:
x[i] = v
+ @compiled
def nd_b_arith(x: npt.NDArray[np.bool_], i: int) -> object:
return x[i] + 1
+ @compiled
def nd_b_whole(x: npt.NDArray[np.bool_]) -> object:
return np.sum(x)
''')
diff --git a/python-multiplatform-ksp/src/test/python/test_class_methods.py b/python-multiplatform-ksp/src/test/python/test_class_methods.py
index d6693dcad..cd08794e3 100644
--- a/python-multiplatform-ksp/src/test/python/test_class_methods.py
+++ b/python-multiplatform-ksp/src/test/python/test_class_methods.py
@@ -40,6 +40,7 @@
import pytest
+from conftest import decorate_compiled
from typedpython import cbuild, cgen, cost, frontend, pipeline, verify
HERE = Path(__file__).resolve().parent
@@ -51,10 +52,10 @@
BINARY_TREES = REPO / "benchmarks" / "typedpython" / "py" / "binary_trees.py"
SOURCE = textwrap.dedent('''\
- # typedpython: compiled
"""typedpython class-method test module."""
+ @compiled
class Node:
__slots__ = ("left", "right")
left: "Node | None"
@@ -135,21 +136,25 @@ def no_annotation(self, x) -> int:
return 1
+ @compiled
class Plain:
def check(self) -> int:
return 5
+ @compiled
def make(depth: int) -> Node:
if depth > 0:
return Node(make(depth - 1), make(depth - 1))
return Node(None, None)
+ @compiled
def check_of(n: Node) -> int:
return n.check()
+ @compiled
def leaf_of(n: Node) -> bool:
return n.leaf()
''')
@@ -231,6 +236,7 @@ def test_the_methods_of_a_class_that_is_not_fixed_layout_are_not_considered(tmp_
def test_a_method_named_like_a_module_function_does_not_collide(tmp_path):
m = lower(tmp_path, SOURCE + textwrap.dedent('''\
+ @compiled
def height(n: Node) -> int:
return n.height()
'''))
@@ -240,7 +246,7 @@ def height(n: Node) -> int:
def test_a_method_with_a_foreign_self_annotation_stays_interpreted(tmp_path):
src = textwrap.dedent('''\
- # typedpython: compiled
+ @compiled
class A:
__slots__ = ("x",)
x: int
@@ -258,7 +264,7 @@ def get(self: int) -> int:
def test_a_method_defined_twice_in_the_class_body_stays_interpreted(tmp_path):
src = textwrap.dedent('''\
- # typedpython: compiled
+ @compiled
class A:
__slots__ = ("x",)
x: int
@@ -277,7 +283,7 @@ def get(self) -> int:
assert "more than once" in m.skipped["A.get"]
-def test_an_at_compiled_class_compiles_its_methods_without_the_module_marker(tmp_path):
+def test_an_at_compiled_class_compiles_its_methods(tmp_path):
src = textwrap.dedent('''\
@compiled
class A:
@@ -887,7 +893,7 @@ def run_main(path_or_module, n):
def test_binary_trees_with_check_as_a_method_compiles_and_matches(tmp_path):
- text = "# typedpython: compiled\n" + method_form(BINARY_TREES.read_text())
+ text = decorate_compiled(method_form(BINARY_TREES.read_text()))
path = tmp_path / "bt_method.py"
path.write_text(text)
result = pipeline.compile_module(path, tmp_path / "out")
@@ -921,8 +927,8 @@ def test_binary_trees_with_check_as_a_method_compiles_and_matches(tmp_path):
@pytest.mark.parametrize("name", sorted(EXPECTED))
def test_the_benchmarks_compile_the_same_functions_as_before(tmp_path, name):
path = tmp_path / f"{name}.py"
- path.write_text("# typedpython: compiled\n" + (REPO / "benchmarks" / "typedpython" / "py"
- / f"{name}.py").read_text())
+ path.write_text(decorate_compiled((REPO / "benchmarks" / "typedpython" / "py"
+ / f"{name}.py").read_text()))
proved, diags = verify.verify(frontend.lower(path))
proved, diags = cost.apply(proved, diags)
assert {f.name for f in proved.functions} == EXPECTED[name]
diff --git a/python-multiplatform-ksp/src/test/python/test_cost_rule.py b/python-multiplatform-ksp/src/test/python/test_cost_rule.py
index db6c4999a..c540bb260 100644
--- a/python-multiplatform-ksp/src/test/python/test_cost_rule.py
+++ b/python-multiplatform-ksp/src/test/python/test_cost_rule.py
@@ -12,20 +12,19 @@
import pytest
+from conftest import decorate_compiled
from typedpython import cost, pipeline
-HEADER = "# typedpython: compiled\n"
-
-OBJECT_ONLY = HEADER + textwrap.dedent('''
+OBJECT_ONLY = decorate_compiled(textwrap.dedent('''
def copy_words(src: list[str], dst: list[str]) -> str:
w: str = ""
for x in src:
w = x + w
dst.append(w)
return w
-''')
+'''))
-INT_LOOP = HEADER + textwrap.dedent('''
+INT_LOOP = decorate_compiled(textwrap.dedent('''
def word_of(word_id: int) -> str:
x: int = word_id + 17576
w: str = ""
@@ -33,7 +32,7 @@ def word_of(word_id: int) -> str:
w = chr(97 + x % 26) + w
x //= 26
return w
-''')
+'''))
def build(tmp_path, source, **kw):
@@ -67,7 +66,7 @@ def test_the_opt_out_compiles_the_same_function(tmp_path):
assert names(r) == {"copy_words"} and r.extension is not None
-MIXED = HEADER + textwrap.dedent('''
+MIXED = decorate_compiled(textwrap.dedent('''
def mixed(n: int, xs: list[str]) -> float:
total: float = 0.0
for i in range(n):
@@ -106,7 +105,7 @@ def native_loop_inside_object_loop(rows: list[list[str]]) -> float:
for k in range(3):
total += 0.5 * 2.0
return total
-''')
+'''))
def test_one_native_loop_with_object_operations_compiles(tmp_path):
@@ -148,10 +147,10 @@ def test_the_rule_is_what_keeps_it_interpreted(tmp_path, monkeypatch):
test_an_object_only_loop_stays_interpreted_with_the_reason(tmp_path)
-STRAIGHT_LINE = HEADER + textwrap.dedent('''
+STRAIGHT_LINE = decorate_compiled(textwrap.dedent('''
def label(n: int, name: str) -> str:
return f"{name}:{n + 1}"
-''')
+'''))
def test_a_straight_line_impure_function_still_compiles(tmp_path):
diff --git a/python-multiplatform-ksp/src/test/python/test_eval_breaker.py b/python-multiplatform-ksp/src/test/python/test_eval_breaker.py
index 51f1bca43..0d6bb643b 100644
--- a/python-multiplatform-ksp/src/test/python/test_eval_breaker.py
+++ b/python-multiplatform-ksp/src/test/python/test_eval_breaker.py
@@ -23,9 +23,9 @@
posix_signals = pytest.mark.skipif(sys.platform == "win32", reason="needs POSIX signals (SIGALRM/SIGUSR1/setitimer) or fork; Windows has none")
SOURCE = """\
-# typedpython: compiled
+@compiled
def spin(n: int) -> float:
acc: float = 0.0
for k in range(n):
@@ -33,12 +33,14 @@ def spin(n: int) -> float:
return acc
+@compiled
def fib(n: int) -> int:
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
+@compiled
def total(xs: list[float], rounds: int) -> float:
s: float = 0.0
for r in range(rounds):
@@ -165,9 +167,9 @@ def handler(signum, frame):
# ---- chunked polling for range loops (#206, SPEC N-8) ---------------------------------------------
CHUNK_SOURCE = """\
-# typedpython: compiled
+@compiled
def brk(n: int, k: int) -> int:
s: int = 0
last: int = -1
@@ -179,6 +181,7 @@ def brk(n: int, k: int) -> int:
return s * 3 + last
+@compiled
def cont(n: int, m: int) -> int:
s: int = 0
for i in range(n):
@@ -188,6 +191,7 @@ def cont(n: int, m: int) -> int:
return s
+@compiled
def ret(n: int, k: int) -> int:
for i in range(n):
if i == k:
@@ -195,6 +199,7 @@ def ret(n: int, k: int) -> int:
return -1
+@compiled
def down(n: int) -> int:
s: int = 0
for i in range(n, 0, -3):
@@ -202,6 +207,7 @@ def down(n: int) -> int:
return s
+@compiled
def down1(n: int) -> int:
s: int = 0
for i in range(n, -1, -1):
@@ -209,6 +215,7 @@ def down1(n: int) -> int:
return s
+@compiled
def up3(a: int, b: int) -> int:
s: int = 0
for i in range(a, b, 3):
@@ -216,6 +223,7 @@ def up3(a: int, b: int) -> int:
return s
+@compiled
def nested(n: int, m: int) -> int:
s: int = 0
for i in range(n):
@@ -226,6 +234,7 @@ def nested(n: int, m: int) -> int:
return s
+@compiled
def brk_in_while(n: int) -> int:
s: int = 0
for i in range(n):
@@ -238,6 +247,7 @@ def brk_in_while(n: int) -> int:
return s
+@compiled
def huge(a: int, b: int, cap: int) -> int:
c: int = 0
for i in range(a, b):
@@ -247,6 +257,7 @@ def huge(a: int, b: int, cap: int) -> int:
return c
+@compiled
def huge_last(a: int, b: int, cap: int) -> int:
c: int = 0
last: int = 0
@@ -346,7 +357,7 @@ def test_range_loop_polls_once_per_chunk(chunk_module):
def test_while_loop_keeps_its_per_iteration_countdown(tmp_path):
path = tmp_path / "w.py"
- path.write_text("# typedpython: compiled\n\n\ndef w(n: int) -> int:\n i: int = 0\n while i < n:\n"
+ path.write_text("\n\n@compiled\ndef w(n: int) -> int:\n i: int = 0\n while i < n:\n"
" i += 1\n return i\n")
body = _function_c(_generated_c(path), "w")
lines = body.splitlines()
@@ -379,9 +390,9 @@ def test_keyboard_interrupt_reaches_a_long_range_loop_in_chunks(extension):
MID_SOURCE = """\
-# typedpython: compiled
+@compiled
def mid(n: int) -> float:
x: float = 1.0
for k in range(n):
diff --git a/python-multiplatform-ksp/src/test/python/test_fast_entry.py b/python-multiplatform-ksp/src/test/python/test_fast_entry.py
index f1e43a1ac..d448e3b41 100644
--- a/python-multiplatform-ksp/src/test/python/test_fast_entry.py
+++ b/python-multiplatform-ksp/src/test/python/test_fast_entry.py
@@ -25,45 +25,53 @@
posix_signals = pytest.mark.skipif(sys.platform == "win32", reason="needs POSIX signals (SIGALRM/SIGUSR1/setitimer) or fork; Windows has none")
SOURCE = """\
-# typedpython: compiled
+@compiled
def helper(x: int) -> float:
d: dict[str, int] = {}
d['a'] = x
return d['a'] * 2.5
+@compiled
def leaf(i: int) -> float:
return 1.0 / (i + 1)
+@compiled
def mid(i: int) -> float:
return leaf(i) + leaf(i + 1)
+@compiled
def top(i: int) -> float:
return mid(i) * 2.0
+@compiled
def objy(i: int) -> float:
return float(helper(i)) + leaf(i)
+@compiled
def viaobj(i: int) -> float:
return objy(i) + 1.0
+@compiled
def rec(n: int) -> int:
if n == 0:
return 0
return 1 + rec(n - 1)
+@compiled
def callrec(n: int) -> int:
return rec(n) + 1
+@compiled
def loopy(n: int) -> float:
s: float = 0.0
for i in range(n):
@@ -71,14 +79,17 @@ def loopy(n: int) -> float:
return s
+@compiled
def outer(n: int) -> float:
return loopy(n) + 1.0
+@compiled
def cube(x: int) -> float:
return float(x * x * x) + leaf(x)
+@compiled
def fill(x: int, out: list[float]) -> None:
out[0] = cube(x)
"""
diff --git a/python-multiplatform-ksp/src/test/python/test_free_threaded.py b/python-multiplatform-ksp/src/test/python/test_free_threaded.py
index 378395e78..e954c03ba 100644
--- a/python-multiplatform-ksp/src/test/python/test_free_threaded.py
+++ b/python-multiplatform-ksp/src/test/python/test_free_threaded.py
@@ -33,9 +33,9 @@
FREE_THREADED = bool(sysconfig.get_config_var("Py_GIL_DISABLED"))
SOURCE = '''\
-# typedpython: compiled
+@compiled
class Cell:
__slots__ = ("a", "b")
a: object
@@ -46,6 +46,7 @@ def __init__(self, a: object, b: object) -> None:
self.b = b
+@compiled
class Twin:
__slots__ = ("a", "b")
a: object
@@ -56,18 +57,22 @@ def __init__(self, a: object, b: object) -> None:
self.b = b
+@compiled
def geta(c: Cell) -> object:
return c.a
+@compiled
def seta(c: Cell, v: object) -> None:
c.a = v
+@compiled
def mk(a: object, b: object) -> Cell:
return Cell(a, b)
+@compiled
def fresh(a: object, n: int) -> object:
c = Cell(a, None)
keep: object = c # read as a value: c is a real object, not virtual (N-12a)
@@ -77,11 +82,13 @@ def fresh(a: object, n: int) -> object:
return s
+@compiled
def vpick(a: object, b: object) -> object:
c = Cell(a, b)
return c.b
+@compiled
def total(xs: list[float], rounds: int) -> float:
s: float = 0.0
for r in range(rounds):
@@ -90,24 +97,29 @@ def total(xs: list[float], rounds: int) -> float:
return s
+@compiled
def fill(xs: list[float], v: float) -> None:
for i in range(len(xs)):
xs[i] = v
+@compiled
def edge(xs: list[float]) -> float:
return xs[0] - xs[len(xs) - 1]
+@compiled
def fill_int(xs: list[int], v: int) -> None:
for i in range(len(xs)):
xs[i] = v
+@compiled
def edge_int(xs: list[int]) -> int:
return xs[0] - xs[len(xs) - 1]
+@compiled
def rotate(xs: list[float]) -> None:
if len(xs) > 1:
first: float = xs[0]
@@ -116,6 +128,7 @@ def rotate(xs: list[float]) -> None:
xs[len(xs) - 1] = first
+@compiled
def zero2(xs: list[int], ys: list[int]) -> int:
for i in range(len(xs)):
xs[i] = 0
@@ -124,10 +137,12 @@ def zero2(xs: list[int], ys: list[int]) -> int:
return 0
+@compiled
def pair_edge(xs: list[int], ys: list[int]) -> int:
return xs[0] - ys[len(ys) - 1]
+@compiled
def three(a: list[int], b: list[int], c: list[int]) -> int:
return len(a) + len(b) + len(c)
@@ -135,6 +150,7 @@ def three(a: list[int], b: list[int], c: list[int]) -> int:
K: int = 3
+@compiled
def usek(x: int) -> int:
return x + K
'''
@@ -244,7 +260,7 @@ def test_three_list_parameters_stay_interpreted(built):
def test_front_end_gives_a_clear_reason(tmp_path):
p = tmp_path / "m.py"
- p.write_text("# typedpython: compiled\n\n\ndef f(a: list[int], b: list[float], c: list[int]) -> int:\n"
+ p.write_text("\n\n@compiled\ndef f(a: list[int], b: list[float], c: list[int]) -> int:\n"
" return len(a)\n")
m = frontend.lower(p)
assert not m.functions
diff --git a/python-multiplatform-ksp/src/test/python/test_frontend.py b/python-multiplatform-ksp/src/test/python/test_frontend.py
index 8b2a55fd9..fe8f4d94c 100644
--- a/python-multiplatform-ksp/src/test/python/test_frontend.py
+++ b/python-multiplatform-ksp/src/test/python/test_frontend.py
@@ -12,6 +12,7 @@
import pytest
+from conftest import decorate_compiled
from typedpython import frontend
from typedpython.ir import (
And, ArrayParam, Assign, BinOp, BinOpKind, Box, Break, Call, CallObject, Compare, CompareKind,
@@ -54,7 +55,7 @@ def nodes(root, cls):
def test_scalar_loop_lowers_to_for_range(tmp_path):
m = lower(tmp_path, """
- # typedpython: compiled
+ @compiled
def total(n: int, x: float) -> float:
acc: float = 0.0
for k in range(n):
@@ -633,16 +634,18 @@ def f(x: float) -> object:
assert Global(OBJ, "K") in nodes(f, Global)
-def test_module_marker_compiles_every_module_level_def(tmp_path):
+def test_compiled_marks_every_decorated_module_level_def(tmp_path):
m = lower(tmp_path, """
- # typedpython: compiled
+ @compiled
def a(x: float) -> float:
return x
+ @compiled
def b(x: float) -> float:
return -x
+ @compiled
@staticmethod
def c(x: float) -> float:
return x
@@ -651,7 +654,19 @@ def c(x: float) -> float:
assert set(m.skipped) == {"c"} and "decorator" in m.skipped["c"]
-def test_without_marker_only_compiled_functions_are_lowered(tmp_path):
+def test_the_former_module_pragma_comment_compiles_nothing(tmp_path):
+ m = lower(tmp_path, """
+ # typedpython: compiled
+ def a(x: float) -> float:
+ return x
+
+ def b(x: float) -> float:
+ return -x
+ """)
+ assert m.functions == () and m.skipped == {}
+
+
+def test_without_a_decorator_only_compiled_functions_are_lowered(tmp_path):
m = lower(tmp_path, """
x: int = 1
# typedpython: compiled
@@ -828,7 +843,7 @@ def f(n: int) -> int:
def test_nbody_kernels_lower(tmp_path):
path = tmp_path / "nbody.py"
shutil.copy(BENCH / "nbody.py", path)
- path.write_text("# typedpython: compiled\n" + path.read_text())
+ path.write_text(decorate_compiled(path.read_text()))
m = frontend.lower(path)
for name in ("advance", "energy", "offset_momentum"):
function(m, name)
@@ -847,7 +862,7 @@ def test_nbody_kernels_lower(tmp_path):
@pytest.mark.skipif(not BENCH.exists(), reason="benchmark worktree not present")
def test_spectral_norm_kernels_lower(tmp_path):
path = tmp_path / "spectral_norm.py"
- path.write_text("# typedpython: compiled\n" + (BENCH / "spectral_norm.py").read_text())
+ path.write_text(decorate_compiled((BENCH / "spectral_norm.py").read_text()))
m = frontend.lower(path)
eval_a = function(m, "eval_a")
assert eval_a.pure and eval_a.may_deopt
@@ -1048,7 +1063,7 @@ def f(a: int, b: int) -> {annotation}:
@pytest.mark.skipif(not BENCH.exists(), reason="benchmark worktree not present")
def test_fannkuch_lowers(tmp_path):
path = tmp_path / "fannkuch.py"
- path.write_text("# typedpython: compiled\n" + (BENCH / "fannkuch.py").read_text())
+ path.write_text(decorate_compiled((BENCH / "fannkuch.py").read_text()))
m = frontend.lower(path)
assert "fannkuch" in [f.name for f in m.functions], m.skipped.get("fannkuch")
f = function(m, "fannkuch")
diff --git a/python-multiplatform-ksp/src/test/python/test_frontend_classes.py b/python-multiplatform-ksp/src/test/python/test_frontend_classes.py
index 33c0473cc..a2daaeed9 100644
--- a/python-multiplatform-ksp/src/test/python/test_frontend_classes.py
+++ b/python-multiplatform-ksp/src/test/python/test_frontend_classes.py
@@ -18,6 +18,7 @@
import pytest
+from conftest import decorate_compiled
from typedpython import frontend
from typedpython.ir import (
And, Assign, Box, Call, CallObject, CheckExact, ClassDecl, Const, ExprStmt, FieldGet, FieldSet,
@@ -28,8 +29,6 @@
BINARY_TREES = Path(__file__).resolve().parents[4] / "benchmarks/typedpython/py/binary_trees.py"
SLOTS_LINE = ' __slots__ = ("left", "right")\n'
-MARK = "# typedpython: compiled\n"
-
POINT = """
class P:
__slots__ = ("x", "y")
@@ -44,7 +43,7 @@ def __init__(self, x: float, y: float) -> None:
def lower(tmp_path, source, name="mod.py"):
path = tmp_path / name
- path.write_text(MARK + textwrap.dedent(source).lstrip("\n"))
+ path.write_text(decorate_compiled(textwrap.dedent(source).lstrip("\n")))
return frontend.lower(path)
@@ -573,7 +572,7 @@ def test_binary_trees_with_slots_lowers_make_and_check(tmp_path):
source = BINARY_TREES.read_text()
assert "class Node:\n" + SLOTS_LINE in source # the benchmark itself declares the slots
path = tmp_path / "binary_trees.py"
- path.write_text(MARK + source)
+ path.write_text(decorate_compiled(source))
m = frontend.lower(path)
assert cls(m, "Node") == ClassDecl("Node", ("left", "right"), True)
@@ -598,7 +597,7 @@ def test_binary_trees_without_slots_stays_object_code(tmp_path):
path = tmp_path / "binary_trees.py"
source = BINARY_TREES.read_text()
assert SLOTS_LINE in source
- path.write_text(MARK + source.replace(SLOTS_LINE, "", 1)) # the run.py no-slots baseline
+ path.write_text(decorate_compiled(source.replace(SLOTS_LINE, "", 1))) # the run.py no-slots baseline
m = frontend.lower(path)
assert m.classes == ()
assert "`__slots__`" in m.skipped["Node"]
diff --git a/python-multiplatform-ksp/src/test/python/test_interval_proofs.py b/python-multiplatform-ksp/src/test/python/test_interval_proofs.py
index 045a593e3..64260ddf7 100644
--- a/python-multiplatform-ksp/src/test/python/test_interval_proofs.py
+++ b/python-multiplatform-ksp/src/test/python/test_interval_proofs.py
@@ -330,39 +330,44 @@ def test_a_mod_result_can_feed_a_proof():
# --- 2. end to end ---------------------------------------------------------------------------------
SOURCE = '''\
-# typedpython: compiled
+@compiled
def dec_if_positive(n: int) -> int:
if n > 0:
return n - 1
return -1
+@compiled
def dec_after_guard(n: int) -> int:
if n <= 0:
return 0
return n - 1
+@compiled
def inc_below_max(n: int) -> int:
if n < 9223372036854775807:
return n + 1
return 0
+@compiled
def inc_below_100(n: int) -> int:
if n < 100:
return n + 1
return 0
+@compiled
def inc_if_positive(n: int) -> int:
if n > 0:
return n + 1
return 0
+@compiled
def reverse_gap(a: int, b: int) -> int:
lo: int = a
hi: int = b
@@ -374,6 +379,7 @@ def reverse_gap(a: int, b: int) -> int:
return hi - lo + steps
+@compiled
def killed_guard(n: int, a: list[int]) -> int:
if n < 100:
n = a[0]
@@ -381,6 +387,7 @@ def killed_guard(n: int, a: list[int]) -> int:
return 0
+@compiled
def killed_in_loop(c: int, a: list[int]) -> int:
n: int = a[0]
t: int = 0
@@ -391,6 +398,7 @@ def killed_in_loop(c: int, a: list[int]) -> int:
return t
+@compiled
def parity(n: int) -> int:
k: int = 0
s: int = 0
@@ -401,11 +409,13 @@ def parity(n: int) -> int:
return s
+@compiled
def const_local() -> int:
x: int = 41
return x + 1
+@compiled
def range_shifts(a: int, b: int) -> int:
if a < 0:
return 0
@@ -415,26 +425,31 @@ def range_shifts(a: int, b: int) -> int:
return t
+@compiled
def guarded_shift(n: int) -> int:
if n >= 0:
return n // 16 + n % 16
return 0
+@compiled
def negative_shift(n: int) -> int:
return n // 4 + n % 8
+@compiled
def odd_divisor(n: int) -> int:
if n >= 0:
return n // 3 + n % 6
return 0
+@compiled
def mod_then_add(n: int) -> int:
return n % 10 + 1
+@compiled
def pair_sums(a: list[int]) -> int:
best: int = 0
for i in range(len(a)):
diff --git a/python-multiplatform-ksp/src/test/python/test_module_report.py b/python-multiplatform-ksp/src/test/python/test_module_report.py
index 8625cf18d..86ead4b97 100644
--- a/python-multiplatform-ksp/src/test/python/test_module_report.py
+++ b/python-multiplatform-ksp/src/test/python/test_module_report.py
@@ -8,16 +8,17 @@
import textwrap
from pathlib import Path
+from conftest import decorate_compiled
from typedpython import cbuild, cgen, frontend, pipeline, verify
-SRC = "# typedpython: compiled\n" + textwrap.dedent("""\
+SRC = decorate_compiled(textwrap.dedent("""\
def twice(n: int) -> int:
return n * 2
def boom(s: str) -> str:
raise ValueError("boom " + s)
- """)
+ """))
def make_project(root: Path) -> Path:
@@ -95,14 +96,14 @@ def test_a_moved_file_with_the_same_content_does_not_reuse_the_extension(tmp_pat
# --- #128 ----------------------------------------------------------------------------------------
-MIXED = "# typedpython: compiled\n" + textwrap.dedent("""\
+MIXED = decorate_compiled(textwrap.dedent("""\
def ok(n: int) -> int:
return n + 1
def greet(name: str) -> str:
return name[1:]
- """)
+ """))
def build_one(tmp_path, rel, source, cache="cache"):
@@ -119,20 +120,20 @@ def test_kind_compiled(tmp_path):
assert m.kind == "compiled" and m.extension is not None
[item] = m.skipped_items
assert item.name == "greet" and item.file == "pkg/mixed.py"
- assert item.line == 7 and not item.message.startswith("line ")
- assert m.skipped["greet"].startswith("line 7: ") # ModuleResult.skipped unchanged
+ assert item.line == 8 and not item.message.startswith("line ")
+ assert m.skipped["greet"].startswith("line 8: ") # ModuleResult.skipped unchanged
def test_kind_nothing_compiled_with_structured_items(tmp_path):
_, _, m = build_one(tmp_path, "pkg/plain.py",
- "# typedpython: compiled\n\ndef f(s: str) -> str:\n return s[1:]\n")
+ "@compiled\ndef f(s: str) -> str:\n return s[1:]\n")
assert m.kind == "nothing_compiled" and m.extension is None
[item] = m.skipped_items
- assert (item.name, item.file, item.line) == ("f", "pkg/plain.py", 4)
+ assert (item.name, item.file, item.line) == ("f", "pkg/plain.py", 3)
assert item.message and not re.match(r"line \d+", item.message)
-def test_kind_not_marked_when_there_is_neither_marker_nor_decorator(tmp_path):
+def test_kind_not_marked_when_there_is_no_decorator(tmp_path):
_, _, m = build_one(tmp_path, "pkg/unmarked.py", "def f(x: int) -> int:\n return x\n")
assert m.kind == "not_marked" and m.extension is None and m.skipped_items == []
@@ -145,15 +146,15 @@ def test_a_compiled_decorator_alone_opts_in(tmp_path):
def test_class_items_carry_name_and_line(tmp_path):
- src = ("# typedpython: compiled\n\n\nclass Node:\n __slots__ = ('a',)\n"
+ src = ("@compiled\nclass Node:\n __slots__ = ('a',)\n"
" def __init__(self, a: int) -> None:\n self.a = a\n")
_, _, m = build_one(tmp_path, "pkg/cls.py", src)
cls = [i for i in m.skipped_items if i.name == "Node"]
- assert cls and cls[0].line == 4 and cls[0].file == "pkg/cls.py"
+ assert cls and cls[0].line == 2 and cls[0].file == "pkg/cls.py"
def test_verifier_skips_carry_a_line_and_no_prefix(tmp_path):
- src = "# typedpython: compiled\n\n\ndef f(x: int) -> int:\n return g(x)\n\n\ndef g(x: int) -> int:\n return x\n"
+ src = "@compiled\ndef f(x: int) -> int:\n return g(x)\n\n\n@compiled\ndef g(x: int) -> int:\n return x\n"
_, _, m = build_one(tmp_path, "pkg/v.py", src)
for i in m.skipped_items:
assert isinstance(i.line, int) and not i.message.startswith("line ")
@@ -170,7 +171,7 @@ def test_structure_round_trips_through_the_cache_for_reused_modules(tmp_path):
def test_not_marked_and_nothing_compiled_survive_reuse(tmp_path):
root, path, _ = build_one(tmp_path, "pkg/plain.py",
- "# typedpython: compiled\n\ndef f(s: str) -> str:\n return s[1:]\n")
+ "@compiled\ndef f(s: str) -> str:\n return s[1:]\n")
again = pipeline.compile_project([path], root, tmp_path / "cache").modules[0]
assert again.status == "reused" and again.kind == "nothing_compiled" and again.skipped_items
root2, path2, _ = build_one(tmp_path, "pkg/un.py", "def f(x: int) -> int:\n return x\n")
@@ -191,5 +192,5 @@ def test_cli_build_prints_file_line_name_message_and_not_marked(tmp_path):
[sys.executable, "-m", "typedpython", "build", str(root), "--cache", str(tmp_path / "c")],
capture_output=True, text=True, env=dict(os.environ, PYTHONPATH=str(src)))
assert out.returncode == 0, out.stdout + out.stderr
- assert re.search(r"^\s*pkg/mixed\.py:7: greet: \S", out.stdout, re.M), out.stdout
+ assert re.search(r"^\s*pkg/mixed\.py:8: greet: \S", out.stdout, re.M), out.stdout
assert re.search(r"pkg/un\.py.*not marked for compilation", out.stdout), out.stdout
diff --git a/python-multiplatform-ksp/src/test/python/test_native_int_impure.py b/python-multiplatform-ksp/src/test/python/test_native_int_impure.py
index f2ceeb875..2da246f90 100644
--- a/python-multiplatform-ksp/src/test/python/test_native_int_impure.py
+++ b/python-multiplatform-ksp/src/test/python/test_native_int_impure.py
@@ -28,13 +28,13 @@
import pytest
+from conftest import decorate_compiled
from typedpython import cbuild, cgen, cost, frontend, ir, verify
REPO = Path(__file__).resolve().parents[4]
WORDFREQ = REPO / "benchmarks" / "typedpython" / "py" / "wordfreq.py"
SOURCE = textwrap.dedent('''
- # typedpython: compiled
import math as GMOD
@@ -42,14 +42,17 @@
MOD: int = 2147483647
+ @compiled
def _one() -> int:
return 1
+ @compiled
def _two() -> int:
return 2
+ @compiled
class GTYPE:
pass
@@ -57,6 +60,7 @@ class GTYPE:
GFN = _one
+ @compiled
def add2(log: list[object], a: int, b: int) -> object:
log.append("start")
r: int = a + b
@@ -66,6 +70,7 @@ def add2(log: list[object], a: int, b: int) -> object:
return r
+ @compiled
def sub2(log: list[object], a: int, b: int) -> object:
log.append("start")
r: int = a - b
@@ -75,6 +80,7 @@ def sub2(log: list[object], a: int, b: int) -> object:
return r
+ @compiled
def mul2(log: list[object], a: int, b: int) -> object:
log.append("start")
r: int = a * b
@@ -84,6 +90,7 @@ def mul2(log: list[object], a: int, b: int) -> object:
return r
+ @compiled
def neg2(log: list[object], a: int) -> object:
log.append("start")
r: int = -a
@@ -93,6 +100,7 @@ def neg2(log: list[object], a: int) -> object:
return r
+ @compiled
def floordiv2(log: list[object], a: int, b: int) -> object:
log.append("start")
r: int = a + 1
@@ -104,6 +112,7 @@ def floordiv2(log: list[object], a: int, b: int) -> object:
return r
+ @compiled
def mod2(log: list[object], a: int, b: int) -> object:
log.append("start")
r: int = a - 1
@@ -115,6 +124,7 @@ def mod2(log: list[object], a: int, b: int) -> object:
return r
+ @compiled
def lsh(log: list[object], a: int, b: int) -> object:
log.append("start")
r: int = a << b
@@ -122,6 +132,7 @@ def lsh(log: list[object], a: int, b: int) -> object:
return r
+ @compiled
def rsh(log: list[object], a: int, b: int) -> object:
log.append("start")
r: int = a + 1
@@ -131,6 +142,7 @@ def rsh(log: list[object], a: int, b: int) -> object:
return r
+ @compiled
def roundtrip(log: list[object], a: int) -> object:
log.append(a)
x: int = a
@@ -145,6 +157,7 @@ def roundtrip(log: list[object], a: int) -> object:
return x
+ @compiled
def compare(log: list[object], a: int, b: int) -> None:
log.append("start")
x: int = a + 1
@@ -167,6 +180,7 @@ def compare(log: list[object], a: int, b: int) -> None:
log.append("y-zero")
+ @compiled
def fact(log: list[object], n: int) -> object:
log.append(n)
f: int = 1
@@ -176,6 +190,7 @@ def fact(log: list[object], n: int) -> object:
return f
+ @compiled
def grow(log: list[object], a: int, k: int) -> object:
x: int = a
i: int = 0
@@ -186,16 +201,19 @@ def grow(log: list[object], a: int, k: int) -> object:
return x
+ @compiled
def consts(log: list[object]) -> None:
log.append(7000000000)
log.append(5)
log.append(-7000000000)
+ @compiled
def next_state(s: int) -> int:
return (s * 48271) % MOD
+ @compiled
def drive(log: list[object], seed: int, n: int) -> object:
s: int = seed + 1
for _ in range(n):
@@ -204,37 +222,44 @@ def drive(log: list[object], seed: int, n: int) -> object:
return s
+ @compiled
def read_limit(log: list[object]) -> object:
log.append(1)
return LIMIT
+ @compiled
def read_twice(log: list[object], cb: object) -> None:
log.append(LIMIT)
cb()
log.append(LIMIT)
+ @compiled
def use_builtin(log: list[object], s: str) -> object:
log.append(1)
return len(s)
+ @compiled
def read_fn(log: list[object]) -> object:
log.append(1)
return GFN
+ @compiled
def read_type(log: list[object]) -> object:
log.append(1)
return GTYPE
+ @compiled
def read_mod(log: list[object]) -> object:
log.append(1)
return GMOD
+ @compiled
def call_g(x: object) -> object:
return GFN()
''')
@@ -314,7 +339,7 @@ def test_int_locals_of_impure_functions_are_native(built):
def test_an_int_fed_from_an_object_stays_an_object(tmp_path):
src = textwrap.dedent('''
- # typedpython: compiled
+ @compiled
def f(log: list[object], g: object) -> object:
log.append(1)
n: int = g()
@@ -656,7 +681,7 @@ def test_a_cache_that_is_never_invalidated_is_caught(tmp_path):
def test_an_int_loop_is_native_work_for_the_cost_rule(tmp_path):
src = textwrap.dedent('''
- # typedpython: compiled
+ @compiled
def digits(log: list[object], n: int) -> None:
x: int = n + 1
while x > 0:
@@ -672,7 +697,7 @@ def digits(log: list[object], n: int) -> None:
def test_an_object_only_loop_is_still_rejected(tmp_path):
src = textwrap.dedent('''
- # typedpython: compiled
+ @compiled
def words(xs: list[object], ys: list[object]) -> None:
for x in xs:
ys.append(x)
@@ -688,7 +713,7 @@ def words(xs: list[object], ys: list[object]) -> None:
@pytest.fixture(scope="module")
def wordfreq(tmp_path_factory):
d = tmp_path_factory.mktemp("wordfreq")
- src = "# typedpython: compiled\n" + WORDFREQ.read_text()
+ src = decorate_compiled(WORDFREQ.read_text())
path = d / "wordfreq.py"
path.write_text(src)
proved, diags = verify.verify(frontend.lower(path))
diff --git a/python-multiplatform-ksp/src/test/python/test_osr_exit.py b/python-multiplatform-ksp/src/test/python/test_osr_exit.py
index fbfbdd3cf..227d50ecd 100644
--- a/python-multiplatform-ksp/src/test/python/test_osr_exit.py
+++ b/python-multiplatform-ksp/src/test/python/test_osr_exit.py
@@ -17,11 +17,11 @@
from typedpython import cbuild, cgen, frontend, verify
SOURCE = textwrap.dedent('''
- # typedpython: compiled
SCALE = 2.0
+ @compiled
def scale_all(x: list[float]) -> float:
s = 0.0
for i in range(len(x)):
@@ -30,6 +30,7 @@ def scale_all(x: list[float]) -> float:
return s
+ @compiled
def nested(x: list[float], n: int) -> int:
hits = 0
for r in range(n):
@@ -43,6 +44,7 @@ def nested(x: list[float], n: int) -> int:
return hits
+ @compiled
def countdown(x: list[int]) -> int:
k = 0
total = 0
diff --git a/python-multiplatform-ksp/src/test/python/test_parallel_loops.py b/python-multiplatform-ksp/src/test/python/test_parallel_loops.py
index 55fc184c6..e86605b69 100644
--- a/python-multiplatform-ksp/src/test/python/test_parallel_loops.py
+++ b/python-multiplatform-ksp/src/test/python/test_parallel_loops.py
@@ -33,26 +33,29 @@
PAR_RULE = "verify/par-loop"
SOURCE = '''\
-# typedpython: compiled
# typedpython: parallel
+@compiled
def scale(xs: list[float], a: float, b: float) -> None:
for i in range(len(xs)):
xs[i] = xs[i] * a + b
+@compiled
def scale_unmarked(xs: list[float], a: float, b: float) -> None:
for i in range(len(xs)):
xs[i] = xs[i] * a + b
# typedpython: parallel
+@compiled
def ramp(xs: list[float], a: float) -> None:
for i in range(len(xs)):
xs[i] = -(float(i) * a) + xs[i]
# typedpython: parallel
+@compiled
def two_stores(xs: list[float], ys: list[float], a: float) -> None:
for i in range(len(xs)):
xs[i] = xs[i] + a
@@ -60,12 +63,14 @@ def two_stores(xs: list[float], ys: list[float], a: float) -> None:
# typedpython: parallel
+@compiled
def neighbour(xs: list[float]) -> None:
for i in range(len(xs) - 1):
xs[i] = xs[i + 1]
# typedpython: parallel
+@compiled
def total(xs: list[float]) -> float:
s: float = 0.0
for i in range(len(xs)):
@@ -73,23 +78,27 @@ def total(xs: list[float]) -> float:
return s
+@compiled
def helper(x: float) -> float:
return x * 2.0
# typedpython: parallel
+@compiled
def with_call(xs: list[float]) -> None:
for i in range(len(xs)):
xs[i] = helper(xs[i])
# typedpython: parallel
+@compiled
def divides(xs: list[float], d: float) -> None:
for i in range(len(xs)):
xs[i] = xs[i] / d
# typedpython: parallel
+@compiled
def local_fill(a: float) -> float:
t = [0.0] * 300000
for i in range(len(t)):
@@ -98,6 +107,7 @@ def local_fill(a: float) -> float:
# typedpython: parallel
+@compiled
def local_dyn(n: int, a: float) -> float:
t = [1.0] * n
for i in range(len(t)):
@@ -438,9 +448,9 @@ def sh():
# =====================================================================================================
RSOURCE = '''\
-# typedpython: compiled
# typedpython: parallel
+@compiled
def r_sum(xs: list[int]) -> int:
s: int = 0
for i in range(len(xs)):
@@ -449,6 +459,7 @@ def r_sum(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def r_sum_init(xs: list[int], s0: int) -> int:
s: int = s0
for i in range(len(xs)):
@@ -457,6 +468,7 @@ def r_sum_init(xs: list[int], s0: int) -> int:
# typedpython: parallel
+@compiled
def r_prod(xs: list[int], c: int) -> int:
s: int = 1
for i in range(len(xs)):
@@ -465,6 +477,7 @@ def r_prod(xs: list[int], c: int) -> int:
# typedpython: parallel
+@compiled
def r_sum_mul(xs: list[int], c: int) -> int:
s: int = 0
for i in range(len(xs)):
@@ -473,6 +486,7 @@ def r_sum_mul(xs: list[int], c: int) -> int:
# typedpython: parallel
+@compiled
def r_two(xs: list[int]) -> int:
a: int = 0
b: int = 7
@@ -483,6 +497,7 @@ def r_two(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def r_range_sum(xs: list[int]) -> int:
s: int = 5
for i in range(len(xs)):
@@ -491,6 +506,7 @@ def r_range_sum(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def r_local(n: int) -> int:
t = [0] * n
for i in range(len(t)):
@@ -501,6 +517,7 @@ def r_local(n: int) -> int:
return s
+@compiled
def r_unmarked(xs: list[int]) -> int:
s: int = 0
for i in range(len(xs)):
@@ -509,6 +526,7 @@ def r_unmarked(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def n_acc_read(xs: list[int]) -> int:
s: int = 0
for i in range(len(xs)):
@@ -517,6 +535,7 @@ def n_acc_read(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def n_two_ops(xs: list[int]) -> int:
s: int = 0
for i in range(len(xs)):
@@ -526,6 +545,7 @@ def n_two_ops(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def n_float(xs: list[float]) -> float:
s: float = 0.0
for i in range(len(xs)):
@@ -534,6 +554,7 @@ def n_float(xs: list[float]) -> float:
# typedpython: parallel
+@compiled
def n_copy(xs: list[int]) -> int:
s: int = 0
w: int = 0
@@ -544,6 +565,7 @@ def n_copy(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def n_mixed(xs: list[int]) -> int:
t = [0.0] * len(xs)
s: int = 0
@@ -554,6 +576,7 @@ def n_mixed(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def n_div(xs: list[int], d: int) -> int:
s: int = 0
for i in range(len(xs)):
@@ -562,6 +585,7 @@ def n_div(xs: list[int], d: int) -> int:
# typedpython: parallel
+@compiled
def n_shift_step(xs: list[int]) -> int:
s: int = 0
for i in range(0, len(xs), 2):
@@ -859,9 +883,9 @@ def test_no_reduction_region_without_a_marker(tmp_path):
# =====================================================================================================
MSOURCE = '''\
-# typedpython: compiled
# typedpython: parallel
+@compiled
def mn_i(xs: list[int]) -> int:
m = xs[0]
for i in range(len(xs)):
@@ -871,6 +895,7 @@ def mn_i(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def mx_i(xs: list[int]) -> int:
m = xs[0]
for i in range(len(xs)):
@@ -880,6 +905,7 @@ def mx_i(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def mn_f(xs: list[float]) -> float:
m = xs[0]
for i in range(len(xs)):
@@ -889,6 +915,7 @@ def mn_f(xs: list[float]) -> float:
# typedpython: parallel
+@compiled
def mx_f(xs: list[float]) -> float:
m = xs[0]
for i in range(len(xs)):
@@ -898,6 +925,7 @@ def mx_f(xs: list[float]) -> float:
# typedpython: parallel
+@compiled
def mn_f_flipped(xs: list[float], init: float) -> float:
m = init
for i in range(len(xs)):
@@ -907,6 +935,7 @@ def mn_f_flipped(xs: list[float], init: float) -> float:
# typedpython: parallel
+@compiled
def mx_f_flipped(xs: list[float], init: float) -> float:
m = init
for i in range(len(xs)):
@@ -916,6 +945,7 @@ def mx_f_flipped(xs: list[float], init: float) -> float:
# typedpython: parallel
+@compiled
def mn_i_expr(xs: list[int], c: int) -> int:
m = c
for i in range(len(xs)):
@@ -925,6 +955,7 @@ def mn_i_expr(xs: list[int], c: int) -> int:
# typedpython: parallel
+@compiled
def mn_f_expr(xs: list[float], a: float) -> float:
m = a
for i in range(len(xs)):
@@ -934,6 +965,7 @@ def mn_f_expr(xs: list[float], a: float) -> float:
# typedpython: parallel
+@compiled
def mn_and_sum(xs: list[int]) -> int:
m = xs[0]
s = 0
@@ -945,6 +977,7 @@ def mn_and_sum(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def two_ext(xs: list[float]) -> float:
lo = xs[0]
hi = xs[0]
@@ -957,6 +990,7 @@ def two_ext(xs: list[float]) -> float:
# typedpython: parallel
+@compiled
def mn_i_init(xs: list[int], init: int) -> int:
m = init
for i in range(len(xs)):
@@ -966,6 +1000,7 @@ def mn_i_init(xs: list[int], init: int) -> int:
# typedpython: parallel
+@compiled
def mn_i_big_e(xs: list[int], c: int) -> int:
m = 0
for i in range(len(xs)):
@@ -974,6 +1009,7 @@ def mn_i_big_e(xs: list[int], c: int) -> int:
return m
+@compiled
def mn_unmarked(xs: list[int]) -> int:
m = xs[0]
for i in range(len(xs)):
@@ -983,6 +1019,7 @@ def mn_unmarked(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def n_le(xs: list[float]) -> float:
m = xs[0]
for i in range(len(xs)):
@@ -992,6 +1029,7 @@ def n_le(xs: list[float]) -> float:
# typedpython: parallel
+@compiled
def n_other_value(xs: list[int]) -> int:
m = xs[0]
for i in range(len(xs)):
@@ -1001,6 +1039,7 @@ def n_other_value(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def n_else(xs: list[int]) -> int:
m = xs[0]
s = 0
@@ -1013,6 +1052,7 @@ def n_else(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def n_two_in_then(xs: list[int]) -> int:
m = xs[0]
s = 0
@@ -1024,6 +1064,7 @@ def n_two_in_then(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def n_acc_in_value(xs: list[int]) -> int:
m = xs[0]
for i in range(len(xs)):
@@ -1034,6 +1075,7 @@ def n_acc_in_value(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def n_cond_other(xs: list[int], t: int) -> int:
m = xs[0]
for i in range(len(xs)):
@@ -1043,6 +1085,7 @@ def n_cond_other(xs: list[int], t: int) -> int:
# typedpython: parallel
+@compiled
def n_mixed_types(xs: list[int], init: float) -> float:
m = init
for i in range(len(xs)):
@@ -1052,6 +1095,7 @@ def n_mixed_types(xs: list[int], init: float) -> float:
# typedpython: parallel
+@compiled
def n_acc_reads_acc(xs: list[int]) -> int:
m = xs[0]
for i in range(len(xs)):
@@ -1063,6 +1107,7 @@ def n_acc_reads_acc(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def n_div(xs: list[float], d: float) -> float:
m = xs[0]
for i in range(len(xs)):
@@ -1490,9 +1535,9 @@ def spin():
# =====================================================================================================
BSOURCE = '''\
-# typedpython: compiled
# typedpython: parallel
+@compiled
def b_and(xs: list[int]) -> int:
s: int = -1
for i in range(len(xs)):
@@ -1501,6 +1546,7 @@ def b_and(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def b_or(xs: list[int]) -> int:
s: int = 0
for i in range(len(xs)):
@@ -1509,6 +1555,7 @@ def b_or(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def b_xor(xs: list[int]) -> int:
s: int = 0
for i in range(len(xs)):
@@ -1517,6 +1564,7 @@ def b_xor(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def b_and_swapped(xs: list[int], s0: int) -> int:
s: int = s0
for i in range(len(xs)):
@@ -1525,6 +1573,7 @@ def b_and_swapped(xs: list[int], s0: int) -> int:
# typedpython: parallel
+@compiled
def b_or_swapped(xs: list[int]) -> int:
s: int = 0
for i in range(len(xs)):
@@ -1533,6 +1582,7 @@ def b_or_swapped(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def b_xor_expr(xs: list[int], c: int) -> int:
s: int = 0
for i in range(len(xs)):
@@ -1541,6 +1591,7 @@ def b_xor_expr(xs: list[int], c: int) -> int:
# typedpython: parallel
+@compiled
def b_or_not(xs: list[int]) -> int:
s: int = 0
for i in range(len(xs)):
@@ -1549,6 +1600,7 @@ def b_or_not(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def b_mix(xs: list[int]) -> int:
a: int = 0
x: int = 5
@@ -1564,6 +1616,7 @@ def b_mix(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def b_local(n: int) -> int:
t = [0] * n
for i in range(len(t)):
@@ -1574,6 +1627,7 @@ def b_local(n: int) -> int:
return s
+@compiled
def b_unmarked(xs: list[int]) -> int:
s: int = 0
for i in range(len(xs)):
@@ -1582,6 +1636,7 @@ def b_unmarked(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def nb_acc_read(xs: list[int]) -> int:
s: int = 0
for i in range(len(xs)):
@@ -1590,6 +1645,7 @@ def nb_acc_read(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def nb_two_ops(xs: list[int]) -> int:
s: int = 0
for i in range(len(xs)):
@@ -1599,6 +1655,7 @@ def nb_two_ops(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def nb_and_then_add(xs: list[int]) -> int:
s: int = 0
for i in range(len(xs)):
@@ -1608,6 +1665,7 @@ def nb_and_then_add(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def nb_shift(xs: list[int]) -> int:
s: int = 0
for i in range(len(xs)):
@@ -1616,6 +1674,7 @@ def nb_shift(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def nb_rshift(xs: list[int], k: int) -> int:
s: int = 0
for i in range(len(xs)):
@@ -1624,6 +1683,7 @@ def nb_rshift(xs: list[int], k: int) -> int:
# typedpython: parallel
+@compiled
def nb_copy(xs: list[int]) -> int:
s: int = 0
w: int = 0
@@ -1634,6 +1694,7 @@ def nb_copy(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def nb_div(xs: list[int], d: int) -> int:
s: int = 0
for i in range(len(xs)):
@@ -1794,15 +1855,16 @@ def test_bitwise_equals_interpreter_for_every_thread_count(bbuilt, threads):
# =====================================================================================================
TSOURCE = '''\
-# typedpython: compiled
# typedpython: parallel
+@compiled
def t_scale(xs: list[float], a: float, b: float) -> None:
for i in range(len(xs)):
xs[i] = xs[i] * a + b
# typedpython: parallel
+@compiled
def t_sum(xs: list[int]) -> int:
s: int = 0
for i in range(len(xs)):
@@ -1811,6 +1873,7 @@ def t_sum(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def t_prod(xs: list[int], c: int) -> int:
s: int = 1
for i in range(len(xs)):
@@ -1819,6 +1882,7 @@ def t_prod(xs: list[int], c: int) -> int:
# typedpython: parallel
+@compiled
def t_min(xs: list[int]) -> int:
m = xs[0]
for i in range(len(xs)):
@@ -1828,6 +1892,7 @@ def t_min(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def t_max(xs: list[float]) -> float:
m = xs[0]
for i in range(len(xs)):
@@ -1837,6 +1902,7 @@ def t_max(xs: list[float]) -> float:
# typedpython: parallel
+@compiled
def t_and(xs: list[int]) -> int:
s: int = -1
for i in range(len(xs)):
@@ -1845,6 +1911,7 @@ def t_and(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def t_or(xs: list[int]) -> int:
s: int = 0
for i in range(len(xs)):
@@ -1853,6 +1920,7 @@ def t_or(xs: list[int]) -> int:
# typedpython: parallel
+@compiled
def t_xor(xs: list[int]) -> int:
s: int = 0
for i in range(len(xs)):
diff --git a/python-multiplatform-ksp/src/test/python/test_pipeline.py b/python-multiplatform-ksp/src/test/python/test_pipeline.py
index 6efbeb14d..ed326eb4c 100644
--- a/python-multiplatform-ksp/src/test/python/test_pipeline.py
+++ b/python-multiplatform-ksp/src/test/python/test_pipeline.py
@@ -20,9 +20,9 @@ def interpreted(path: Path):
SCALAR = """\
-# typedpython: compiled
+@compiled
def total(n: int) -> float:
acc: float = 0.0
for k in range(n):
@@ -30,6 +30,7 @@ def total(n: int) -> float:
return acc
+@compiled
def fact(n: int) -> int:
r: int = 1
for k in range(2, n + 1):
@@ -53,6 +54,7 @@ def test_scalar_module_compiles_and_matches_cpython(write, tmp_path):
def test_unsupported_functions_stay_interpreted_with_a_reason(write, tmp_path):
path = write("mixed.py", SCALAR + """
+@compiled
def greet(name: str) -> str:
return name[1:]
""")
@@ -68,16 +70,16 @@ def test_a_module_with_nothing_to_compile_produces_no_extension(write, tmp_path)
def test_gate_errors_block_compilation(write, tmp_path):
- path = write("bad.py", "# typedpython: compiled\n\ndef f(x):\n return x\n")
+ path = write("bad.py", "@compiled\ndef f(x):\n return x\n")
with pytest.raises(pipeline.CompileError, match="implicit-any-parameter"):
pipeline.compile_module(path, tmp_path / "out")
DEMO = """\
-# typedpython: compiled
import sys
+@compiled
def total(n: int) -> float:
acc: float = 0.0
for k in range(n):
@@ -85,6 +87,7 @@ def total(n: int) -> float:
return acc
+@compiled
def main() -> None:
print(f"{total(int(sys.argv[1])):.3f}")
"""
@@ -103,15 +106,16 @@ def test_demo_reports_identical_output(write, tmp_path):
ARRAY_CALLBACK = """\
-# typedpython: compiled
from typing import Callable
+@compiled
def first_after(xs: list[float], cb: Callable[[], None]) -> float:
cb()
return xs[0]
+@compiled
def fill_then(xs: list[float], cb: Callable[[], None]) -> None:
xs[1] = 5.0
cb()
@@ -138,9 +142,9 @@ def cb2():
GC_REACHABLE = """\
-# typedpython: compiled
+@compiled
class Cell:
a: object
b: object
@@ -151,10 +155,12 @@ def __init__(self, a: object, b: object) -> None:
self.b = b
+@compiled
def seta(c: Cell, v: object) -> None:
c.a = v
+@compiled
def fresh(a: object, n: int) -> object:
c = Cell(None, None)
seta(c, a)
diff --git a/python-multiplatform-ksp/src/test/python/test_pipeline_incremental.py b/python-multiplatform-ksp/src/test/python/test_pipeline_incremental.py
index dd7cfad86..3735df9bc 100644
--- a/python-multiplatform-ksp/src/test/python/test_pipeline_incremental.py
+++ b/python-multiplatform-ksp/src/test/python/test_pipeline_incremental.py
@@ -9,25 +9,24 @@
import pytest
+from conftest import decorate_compiled
from typedpython import cbuild, frontend, pipeline
-HEADER = "# typedpython: compiled\n"
-
-B = HEADER + textwrap.dedent("""\
+B = decorate_compiled(textwrap.dedent("""\
def twice(n: int) -> int:
return n * 2
- """)
-A = HEADER + textwrap.dedent("""\
+ """))
+A = decorate_compiled(textwrap.dedent("""\
import b
def inc(n: int) -> int:
return n + 1
- """)
-C = HEADER + textwrap.dedent("""\
+ """))
+C = decorate_compiled(textwrap.dedent("""\
def half(x: float) -> float:
return x * 0.5
- """)
+ """))
def interpreted(path: Path):
@@ -133,7 +132,7 @@ def test_rebuilt_and_reused_extensions_compute_the_interpreted_results(project,
def test_module_with_nothing_compiled_has_no_extension_and_is_reused(tmp_path):
root = tmp_path / "proj"
root.mkdir()
- (root / "plain.py").write_text(HEADER + "def f(s: str) -> str:\n return s[1:]\n")
+ (root / "plain.py").write_text("@compiled\ndef f(s: str) -> str:\n return s[1:]\n")
cache = tmp_path / "cache"
first = pipeline.compile_project([root / "plain.py"], root, cache)
m = first.modules[0]
@@ -151,7 +150,7 @@ def test_interface_describes_the_verified_module_not_the_lowered_one(tmp_path, m
root = tmp_path / "proj"
root.mkdir()
- (root / "dep.py").write_text(HEADER + "def ok(n: int) -> int:\n return n + 1\n")
+ (root / "dep.py").write_text("@compiled\ndef ok(n: int) -> int:\n return n + 1\n")
real_lower = frontend.lower
seen = {}
diff --git a/python-multiplatform-ksp/src/test/python/test_recursion.py b/python-multiplatform-ksp/src/test/python/test_recursion.py
index 289e1de66..49901b0e6 100644
--- a/python-multiplatform-ksp/src/test/python/test_recursion.py
+++ b/python-multiplatform-ksp/src/test/python/test_recursion.py
@@ -20,15 +20,16 @@
PACKAGE = Path(__file__).resolve().parents[2] / "main" / "python"
SOURCE = """\
-# typedpython: compiled
+@compiled
def fib(n: int) -> int:
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
+@compiled
def ack(m: int, n: int) -> int:
if m == 0:
return n + 1
@@ -37,30 +38,35 @@ def ack(m: int, n: int) -> int:
return ack(m - 1, ack(m, n - 1))
+@compiled
def is_even(n: int) -> bool:
if n == 0:
return True
return is_odd(n - 1)
+@compiled
def is_odd(n: int) -> bool:
if n == 0:
return False
return is_even(n - 1)
+@compiled
def down(n: int) -> int:
if n == 0:
return 0
return 1 + down(n - 1)
+@compiled
def pow2(n: int) -> int:
if n == 0:
return 1
return 2 * pow2(n - 1)
+@compiled
def fact(n: int) -> int:
if n < 2:
return 1
diff --git a/python-multiplatform-ksp/src/test/python/test_regions.py b/python-multiplatform-ksp/src/test/python/test_regions.py
index 0246fd207..ac7aad6c8 100644
--- a/python-multiplatform-ksp/src/test/python/test_regions.py
+++ b/python-multiplatform-ksp/src/test/python/test_regions.py
@@ -37,12 +37,12 @@
FAIL_AFTER = "__typedpython_region_fail_after__" # (n) -> the nth region object allocation fails; -1 off
SOURCE = textwrap.dedent('''\
- # typedpython: compiled
"""typedpython region test module."""
KEPT = None
+ @compiled
class Node:
__slots__ = ("left", "right")
left: "Node | None"
@@ -53,12 +53,14 @@ def __init__(self, left: "Node | None", right: "Node | None") -> None:
self.right = right
+ @compiled
def make(depth: int) -> Node:
if depth > 0:
return Node(make(depth - 1), make(depth - 1))
return Node(None, None)
+ @compiled
def check(node: Node) -> int:
left: Node | None = node.left
right: Node | None = node.right
@@ -67,10 +69,12 @@ def check(node: Node) -> int:
return 1 + check(left) + check(right)
+ @compiled
def tree_sum(d: int) -> int:
return check(make(d))
+ @compiled
def repeat(d: int, n: int) -> int:
t: int = 0
for _ in range(n):
@@ -78,35 +82,42 @@ def repeat(d: int, n: int) -> int:
return t
+ @compiled
def escapes(d: int) -> Node:
return make(d)
+ @compiled
def keeps(d: int) -> int:
global KEPT
KEPT = make(d)
return check(KEPT)
+ @compiled
def prints(d: int) -> int:
n = make(d)
print("unknown code while the tree is alive")
return check(n)
+ @compiled
def ident(d: int) -> int:
return id(make(d)) * 0
+ @compiled
def same(d: int) -> bool:
return make(d) is make(d)
+ @compiled
def mixed(d: int) -> int:
t = make(d)
return check(make(d)) + check(t)
+ @compiled
def noisy(d: int) -> object:
print("unknown code")
return check(make(d))
diff --git a/python-multiplatform-ksp/src/test/python/test_resume.py b/python-multiplatform-ksp/src/test/python/test_resume.py
index 14115cc79..984c7b4b3 100644
--- a/python-multiplatform-ksp/src/test/python/test_resume.py
+++ b/python-multiplatform-ksp/src/test/python/test_resume.py
@@ -519,7 +519,7 @@ def lower(tmp_path, source):
def test_frontend_gives_a_snapshot_holder_one_resume_per_loop(tmp_path):
module = lower(tmp_path, """
- # typedpython: compiled
+ @compiled
def f(xs: list[float], n: int) -> float:
t = 0.0
for i in range(n):
@@ -572,7 +572,7 @@ def f(xs: list[float], n: int) -> float:
def test_frontend_function_without_a_snapshot_has_no_resumes(tmp_path):
module = lower(tmp_path, """
- # typedpython: compiled
+ @compiled
def g(n: int) -> int:
t = 0
for i in range(n):
diff --git a/python-multiplatform-ksp/src/test/python/test_str_dict.py b/python-multiplatform-ksp/src/test/python/test_str_dict.py
index 2362033c4..e6bc52c7b 100644
--- a/python-multiplatform-ksp/src/test/python/test_str_dict.py
+++ b/python-multiplatform-ksp/src/test/python/test_str_dict.py
@@ -26,70 +26,84 @@
import pytest
+from conftest import decorate_compiled
from typedpython import cbuild, cgen, frontend, verify
REPO = Path(__file__).resolve().parents[4]
WORDFREQ = REPO / "benchmarks" / "typedpython" / "py" / "wordfreq.py"
SOURCE = textwrap.dedent('''
- # typedpython: compiled
from typing import SupportsIndex
+ @compiled
def get(d: dict[object, object], k: object) -> object:
return d[k]
+ @compiled
def get_list(xs: list[object], i: int) -> object:
return xs[i]
+ @compiled
def get_index(xs: list[object], i: SupportsIndex) -> object:
return xs[i]
+ @compiled
def get_str(s: str, i: int) -> str:
return s[i]
+ @compiled
def get_nested(d: dict[object, dict[object, object]], a: object, b: object) -> object:
return d[a][b]
+ @compiled
def put(d: dict[object, object], k: object, v: object) -> None:
d[k] = v
+ @compiled
def put_list(xs: list[object], i: int, v: object) -> None:
xs[i] = v
+ @compiled
def has(c: list[object], x: object) -> bool:
return x in c
+ @compiled
def has_not(c: list[object], x: object) -> bool:
return x not in c
+ @compiled
def has_dict(c: dict[object, object], x: object) -> bool:
return x in c
+ @compiled
def has_str(c: str, x: str) -> bool:
return x in c
+ @compiled
def has_branch(c: list[object], x: object) -> str:
if x in c:
return "yes"
return "no"
+ @compiled
def has_chain(c: list[object], x: object, y: object) -> bool:
return x in c in y
+ @compiled
def pairs(xs: list[tuple[object, ...]]) -> list[object]:
out: list[object] = []
for a, b in xs:
@@ -98,6 +112,7 @@ def pairs(xs: list[tuple[object, ...]]) -> list[object]:
return out
+ @compiled
def triples(xs: list[tuple[object, ...]]) -> list[object]:
out: list[object] = []
for a, b, c in xs:
@@ -106,6 +121,7 @@ def triples(xs: list[tuple[object, ...]]) -> list[object]:
return out
+ @compiled
def each(xs: list[object]) -> list[object]:
out: list[object] = []
for x in xs:
@@ -115,6 +131,7 @@ def each(xs: list[object]) -> list[object]:
return out
+ @compiled
def first_after(xs: list[object], stop: object) -> object:
found: object = None
for x in xs:
@@ -124,12 +141,14 @@ def first_after(xs: list[object], stop: object) -> object:
return found
+ @compiled
def return_inside(xs: list[object]) -> object:
for x in xs:
return x
return None
+ @compiled
def nested(xs: list[list[object]]) -> list[object]:
out: list[object] = []
for row in xs:
@@ -140,6 +159,7 @@ def nested(xs: list[list[object]]) -> list[object]:
return out
+ @compiled
def items_of(d: dict[str, object]) -> list[object]:
out: list[object] = []
for k, v in d.items():
@@ -148,6 +168,7 @@ def items_of(d: dict[str, object]) -> list[object]:
return out
+ @compiled
def mutate_while_iterating(d: dict[str, object]) -> object:
n: int = 0
for k in d:
@@ -156,30 +177,37 @@ def mutate_while_iterating(d: dict[str, object]) -> object:
return n
+ @compiled
def fmt(x: object, y: float, n: int) -> str:
return f"{x}|{x!r}|{x!s}|{x!a}|{y:.3f}|{n:05d}|{x:>5}|{n}"
+ @compiled
def fmt_one(x: object) -> str:
return f"{x}"
+ @compiled
def fmt_text(x: object) -> str:
return f"a{x}b{x!r}c"
+ @compiled
def fmt_empty() -> str:
return f""
+ @compiled
def fmt_debug(x: object) -> str:
return f"{x=}"
+ @compiled
def cat(a: str, b: str) -> str:
return a + b
+ @compiled
def cat_in_place(a: str, b: str) -> str:
s: str = ""
s += a
@@ -187,6 +215,7 @@ def cat_in_place(a: str, b: str) -> str:
return s
+ @compiled
def grow(xs: list[object], ys: list[object]) -> list[object]:
out: list[object] = []
out += xs
@@ -194,6 +223,7 @@ def grow(xs: list[object], ys: list[object]) -> list[object]:
return out
+ @compiled
def empties() -> object:
d: dict[object, object] = {}
xs: list[object] = []
@@ -201,6 +231,7 @@ def empties() -> object:
return xs
+ @compiled
class Acc:
def __init__(self, log: list[object]) -> None:
self.log = log
@@ -230,6 +261,7 @@ def __imod__(self, other: int) -> Acc:
return self
+ @compiled
def aug_ops(a: Acc, b: int) -> Acc:
a += b
a -= b
@@ -240,12 +272,14 @@ def aug_ops(a: Acc, b: int) -> Acc:
return a
+ @compiled
def aug_list(xs: list[str], n: int) -> list[str]:
xs *= n
xs += xs
return xs
+ @compiled
def lookup_default(d: dict[str, int], k: str) -> object:
counts: dict[str, int] = {}
counts[k] = counts.get(k, 0) + 1
@@ -905,7 +939,7 @@ def run_main(main, n):
@pytest.fixture(scope="module")
def wordfreq(tmp_path_factory):
d = tmp_path_factory.mktemp("wordfreq")
- src = "# typedpython: compiled\n" + WORDFREQ.read_text()
+ src = decorate_compiled(WORDFREQ.read_text())
proved, diags, m = build_source(d, src, name="wordfreq")
spec = importlib.util.spec_from_file_location("wordfreq_plain", WORDFREQ)
plain = importlib.util.module_from_spec(spec)
diff --git a/python-multiplatform-ksp/src/test/python/test_tracing.py b/python-multiplatform-ksp/src/test/python/test_tracing.py
index 2c6939249..39f3c78b3 100644
--- a/python-multiplatform-ksp/src/test/python/test_tracing.py
+++ b/python-multiplatform-ksp/src/test/python/test_tracing.py
@@ -19,14 +19,15 @@
from typedpython import pipeline
SOURCE = """\
-# typedpython: compiled
from typing import Callable
+@compiled
def sq(x: float) -> float:
return x * x
+@compiled
def loop(n: int) -> float:
acc: float = 0.0
for k in range(n):
@@ -34,11 +35,13 @@ def loop(n: int) -> float:
return acc
+@compiled
def leaf(cb: Callable[[], None], n: float) -> float:
cb()
return n + 1.0
+@compiled
def outer(cb: Callable[[], None], n: float) -> float:
cb()
return leaf(cb, n) + leaf(cb, n)
From cca2ef5753f544a53c2c19eae5715e3a6b3a6616 Mon Sep 17 00:00:00 2001
From: BREW <81044362+b-re-w@users.noreply.github.com>
Date: Fri, 30 Oct 2026 05:01:16 +0900
Subject: [PATCH 2/2] TypedPython tests: child processes install the compiled
builtin themselves
Plain CPython without typedpython_builtins.pth has no builtins.compiled; the child scripts now prepend conftest's COMPILED_SHIM. Also fixes test_regions' split that left a dangling @compiled.
---
python-multiplatform-ksp/src/test/python/conftest.py | 4 ++++
.../src/test/python/test_class_methods.py | 8 ++++----
.../src/test/python/test_eval_breaker.py | 3 ++-
.../src/test/python/test_fast_entry.py | 3 ++-
.../src/test/python/test_free_threaded.py | 3 ++-
.../src/test/python/test_parallel_loops.py | 3 ++-
.../src/test/python/test_recursion.py | 3 ++-
python-multiplatform-ksp/src/test/python/test_regions.py | 4 +++-
python-multiplatform-ksp/src/test/python/test_tracing.py | 3 ++-
9 files changed, 23 insertions(+), 11 deletions(-)
diff --git a/python-multiplatform-ksp/src/test/python/conftest.py b/python-multiplatform-ksp/src/test/python/conftest.py
index 669816bf4..85b038234 100644
--- a/python-multiplatform-ksp/src/test/python/conftest.py
+++ b/python-multiplatform-ksp/src/test/python/conftest.py
@@ -9,6 +9,10 @@
# The `typedpython_builtins.pth` of an installed wheel; the test interpreter has no such install.
builtins.compiled = getattr(builtins, "compiled", lambda f, /: f)
+# Prepended to every child-process script that loads a module whose source uses `@compiled`: a plain
+# CPython has the builtin only when `typedpython_builtins.pth` is installed, so the child installs it.
+COMPILED_SHIM = "import builtins\nif not hasattr(builtins, 'compiled'):\n builtins.compiled = lambda f: f\n"
+
FIXTURES = Path(__file__).parent / "fixtures"
diff --git a/python-multiplatform-ksp/src/test/python/test_class_methods.py b/python-multiplatform-ksp/src/test/python/test_class_methods.py
index cd08794e3..b35b5db6e 100644
--- a/python-multiplatform-ksp/src/test/python/test_class_methods.py
+++ b/python-multiplatform-ksp/src/test/python/test_class_methods.py
@@ -40,7 +40,7 @@
import pytest
-from conftest import decorate_compiled
+from conftest import COMPILED_SHIM, decorate_compiled
from typedpython import cbuild, cgen, cost, frontend, pipeline, verify
HERE = Path(__file__).resolve().parent
@@ -553,7 +553,7 @@ def test_class_info_reports_the_methods(built):
# --- class state changed after import: child processes --------------------------------------------
-CHILD = '''
+CHILD = COMPILED_SHIM + '''
import json, sys
from importlib.util import module_from_spec, spec_from_file_location
from pathlib import Path
@@ -736,7 +736,7 @@ def test_reference_counts_are_flat_over_many_calls(built):
# --- free-threaded concurrency --------------------------------------------------------------------
-THREADS = '''
+THREADS = COMPILED_SHIM + '''
import sys, threading
from pathlib import Path
sys.path.insert(0, {main!r})
@@ -800,7 +800,7 @@ def test_concurrent_calls_on_shared_and_private_instances(built):
# --- mutation: the exact-type guard of `self` -------------------------------------------------------
-GUARD_PROBE = '''
+GUARD_PROBE = COMPILED_SHIM + '''
import sys
from pathlib import Path
sys.path.insert(0, {main!r})
diff --git a/python-multiplatform-ksp/src/test/python/test_eval_breaker.py b/python-multiplatform-ksp/src/test/python/test_eval_breaker.py
index 0d6bb643b..dece8d3b8 100644
--- a/python-multiplatform-ksp/src/test/python/test_eval_breaker.py
+++ b/python-multiplatform-ksp/src/test/python/test_eval_breaker.py
@@ -18,6 +18,7 @@
import pytest
+from conftest import COMPILED_SHIM
from typedpython import pipeline
posix_signals = pytest.mark.skipif(sys.platform == "win32", reason="needs POSIX signals (SIGALRM/SIGUSR1/setitimer) or fork; Windows has none")
@@ -49,7 +50,7 @@ def total(xs: list[float], rounds: int) -> float:
return s
"""
-LOADER = """\
+LOADER = COMPILED_SHIM + """\
import importlib.machinery, importlib.util, json, signal, sys, threading, time
path = sys.argv[1]
loader = importlib.machinery.ExtensionFileLoader("loops", path)
diff --git a/python-multiplatform-ksp/src/test/python/test_fast_entry.py b/python-multiplatform-ksp/src/test/python/test_fast_entry.py
index d448e3b41..633a14487 100644
--- a/python-multiplatform-ksp/src/test/python/test_fast_entry.py
+++ b/python-multiplatform-ksp/src/test/python/test_fast_entry.py
@@ -20,6 +20,7 @@
import pytest
+from conftest import COMPILED_SHIM
from typedpython import cbuild, cgen, frontend, verify
posix_signals = pytest.mark.skipif(sys.platform == "win32", reason="needs POSIX signals (SIGALRM/SIGUSR1/setitimer) or fork; Windows has none")
@@ -170,7 +171,7 @@ def test_the_fast_impl_is_never_addressed(traced):
assert "&tp_fimpl_" not in c and "(tp_fimpl_" not in c
-LOAD = """\
+LOAD = COMPILED_SHIM + """\
import importlib.machinery, importlib.util, json, sys
def load(name, path, ext):
if ext:
diff --git a/python-multiplatform-ksp/src/test/python/test_free_threaded.py b/python-multiplatform-ksp/src/test/python/test_free_threaded.py
index e954c03ba..e857d3950 100644
--- a/python-multiplatform-ksp/src/test/python/test_free_threaded.py
+++ b/python-multiplatform-ksp/src/test/python/test_free_threaded.py
@@ -27,6 +27,7 @@
import pytest
+from conftest import COMPILED_SHIM
from typedpython import cbuild, cgen, frontend, pipeline, verify
STRESS = max(1, int(os.environ.get("TP_FT_STRESS", "1")))
@@ -155,7 +156,7 @@ def usek(x: int) -> int:
return x + K
'''
-LOADER = """\
+LOADER = COMPILED_SHIM + """\
import faulthandler, importlib.machinery, importlib.util, json, sys, threading, time
faulthandler.enable()
path = sys.argv[1]
diff --git a/python-multiplatform-ksp/src/test/python/test_parallel_loops.py b/python-multiplatform-ksp/src/test/python/test_parallel_loops.py
index e86605b69..ecf4b8c7a 100644
--- a/python-multiplatform-ksp/src/test/python/test_parallel_loops.py
+++ b/python-multiplatform-ksp/src/test/python/test_parallel_loops.py
@@ -25,6 +25,7 @@
import pytest
+from conftest import COMPILED_SHIM
from typedpython import cbuild, cgen, frontend, ir, verify
posix_signals = pytest.mark.skipif(sys.platform == "win32", reason="needs POSIX signals (SIGALRM/SIGUSR1/setitimer) or fork; Windows has none")
@@ -335,7 +336,7 @@ def test_list_untouched_when_the_call_raises_before_the_loop(mod):
# --- child processes: thread counts, creation failure, concurrency -----------------------------------
-CHILD = """\
+CHILD = COMPILED_SHIM + """\
import importlib.machinery, importlib.util, json, struct, sys, threading
path = sys.argv[1]
name = path.replace(chr(92), "/").rsplit("/", 1)[-1].split(".")[0]
diff --git a/python-multiplatform-ksp/src/test/python/test_recursion.py b/python-multiplatform-ksp/src/test/python/test_recursion.py
index 49901b0e6..84c36f1e6 100644
--- a/python-multiplatform-ksp/src/test/python/test_recursion.py
+++ b/python-multiplatform-ksp/src/test/python/test_recursion.py
@@ -15,6 +15,7 @@
import pytest
+from conftest import COMPILED_SHIM
from typedpython import pipeline
PACKAGE = Path(__file__).resolve().parents[2] / "main" / "python"
@@ -239,7 +240,7 @@ def inner(j):
# --- sanitizers: the RecursionError path ---------------------------------------------------------
-ASAN_SCRIPT = textwrap.dedent('''\
+ASAN_SCRIPT = COMPILED_SHIM + textwrap.dedent('''\
import sys
sys.path.insert(0, {pkg!r})
from typedpython import cbuild
diff --git a/python-multiplatform-ksp/src/test/python/test_regions.py b/python-multiplatform-ksp/src/test/python/test_regions.py
index ac7aad6c8..7d7080ee9 100644
--- a/python-multiplatform-ksp/src/test/python/test_regions.py
+++ b/python-multiplatform-ksp/src/test/python/test_regions.py
@@ -25,6 +25,7 @@
import pytest
from test_cgen import runtime_dir, need_runtime
+from conftest import COMPILED_SHIM
from typedpython import cbuild, cgen, cost, frontend, verify
posix_signals = pytest.mark.skipif(sys.platform == "win32", reason="needs POSIX signals (SIGALRM/SIGUSR1/setitimer) or fork; Windows has none")
@@ -392,10 +393,11 @@ def test_a_forged_module_level_variant_set_is_reported(built):
def test_a_finalizer_or_a_field_store_keeps_the_class_out_of_a_region(tmp_path):
- base = SOURCE.split("def escapes")[0]
+ base = SOURCE.split("@compiled\ndef escapes")[0]
with_del = base.replace(" self.right = right\n",
" self.right = right\n\n def __del__(self) -> None:\n pass\n", 1)
with_store = base + textwrap.dedent('''\
+ @compiled
def poke(node: Node) -> int:
node.left = None
return 0
diff --git a/python-multiplatform-ksp/src/test/python/test_tracing.py b/python-multiplatform-ksp/src/test/python/test_tracing.py
index 39f3c78b3..823c1dd1d 100644
--- a/python-multiplatform-ksp/src/test/python/test_tracing.py
+++ b/python-multiplatform-ksp/src/test/python/test_tracing.py
@@ -16,6 +16,7 @@
import pytest
+from conftest import COMPILED_SHIM
from typedpython import pipeline
SOURCE = """\
@@ -47,7 +48,7 @@ def outer(cb: Callable[[], None], n: float) -> float:
return leaf(cb, n) + leaf(cb, n)
"""
-LOADER = """\
+LOADER = COMPILED_SHIM + """\
import importlib.machinery, importlib.util, json, sys, threading
path = sys.argv[1]
loader = importlib.machinery.ExtensionFileLoader("tmod", path)