From c30a772c2158b4eb27d905c9b6f1936bbe11c2da Mon Sep 17 00:00:00 2001 From: BREW <81044362+b-re-w@users.noreply.github.com> Date: Sat, 3 Oct 2026 09:19:53 +0900 Subject: [PATCH] Feat: @compiled is a builtin from the moment the interpreter starts Python3.initialize installs builtins.compiled as an identity decorator once, unless something already defined it (the TypedPython dev shim on plain CPython does the same). User code marks compiled functions with no import; the compiler picks them out at build time, so at run time it is a no-op. --- docs/SPEC.md | 1 + .../python/multiplatform/ffi/Python3.kt | 37 ++++++++++++++++++ .../multiplatform/ffi/BuiltinCompiledTest.kt | 38 +++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 python-multiplatform/src/commonTest/kotlin/python/multiplatform/ffi/BuiltinCompiledTest.kt diff --git a/docs/SPEC.md b/docs/SPEC.md index d54621613..19603ed2d 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -70,6 +70,7 @@ fail, then implement. - **L-7** iOS: the framework carries no stdlib, so `PYTHONHOME` must point at one; the test build extracts it. `Status: partial` — exercised by the shared suite on the simulator; no iOS-specific lifecycle test beyond `PM/iosSimulatorArm64Test/.../AsyncioAvailabilityProbeTest.kt`. +- **L-8** Initialisation sets `builtins.compiled` to an identity decorator (an existing one is kept), so `@compiled` needs no import on any platform (N-7, issue #42). `Status: implemented` — `PM/commonTest/.../ffi/BuiltinCompiledTest.kt`. ## 2. Low-level C API (downcall surface) diff --git a/python-multiplatform/src/commonMain/kotlin/python/multiplatform/ffi/Python3.kt b/python-multiplatform/src/commonMain/kotlin/python/multiplatform/ffi/Python3.kt index 8891d5c7a..fbd3095e1 100644 --- a/python-multiplatform/src/commonMain/kotlin/python/multiplatform/ffi/Python3.kt +++ b/python-multiplatform/src/commonMain/kotlin/python/multiplatform/ffi/Python3.kt @@ -49,6 +49,7 @@ object Python3 { // get here and the consumer's payload has still not been put on sys.path. The hook // does its work at most once per process, so arriving here repeatedly is free. PythonPayload.installStagedRootsOnStartup() + installBuiltinCompiledOnce() return } // Runs once per process, before the call whose own failure mode is either an uncatchable @@ -78,6 +79,9 @@ object Python3 { // commonMain and commonTest is now inside withPython{} or withGIL{}, so this is safe. // See ROADMAP §1 and §4 for the history and the previous revert. if (mainThreadState == null) mainThreadState = PyEval_SaveThread() + // `compiled` becomes a builtin name here (docs/SPEC.md N-7). Needs the GIL, so it comes + // after the parking above and goes through exec()'s withPython/withGIL. + installBuiltinCompiledOnce() // The consumer's own Python code goes on sys.path here: after Py_Initialize() built the // list (nothing can add to it before that point) and before this function returns, so // before any import a caller can reach. `PYTHONHOME` above is the *standard library* and @@ -86,6 +90,38 @@ object Python3 { PythonPayload.installStagedRootsOnStartup() } + private var builtinCompiledInstalled = false + + private fun installBuiltinCompiledOnce() { + if (builtinCompiledInstalled) return + installBuiltinCompiled() + builtinCompiledInstalled = true + } + + /** + * Sets `builtins.compiled` to an identity decorator so `@compiled` needs no import. + * The TypedPython compiler picks decorated functions at build time; at run time it does + * nothing. An existing `builtins.compiled` (e.g. the typedpython dev shim) is left alone. + * + * Runs through [exec], which holds the GIL for every C API call it makes; no raw pointer is + * held here, so there is no reference to state. The snippet runs inside a function so it + * leaves no names behind in `__main__`. + */ + internal fun installBuiltinCompiled() { + exec( + """ + def _pm_install_compiled(): + import builtins + if not hasattr(builtins, "compiled"): + def compiled(f): + return f + builtins.compiled = compiled + _pm_install_compiled() + del _pm_install_compiled + """.trimIndent() + ) + } + /** * Finalize Python (Will not be able to re-initialize after this | Let this function be internal) */ @@ -110,6 +146,7 @@ object Python3 { // initialize() rebuilds it instead of calling through a dangling one. Same for the // runMain/runApp helpers, which are cached the same way and would dangle the same way. checkpointCallable = null + builtinCompiledInstalled = false runModuleCallable = null runTopLevelCallable = null runFileCallable = null diff --git a/python-multiplatform/src/commonTest/kotlin/python/multiplatform/ffi/BuiltinCompiledTest.kt b/python-multiplatform/src/commonTest/kotlin/python/multiplatform/ffi/BuiltinCompiledTest.kt new file mode 100644 index 000000000..5d3ef407f --- /dev/null +++ b/python-multiplatform/src/commonTest/kotlin/python/multiplatform/ffi/BuiltinCompiledTest.kt @@ -0,0 +1,38 @@ +package python.multiplatform.ffi + +import kotlin.test.Test +import kotlin.test.assertEquals + +/** Issue #42 / SPEC N-7: `compiled` is a builtin installed at interpreter initialisation. */ +class BuiltinCompiledTest { + + private fun evalStr(expr: String): String = PythonTestFixture.eval(expr).toString() + + @Test + fun compiledResolvesWithoutImportAndIsIdentity() = PythonTestFixture.withInterpreter { + assertEquals("True", evalStr("compiled(len) is len")) + Python3.exec("def _bc_f(): return 1\n_bc_r = compiled(_bc_f) is _bc_f") + assertEquals("True", evalStr("_bc_r")) + // It must live in builtins, not leak into __main__. + assertEquals("False", evalStr("'compiled' in globals()")) + } + + @Test + fun existingBuiltinCompiledIsNotOverwritten() = PythonTestFixture.withInterpreter { + Python3.exec( + """ + import builtins as _b + _bc_orig = _b.compiled + _bc_sentinel = lambda f: f + _b.compiled = _bc_sentinel + """.trimIndent() + ) + try { + Python3.installBuiltinCompiled() + assertEquals("True", evalStr("_b.compiled is _bc_sentinel")) + } finally { + Python3.exec("_b.compiled = _bc_orig") + } + assertEquals("True", evalStr("_b.compiled is _bc_orig")) + } +}