feat(checker): grant Plain for a layout without counted fields #160
Loading…
Reference in a new issue
No description provided.
Delete branch "loop/155-plain-marker"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #155
Gap 4 of
docs/design/CONCURRENCY.md: the markerPlain(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 fromstd/core(not in the prelude, so it is an import).compiler/src/semantics/checker/plain.trb(new):isPlaindecides from the layout. The numbers exceptDecimal,Bool,Char,Void,Never, and anArrayof aPlainitem are granted, and so is every record, variant and tuple whose fields are allPlain. Refused:String, the collections,Decimal, everyshared type, every trait-typed value, every function value, and a type that contains itself. A type parameter isPlainwhere its bounds say so. Answers for concrete types are memoized.implementation.trb/derive.trb: a boundItem: Plainresolves through the layout walk (grantPlain), the way a derivedShowdoes. A writtenwith Plainorextend X with Plainis an error: "Plainis 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 heapA type parameter without the bound is reported with the fix: "Add the bound to the declaration:
where Item: Plain".Plainsection indocs/standard-library/core.md(atrb checkblock and atrb errorblock), the regenerated skill reference, and gap 4 marked as done inCONCURRENCY.md.compiler/tests/plain.test.trb(21 tests: granted, refused with exact messages and notes,with Plainrefused) and the conformance programtests/conformance/plain-bound.trb.Plainis not used as a bound anywhere incompiler/srcorstdin 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
Plainto a record whose fields are allPlainwhatever its size, as the acceptance criteria ask. Whoever builds the borrowing on top ofPlain(slice F) has to either stop boxing such records or take the size into account. This is also noted in gap 4 ofCONCURRENCY.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 calltorb_machine_installyet": 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 knowPlainyet and refuses it, as expected.torb run torb docs checkon the standard library docs: no problems in the new blocks.torb format --checkon every changed.trbfile;docs index --checkis up to date.tests/conformance/plain-bound.expectedand.exitwere written by hand, since the program cannot be compiled to C here. CI is the first real run.Plainmarker (CONCURRENCY gap 4, part of slice F) #155c9ed0a6099557d7ce96f[loop:torbscript] Rebased onto main
9e2ab91as557d7ce. The previous head rewrote whole files; this one is a clean 16-file change. CI is re-running.Approved at
557d7ce.compiler/tests/plain.test.trb(granted, refused with exact messages and notes,with Plainrefused) and the conformance program are in; CI is green for this head (tier A, bootstrap linux-x64, pdisc, gates).isPlainmirrors the layout rules (numbers exceptDecimal,Bool,Char,Void,Never,Arrayof aPlainitem; 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.grantPlainroutes parameters through their bounds and everything else through the layout walk. ThereportUnmetBoundchange reports a generic type only when it holds no variable and does not absorb.Plainis not used as a bound anywhere incompiler/srcorstd, no seed refresh.Non-blocking:
tests/conformance/plain-bound.expectedand.exitwere 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.