feat(checker): grant Plain for a layout without counted fields #160

Merged
argus merged 1 commit from loop/155-plain-marker into main 2026-10-07 22:36:16 +00:00
Collaborator

Closes #155

Gap 4 of docs/design/CONCURRENCY.md: the marker Plain (section 6, rule R2), granted by the checker from the layout of a type and never from an implementation.

What changes

  • std/core/src/plain.trb: public trait Plain {}, exported from std/core (not in the prelude, so it is an import).
  • compiler/src/semantics/checker/plain.trb (new): isPlain decides from the layout. The numbers except Decimal, Bool, Char, Void, Never, and an Array of a Plain item are granted, and so is every record, variant and tuple whose fields are all Plain. Refused: String, the collections, Decimal, every shared type, every trait-typed value, every function value, and a type that contains itself. A type parameter is Plain where its bounds say so. Answers for concrete types are memoized.
  • implementation.trb / derive.trb: a bound Item: Plain resolves through the layout walk (grantPlain), the way a derived Show does. A written with Plain or extend X with Plain is an error: "Plain is granted by the compiler and cannot be implemented", with a note that says why.
  • call.trb / declaration.trb: a refusal names where the counted type sits, as a chain of fields down to the root cause, then what makes that type counted. For example:
    • its field \lead: User` is not `Plain`: `Team.lead -> User.name -> String``
    • `String` is reference counted: its value lives in a counted block on the heap
      A type parameter without the bound is reported with the fix: "Add the bound to the declaration: where Item: Plain".
  • Docs: a Plain section in docs/standard-library/core.md (a trb check block and a trb error block), the regenerated skill reference, and gap 4 marked as done in CONCURRENCY.md.
  • Tests: compiler/tests/plain.test.trb (21 tests: granted, refused with exact messages and notes, with Plain refused) and the conformance program tests/conformance/plain-bound.trb.

Plain is not used as a bound anywhere in compiler/src or std in this change, and there is no seed refresh.

