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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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)
*/
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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"))
}
}
Loading