Closure-routed run: ck run on a project dir (helloWorld runs its lifecycle) #37

Manually merged
hkowalski merged 3 commits from feat/037-closure-routed-run-ck-run-on-a-project-dir-hel into master 2026-02-16 08:00:00 +01:00
Owner

Closure-routed run: ck run on a project dir (helloWorld runs its lifecycle)

Along the way:

  • First stdout from CkLang: fn main calls intrinsic provider op with a string
  • Effect-interface dispatch: Log::info -> sole provider's fn

Touches 13 files: docs/impl/08-diagnostics.md, docs/impl/lowering/01-cranelift-emit.md, docs/impl/resolution/C2-provider-table.md, docs/impl/runtime/substrate-and-supervisor.md, src/rust/crates/ck_driver/src/compile.rs, src/rust/crates/ck_driver/src/lib.rs and 7 more.

Closure-routed run: ck run on a project dir (helloWorld runs its lifecycle) Along the way: - First stdout from CkLang: fn main calls intrinsic provider op with a string - Effect-interface dispatch: Log::info -> sole provider's fn Touches 13 files: `docs/impl/08-diagnostics.md`, `docs/impl/lowering/01-cranelift-emit.md`, `docs/impl/resolution/C2-provider-table.md`, `docs/impl/runtime/substrate-and-supervisor.md`, `src/rust/crates/ck_driver/src/compile.rs`, `src/rust/crates/ck_driver/src/lib.rs` and 7 more.
Author
Owner

Nothing outside calls run_project. It is pub(crate) in src/rust/crates/ck_driver/src/run.rs now.

Nothing outside calls `run_project`. It is pub(crate) in `src/rust/crates/ck_driver/src/run.rs` now.
Author
Owner

Split. The decision in repo_root is a pure function now, with its own tests in src/rust/crates/ck_driver/tests/log_interface_run.rs.

Split. The decision in `repo_root` is a pure function now, with its own tests in `src/rust/crates/ck_driver/tests/log_interface_run.rs`.
hkowalski approved these changes 2026-02-16 07:55:00 +01:00
hkowalski left a comment

A clear improvement. The change in src/rust/crates/ck_driver/src/run.rs is the part I would have argued for.