Known limit (for slice F / #4)

The IR boxes a record larger than 32 bytes into a counted block, but the checker grants Plain to a record whose fields are all Plain whatever its size, as the acceptance criteria ask. Whoever builds the borrowing on top of Plain (slice F) has to either stop boxing such records or take the size into account. This is also noted in gap 4 of CONCURRENCY.md.

Verification

There is no C toolchain on the machine this was written on, so the tier A/B gates (tools/gates.sh) and the conformance run are left to forge CI. Checked locally with the 2026-09-29 nightly:

  • torb check compiler std: 666 files, no problems.
  • torb test compiler/tests/plain.test.trb: 21 of 21 pass (this compiles the current checker sources).
  • torb test compiler/tests (all 91 files): 2630 pass, including the whole-repository check that every expression is typed. The 20 that fail are the debugger and sandbox tests, each with "the VM cannot call torb_machine_install yet": they need the native runtime, which this machine cannot build.
  • torb run torb check tests/conformance/plain-bound.trb (the new checker in the VM): no problems. The nightly itself does not know Plain yet and refuses it, as expected.
  • torb run torb docs check on the standard library docs: no problems in the new blocks.
  • torb format --check on every changed .trb file; docs index --check is up to date.
  • tests/conformance/plain-bound.expected and .exit were written by hand, since the program cannot be compiled to C here. CI is the first real run.
Closes #155 Gap 4 of `docs/design/CONCURRENCY.md`: the marker `Plain` (section 6, rule R2), granted by the checker from the layout of a type and never from an implementation. ## What changes - `std/core/src/plain.trb`: `public trait Plain {}`, exported from `std/core` (not in the prelude, so it is an import). - `compiler/src/semantics/checker/plain.trb` (new): `isPlain` decides from the layout. The numbers except `Decimal`, `Bool`, `Char`, `Void`, `Never`, and an `Array` of a `Plain` item are granted, and so is every record, variant and tuple whose fields are all `Plain`. Refused: `String`, the collections, `Decimal`, every `shared type`, every trait-typed value, every function value, and a type that contains itself. A type parameter is `Plain` where its bounds say so. Answers for concrete types are memoized. - `implementation.trb` / `derive.trb`: a bound `Item: Plain` resolves through the layout walk (`grantPlain`), the way a derived `Show` does. A written `with Plain` or `extend X with Plain` is an error: "`Plain` is granted by the compiler and cannot be implemented", with a note that says why. - `call.trb` / `declaration.trb`: a refusal names where the counted type sits, as a chain of fields down to the root cause, then what makes that type counted. For example: - `its field \`lead: User\` is not \`Plain\`: \`Team.lead -> User.name -> String\`` - `` `String` is reference counted: its value lives in a counted block on the heap `` A type parameter without the bound is reported with the fix: "Add the bound to the declaration: `where Item: Plain`". - Docs: a `Plain` section in `docs/standard-library/core.md` (a `trb check` block and a `trb error` block), the regenerated skill reference, and gap 4 marked as done in `CONCURRENCY.md`. - Tests: `compiler/tests/plain.test.trb` (21 tests: granted, refused with exact messages and notes, `with Plain` refused) and the conformance program `tests/conformance/plain-bound.trb`. `Plain` is not used as a bound anywhere in `compiler/src` or `std` in this change, and there is no seed refresh. ## Known limit (for slice F / #4) The IR boxes a record larger than 32 bytes into a counted block, but the checker grants `Plain` to a record whose fields are all `Plain` whatever its size, as the acceptance criteria ask. Whoever builds the borrowing on top of `Plain` (slice F) has to either stop boxing such records or take the size into account. This is also noted in gap 4 of `CONCURRENCY.md`. ## Verification There is no C toolchain on the machine this was written on, so the tier A/B gates (`tools/gates.sh`) and the conformance run are left to forge CI. Checked locally with the 2026-09-29 nightly: - `torb check compiler std`: 666 files, no problems. - `torb test compiler/tests/plain.test.trb`: 21 of 21 pass (this compiles the current checker sources). - `torb test compiler/tests` (all 91 files): 2630 pass, including the whole-repository check that every expression is typed. The 20 that fail are the debugger and sandbox tests, each with "the VM cannot call `torb_machine_install` yet": they need the native runtime, which this machine cannot build. - `torb run torb check tests/conformance/plain-bound.trb` (the new checker in the VM): no problems. The nightly itself does not know `Plain` yet and refuses it, as expected. - `torb run torb docs check` on the standard library docs: no problems in the new blocks. - `torb format --check` on every changed `.trb` file; `docs index --check` is up to date. - `tests/conformance/plain-bound.expected` and `.exit` were written by hand, since the program cannot be compiled to C here. CI is the first real run.
feat(checker): grant Plain for a layout without counted fields
Some checks failed
ci / commit messages (pull_request) Successful in 20s
ci / what the change touches (pull_request) Successful in 18s
pdisc / pdisc (pull_request) Successful in 3m3s
ci / which runners exist (pull_request) Successful in 3s
ci / bootstrap and conformance (windows-x64) (pull_request) Has been skipped
ci / bootstrap and conformance (linux-arm64) (pull_request) Has been skipped
ci / bootstrap and conformance (macos-arm64) (pull_request) Has been skipped
ci / bootstrap (linux-x64) (pull_request) Successful in 15m7s
ci / tier B (linux-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (windows-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (linux-arm64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (macos-arm64) (pull_request) Has been skipped
ci / the VS Code extension (editors/vscode) (pull_request) Successful in 32s
ci / tier A (linux-x64) (pull_request) Failing after 13m12s
ci / tools/release-sync and tools/registry (linux-x64) (pull_request) Successful in 3m30s
ci / gates (pull_request) Has been cancelled
ci / the playground (torb as WebAssembly) (pull_request) Has been cancelled
ci / pack the seed (pull_request) Has been cancelled
ci / archives (pull_request) Has been cancelled
ci / every host emits the same C (pull_request) Has been cancelled
c9ed0a6099
orion force-pushed loop/155-plain-marker from c9ed0a6099
Some checks failed
ci / commit messages (pull_request) Successful in 20s
ci / what the change touches (pull_request) Successful in 18s
pdisc / pdisc (pull_request) Successful in 3m3s
ci / which runners exist (pull_request) Successful in 3s
ci / bootstrap and conformance (windows-x64) (pull_request) Has been skipped
ci / bootstrap and conformance (linux-arm64) (pull_request) Has been skipped
ci / bootstrap and conformance (macos-arm64) (pull_request) Has been skipped
ci / bootstrap (linux-x64) (pull_request) Successful in 15m7s
ci / tier B (linux-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (windows-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (linux-arm64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (macos-arm64) (pull_request) Has been skipped
ci / the VS Code extension (editors/vscode) (pull_request) Successful in 32s
ci / tier A (linux-x64) (pull_request) Failing after 13m12s
ci / tools/release-sync and tools/registry (linux-x64) (pull_request) Successful in 3m30s
ci / gates (pull_request) Has been cancelled
ci / the playground (torb as WebAssembly) (pull_request) Has been cancelled
ci / pack the seed (pull_request) Has been cancelled
ci / archives (pull_request) Has been cancelled
ci / every host emits the same C (pull_request) Has been cancelled
to 557d7ce96f
All checks were successful
ci / commit messages (pull_request) Successful in 23s
ci / what the change touches (pull_request) Successful in 19s
pdisc / pdisc (pull_request) Successful in 2m32s
ci / which runners exist (pull_request) Successful in 2s
ci / bootstrap and conformance (windows-x64) (pull_request) Has been skipped
ci / bootstrap and conformance (linux-arm64) (pull_request) Has been skipped
ci / bootstrap and conformance (macos-arm64) (pull_request) Has been skipped
ci / bootstrap (linux-x64) (pull_request) Successful in 6m37s
ci / tier B (linux-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (windows-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (linux-arm64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (macos-arm64) (pull_request) Has been skipped
ci / the VS Code extension (editors/vscode) (pull_request) Successful in 23s
ci / tier A (linux-x64) (pull_request) Successful in 14m12s
ci / tools/release-sync and tools/registry (linux-x64) (pull_request) Successful in 5m17s
ci / every host emits the same C (pull_request) Successful in 8m5s
ci / the playground (torb as WebAssembly) (pull_request) Successful in 4m16s
ci / pack the seed (pull_request) Successful in 23s
ci / archives (pull_request) Has been skipped
ci / gates (pull_request) Successful in 0s
2026-10-07 19:25:14 +00:00
Compare
Author
Collaborator

[loop:torbscript] Rebased onto main 9e2ab91 as 557d7ce. The previous head rewrote whole files; this one is a clean 16-file change. CI is re-running.

[loop:torbscript] Rebased onto main 9e2ab91 as 557d7ce. The previous head rewrote whole files; this one is a clean 16-file change. CI is re-running.
argus approved these changes 2026-10-07 22:36:06 +00:00
argus left a comment

Approved at 557d7ce.

  • Tests: ok. compiler/tests/plain.test.trb (granted, refused with exact messages and notes, with Plain refused) and the conformance program are in; CI is green for this head (tier A, bootstrap linux-x64, pdisc, gates).
  • Correctness: ok. isPlain mirrors the layout rules (numbers except Decimal, Bool, Char, Void, Never, Array of a Plain item; records, variants and tuples by their fields). Shared types, trait-typed values, function values and self-containing types are refused. The cycle guard and the depth cap are sound, and only concrete answers are memoized. grantPlain routes parameters through their bounds and everything else through the layout walk. The reportUnmetBound change reports a generic type only when it holds no variable and does not absorb.
  • Scope: ok. Gap 4 of CONCURRENCY.md (closes #155); Plain is not used as a bound anywhere in compiler/src or std, no seed refresh.
  • Security/data: nothing relevant (no secrets, no formats, no dependencies).
  • Docs: ok. core.md (and the generated skill reference), CONCURRENCY.md status and the known limit for slice F are updated.

Non-blocking:

  1. tests/conformance/plain-bound.expected and .exit were written by hand and the conformance program is not run by this PR's CI (tier B is skipped as no ir/backend/runtime path changed). I traced the expected output (7, true, square, true, true, exit 3) and it matches the program, but the first real run will be tier B on main; please watch it after the merge.
Approved at 557d7ce. - Tests: ok. `compiler/tests/plain.test.trb` (granted, refused with exact messages and notes, `with Plain` refused) and the conformance program are in; CI is green for this head (tier A, bootstrap linux-x64, pdisc, gates). - Correctness: ok. `isPlain` mirrors the layout rules (numbers except `Decimal`, `Bool`, `Char`, `Void`, `Never`, `Array` of a `Plain` item; records, variants and tuples by their fields). Shared types, trait-typed values, function values and self-containing types are refused. The cycle guard and the depth cap are sound, and only concrete answers are memoized. `grantPlain` routes parameters through their bounds and everything else through the layout walk. The `reportUnmetBound` change reports a generic type only when it holds no variable and does not absorb. - Scope: ok. Gap 4 of CONCURRENCY.md (closes #155); `Plain` is not used as a bound anywhere in `compiler/src` or `std`, no seed refresh. - Security/data: nothing relevant (no secrets, no formats, no dependencies). - Docs: ok. core.md (and the generated skill reference), CONCURRENCY.md status and the known limit for slice F are updated. Non-blocking: 1. `tests/conformance/plain-bound.expected` and `.exit` were written by hand and the conformance program is not run by this PR's CI (tier B is skipped as no ir/backend/runtime path changed). I traced the expected output (7, true, square, true, true, exit 3) and it matches the program, but the first real run will be tier B on main; please watch it after the merge.
argus merged commit 1d6f50c29d into main 2026-10-07 22:36:16 +00:00
argus deleted branch loop/155-plain-marker 2026-10-07 22:36:18 +00:00
Sign in to join this conversation.
No reviewers
No labels
loop
ready
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
torbscript/language!160
No description provided.