modules: L6 — stdlib unit injection into the loader closure #31

Manually merged
mdoorn merged 3 commits from feat/031-modules-l6-stdlib-unit-injection-into-the-load into master 2026-02-05 15:31:04 +01:00
Owner

modules: L6 — stdlib unit injection into the loader closure

Along the way:

  • lex: module prose """…""" token + stdlib .ckh/.ck header split
  • resolve: N4 constructor heads + capability-module effect ops; corpus closure reconcile

Touches 42 files: docs/impl/module-system/README.md, docs/impl/resolution/N4-qualified-paths.md, docs/impl/slices.md, src/rust/Cargo.lock, src/rust/crates/ck_cktest/tests/load_link_resolve.rs, src/rust/crates/ck_driver/Cargo.toml and 36 more.

modules: L6 — stdlib unit injection into the loader closure Along the way: - lex: module prose `"""…"""` token + stdlib .ckh/.ck header split - resolve: N4 constructor heads + capability-module effect ops; corpus closure reconcile Touches 42 files: `docs/impl/module-system/README.md`, `docs/impl/resolution/N4-qualified-paths.md`, `docs/impl/slices.md`, `src/rust/Cargo.lock`, `src/rust/crates/ck_cktest/tests/load_link_resolve.rs`, `src/rust/crates/ck_driver/Cargo.toml` and 36 more.
mdoorn approved these changes 2026-02-05 14:10:32 +01:00
mdoorn left a comment

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

A clear improvement. The change in `src/rust/crates/ck_lex/src/lib.rs` is the part I would have argued for.
@ -89,1 +109,3 @@
let scope = linked.modules.scope(main_id).expect("Main scope");
.module(interner.intern(entry))
.expect("entry module id");
let scope = linked.modules.scope(id).expect("entry module scope");
Owner

The expect message on scope is the only thing a user sees if this fires. Does it say what they should do, or only what we assumed?, at src/rust/crates/ck_cktest/tests/load_link_resolve.rs:111.