A clear improvement. The change in `src/rust/crates/ck_driver/src/run.rs` is the part I would have argued for.
@ -71,0 +101,4 @@
/// (most often: it does not exist); otherwise [`RunError`] if any stage fails. A
/// non-zero *program* exit is reported in [`RunOutcome::exit_code`], not as an
/// error.
pub fn run_project(
Owner

run_project is public. Does anything outside the crate call it, or is it pub because that was easier? Every public item is a promise.

`run_project` is public. Does anything outside the crate call it, or is it pub because that was easier? Every public item is a promise.
@ -0,0 +136,4 @@
/// The repo root, four parents above this crate's manifest dir
/// (`<repo>/src/rust/crates/ck_driver`).
fn repo_root() -> PathBuf {
Owner

repo_root reads, decides and writes in one function. The deciding part is the piece I would want to test on its own. Worth splitting?, at src/rust/crates/ck_driver/tests/log_interface_run.rs:139.

`repo_root` reads, decides and writes in one function. The deciding part is the piece I would want to test on its own. Worth splitting?, at src/rust/crates/ck_driver/tests/log_interface_run.rs:139.
ck run <dir> now compiles a multi-module project closure (app + injected
stdlib) instead of a single file: run_cmd branches file-vs-directory; run_project
-> compile_project_object reuses the corpus closure cascade (load_project with
stdlib injection, N2 effects + link + body resolution across the closure), finds
the first actor in an app-owned module, and emits it via the existing order-5a
emit_actor_object. Single-file ck run is unchanged. Stdlib root: --stdlib >
$CK_STDLIB > default (repo tests/corpus/stdlib).

This makes `ck run tests/corpus/helloWorld` compile through the closure (Log
resolves via the injected stdlib, front-end clean) and run the Main lifecycle
(ActorSpawned root -> ActorSpawned Main -> ActorDespawned Normal). The spawn body
is NOT executed yet (trivial behavior), so no stdout line — behavior execution +
effect dispatch + the native leaf are the remaining slices.

Fixed a path-normalization defect found in review: run_project now canonicalizes
both app and stdlib roots at the boundary (loader stores lexically-normalized
paths, so a relative app path never matched the starts_with app-owned filter and
ck run on a relative path failed "no actor declared"). New RunError::PathResolve
for a clean message; regression fixture closure_run_accepts_a_relative_app_path
runs the exact ../..-relative invocation.

Gates: fmt 0, clippy --all-targets -D warnings 0, cargo test workspace 0 fail
(closure_run 3/3 incl. relative-path regression, single-file paths intact,
corpus reconciliation, architecture R1-R4, linkchecker), build clean, 0 leaked
containers. Independently verified: relative + absolute `ck run helloWorld`
both run the lifecycle, no stdout.
`ck run` on a project whose app entry is
  fn main() -> Unit { StdoutLog::info("hello, world") }
now prints `hello, world` from inside the container — the first time a CkLang
program produces output. Independently verified with an arbitrary string
("orchestrator was here 42" → that exact stdout), proving the literal flows
through, not a hardcoded write.

Lowering (ck_lower/codegen.rs): Lit::Str -> a read-only data symbol holding the
unescaped bytes (read_str_bytes/decode_escapes over the lexer's escape table),
value = (ptr, len). emit_intrinsic_call is general: callee must resolve to
Ref(Resolution::Item(Fn)), the driver-supplied IntrinsicTargets table maps the
FnId -> (module,op), and codegen's intrinsic_target maps (module,op) -> floor
symbol (StdoutLog::* -> ck_rt_write_stdout) — no call-site name special-casing;
intrinsic-ness comes only from the fn's `= intrinsic` identity + the target map.
The floor symbol is imported and called with (ptr,len). Unit fn main runs the
body then exits 0.

compile_project_object gains a fn-main entry path (prefers actor if present).
Driver builds IntrinsicTargets (FnId -> (module,op)) by re-scanning the closure's
intrinsic-bodied fns. ck_lower still depends only on ck_hir/ck_span/ck_types.

Provider privacy (E0302): a `mod X provides E` module is its own header, so its
operation fns are public (docs/effects.md "neither is hidden"); link.rs promotes
a provider module's top-level fns to Public. Doc-backed; follow-up tracked to
narrow this to op-fns only (so future private helpers stay private) and to add
run-path type-checking (the fn-main path is resolve-only today).

Gates: fmt 0, clippy --all-targets -D warnings 0, cargo test workspace 0 fail
(stdout_run 2 real-docker two-string, closure_run 3, F2 42/9, actor_lifecycle,
meta, corpus reconciliation, architecture R1-R4, ck_resolve 89, linkchecker),
build clean, 0 leaked containers.
A call to an effect's interface op now compile-time-resolves to its bound
provider and prints: `ck run` on `fn main() -> Unit { Log::info("hi") }` prints
`hi` via Log -> StdoutLog::info -> ck_rt_write_stdout. Independently verified
with an arbitrary string through the interface.

Driver (compile.rs) builds the C2 provider table over the closure and an
OpId->FnId dispatch map via the locked tree-search: OpId -> (EffectId, op-name)
via interner.op_data; exactly one provider -> use its fn for that op; zero ->
E0503; two-or-more -> E0504 (config tie-break NOT implemented — config.dev.ck's
`Log <- StdoutLog` is not consumed; Log resolves only by sole-impl). Never a
silent pick. Codegen's emit_intrinsic_call now accepts a Resolution::Op(OpId)
callee (mapped via the driver-supplied OpDispatch) as well as the concrete
Resolution::Item(Fn) form, then runs the unchanged FnId->(module,op)->floor
symbol path. ck_lower still depends only on ck_hir/ck_span/ck_types.

helloWorld's Log::info resolves to Op(OpId) and will dispatch unambiguously to
the sole StdoutLog provider once its actor body is executed (next slice).

Gates: fmt 0, clippy --all-targets -D warnings 0, cargo test workspace 0 fail
(log_interface_run 2 real-docker, stdout_run 2, closure_run 3, F2 42/9,
actor_lifecycle, meta, corpus reconciliation, architecture R1-R4, linkchecker),
build clean, 0 leaked containers. Diagnostics E0502/E0503/E0504 documented.
hkowalski manually merged commit 04537190f8 into master 2026-02-16 08:00:00 +01:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
northgale/cklang-bootstrap!37
No description provided.