Skip to content

Commit f2eba65

Browse files
authored
Merge pull request #62 from thisisthepy/work/builtin-compiled
Feat: @compiled is a builtin from the moment the interpreter starts
2 parents 6536dc2 + e40bffb commit f2eba65

3 files changed

Lines changed: 76 additions & 0 deletions

File tree

‎docs/SPEC.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ fail, then implement.
7070
- **L-7** iOS: the framework carries no stdlib, so `PYTHONHOME` must point at one; the test build
7171
extracts it. `Status: partial` — exercised by the shared suite on the simulator; no iOS-specific
7272
lifecycle test beyond `PM/iosSimulatorArm64Test/.../AsyncioAvailabilityProbeTest.kt`.
73+
- **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`.
7374

7475
## 2. Low-level C API (downcall surface)
7576

‎python-multiplatform/src/commonMain/kotlin/python/multiplatform/ffi/Python3.kt‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ object Python3 {
4949
// get here and the consumer's payload has still not been put on sys.path. The hook
5050
// does its work at most once per process, so arriving here repeatedly is free.
5151
PythonPayload.installStagedRootsOnStartup()
52+
installBuiltinCompiledOnce()
5253
return
5354
}
5455
// Runs once per process, before the call whose own failure mode is either an uncatchable
@@ -78,6 +79,9 @@ object Python3 {
7879
// commonMain and commonTest is now inside withPython{} or withGIL{}, so this is safe.
7980
// See ROADMAP §1 and §4 for the history and the previous revert.
8081
if (mainThreadState == null) mainThreadState = PyEval_SaveThread()
82+
// `compiled` becomes a builtin name here (docs/SPEC.md N-7). Needs the GIL, so it comes
83+
// after the parking above and goes through exec()'s withPython/withGIL.
84+
installBuiltinCompiledOnce()
8185
// The consumer's own Python code goes on sys.path here: after Py_Initialize() built the
8286
// list (nothing can add to it before that point) and before this function returns, so
8387
// before any import a caller can reach. `PYTHONHOME` above is the *standard library* and
@@ -86,6 +90,38 @@ object Python3 {
8690
PythonPayload.installStagedRootsOnStartup()
8791
}
8892

93+
private var builtinCompiledInstalled = false
94+
95+
private fun installBuiltinCompiledOnce() {
96+
if (builtinCompiledInstalled) return
97+
installBuiltinCompiled()
98+
builtinCompiledInstalled = true
99+
}
100+
101+
/**
102+
* Sets `builtins.compiled` to an identity decorator so `@compiled` needs no import.
103+
* The TypedPython compiler picks decorated functions at build time; at run time it does
104+
* nothing. An existing `builtins.compiled` (e.g. the typedpython dev shim) is left alone.
105+
*
106+
* Runs through [exec], which holds the GIL for every C API call it makes; no raw pointer is
107+
* held here, so there is no reference to state. The snippet runs inside a function so it
108+
* leaves no names behind in `__main__`.
109+
*/
110+
internal fun installBuiltinCompiled() {
111+
exec(
112+
"""
113+
def _pm_install_compiled():
114+
import builtins
115+
if not hasattr(builtins, "compiled"):
116+
def compiled(f):
117+
return f
118+
builtins.compiled = compiled
119+
_pm_install_compiled()
120+
del _pm_install_compiled
121+
""".trimIndent()
122+
)
123+
}
124+
89125
/**
90126
* Finalize Python (Will not be able to re-initialize after this | Let this function be internal)
91127
*/
@@ -110,6 +146,7 @@ object Python3 {
110146
// initialize() rebuilds it instead of calling through a dangling one. Same for the
111147
// runMain/runApp helpers, which are cached the same way and would dangle the same way.
112148
checkpointCallable = null
149+
builtinCompiledInstalled = false
113150
runModuleCallable = null
114151
runTopLevelCallable = null
115152
runFileCallable = null
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package python.multiplatform.ffi
2+
3+
import kotlin.test.Test
4+
import kotlin.test.assertEquals
5+
6+
/** Issue #42 / SPEC N-7: `compiled` is a builtin installed at interpreter initialisation. */
7+
class BuiltinCompiledTest {
8+
9+
private fun evalStr(expr: String): String = PythonTestFixture.eval(expr).toString()
10+
11+
@Test
12+
fun compiledResolvesWithoutImportAndIsIdentity() = PythonTestFixture.withInterpreter {
13+
assertEquals("True", evalStr("compiled(len) is len"))
14+
Python3.exec("def _bc_f(): return 1\n_bc_r = compiled(_bc_f) is _bc_f")
15+
assertEquals("True", evalStr("_bc_r"))
16+
// It must live in builtins, not leak into __main__.
17+
assertEquals("False", evalStr("'compiled' in globals()"))
18+
}
19+
20+
@Test
21+
fun existingBuiltinCompiledIsNotOverwritten() = PythonTestFixture.withInterpreter {
22+
Python3.exec(
23+
"""
24+
import builtins as _b
25+
_bc_orig = _b.compiled
26+
_bc_sentinel = lambda f: f
27+
_b.compiled = _bc_sentinel
28+
""".trimIndent()
29+
)
30+
try {
31+
Python3.installBuiltinCompiled()
32+
assertEquals("True", evalStr("_b.compiled is _bc_sentinel"))
33+
} finally {
34+
Python3.exec("_b.compiled = _bc_orig")
35+
}
36+
assertEquals("True", evalStr("_b.compiled is _bc_orig"))
37+
}
38+
}

0 commit comments

Comments
 (0)