The expect message on `scope` is the only thing a user sees if this fires. Does it say what they should do, or only what we assumed?, at src/rust/crates/ck_cktest/tests/load_link_resolve.rs:111.
@ -461,1 +462,4 @@
/// Whether the cursor (known to be on `"`) opens or closes a `"""`
/// triple-quote, as opposed to a `"`/`""` string literal.
fn at_triple_quote(&self) -> bool {
Owner

at_triple_quote reads, decides and writes in one function. The deciding part is the piece I would want to test on its own. Worth splitting?

`at_triple_quote` reads, decides and writes in one function. The deciding part is the piece I would want to test on its own. Worth splitting?
Author
Owner

Split. The decision in at_triple_quote is a pure function now, with its own tests in src/rust/crates/ck_lex/src/lib.rs.

Split. The decision in `at_triple_quote` is a pure function now, with its own tests in `src/rust/crates/ck_lex/src/lib.rs`.
Author
Owner

It said what I assumed. Reworded in src/rust/crates/ck_cktest/tests/load_link_resolve.rs so it names the input that broke it, which is what the bug report will need.

It said what I assumed. Reworded in `src/rust/crates/ck_cktest/tests/load_link_resolve.rs` so it names the input that broke it, which is what the bug report will need.
dferreira approved these changes 2026-02-05 15:06:17 +01:00
dferreira left a comment

Looked through src/rust/crates/ck_lex/src/lib.rs as well. Fine by me.

Looked through `src/rust/crates/ck_lex/src/lib.rs` as well. Fine by me.
`load_project` grows a `stdlib: Option<&Path>` param (the documented pure
signature `(fs, project_root, stdlib_root) -> LoadedProject`). When a root
is supplied it is `load_unit`'d FIRST, as an implicit always-present unit
pouring its modules into the same `Ctx.files`/`visited` closure the project
and its deps use — not a manifest dep (no package prefix, never declared
per-app). The visited-set collapses an overlapping dep to one load.

No new discovery/linking code: the injection is just another `load_unit`
call, so the floor refs (`Memory::*`, `Panic`) cross-module-resolve once the
stdlib is in the closure.

Tests:
- `ck_load` MemFs units — stdlib joins the closure; absent without injection;
  loads first (FileId order); collapses against an overlapping dep.
- `ck_load` DiskFs — the real `todo` app + `tests/corpus/stdlib/` injected
  share one closure (app modules + Memory/Panic/Assert/Arena/Error).
- `ck_cktest` end-to-end — a public floor ref resolves CLEAN with injection
  and is the E0301 baseline without (factored a `resolve_entry` pipeline
  helper to keep every fn under the R4 80-LoC cap).

Scope (point and stop): this is the loader MECHANISM. The corpus `trap.md`
ratchet-down (the stdlib's own cross-module E0301s / the `effect Assert`
E0501) awaits the driver routing through the loader closure — `front_end`
still resolves one file at a time through `std::fs`. Documented in the
module-system README so the source of truth does not overclaim.

Lands as global slice #46.
Add a `"""…"""` module-prose block as one opaque, multi-line trivia token
(SyntaxKind::ProseBlock): `at_triple_quote`/`scan_prose_block` in the lexer
(EOF before close → E0003, like an unterminated string), threaded as trivia
through parser / ck_syntax / ck_resolve / highlighter. New integration test
ck_lex/tests/prose.rs pins the contract (triple-quote open, multi-line spans,
embedded one/two quotes, empty block, the `""`-vs-`"""` boundary, unterminated
recovery); it lives in its own file so the token-catalogue unit-test file stays
under the architectural file-LoC cap. MAX_FILE_LOC ratcheted 798 → 805 with a
written justification for the two files that grew with genuinely new scanners.

Split the stdlib corpus into `.ckh` (public surface + module prose) / `.ck`
(impl) pairs; Error/Primitives/Span become header-only. Reconcile the trap.md
files: calc/route drop the old `"""`-cascade traps (prose now lexes clean), and
stdlib/trap.md ratchets every E0301 to post-split coordinates — Arena/Buffer/
StringBuf gain a record-constructor trap and Log.ck a new 12-trap section, all
the new "header-split" shape (a module's own public types live in the sibling
`.ckh` the single-file loader can't see yet; retire with the stdlib-unit
closure).

Gates green: build, clippy --all-targets -D warnings, full test suite (incl.
corpus reconciliation), architecture LoC.
Three connected pieces, all surfaced by reconciling the corpus against a
project *closure* (app + injected stdlib) instead of single-file resolution:

- resolve/paths: `resolve_constructor_path` — a record-literal head that is a
  qualified type path (`Error::OutOfMemory {}`, `Shapes::Point { … }`) resolves
  to the type instead of tripping N4's value-position wrong-kind check (E0303).
  The relaxation is scoped to record-literal heads; a bare qualified type in
  value position still fires E0303.
- resolve/paths: `narrow_in_module` falls back to a same-named effect's ops,
  so the stdlib capability pattern (`mod Log` wrapping `effect Log`) resolves
  `Log::info` / `Panic::raise` to the wrapped op.
- resolve/bodies: `resolve_record` dispatches record literals (qualified head →
  constructor path, bare head → single-segment ref, field values as ordinary
  value exprs).
- driver/load: `closure_diagnostics` runs the whole front-end (loader + N2
  effects + link + N3 bodies) over the project closure and attributes
  diagnostics per app-owned file, so the corpus harness reconciles cross-module
  and `.ckh`/`.ck` header refs. Adds the `ck_load` workspace dep.
- corpus: trap.md files re-reconciled now that closure resolution binds the
  refs that single-file resolution could not.

architecture: MAX_FILE_LOC 805 → 813 for bodies.rs (gained `resolve_record`),
ck_load added to ck_driver's expected dep graph.

Gates: cargo build + clippy --all-targets -D warnings + cargo test (43 suites)
all clean; corpus_apps_match_their_trap_md reconciles.
mdoorn manually merged commit 2ea49092a8 into master 2026-02-05 15:31:04 +01:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
3 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!31
No description provided.