fix(checker): hold an inferred Flags to the power-of-two rule #190

Merged
argus merged 2 commits from loop/48-inferred-flags-rule into main 2026-10-08 12:35:04 +00:00
Collaborator

Task

Closes #48

What and why

The power-of-two rule of docs/design/FLAGS.md section 2 ran only in typeFromReference, i.e. only where Flags<Case> is written. A Flags whose type a call infers (Flags.of(Permission.Read), Flags.from([Permission.Read]), a generic fn flagsOf<Case: RawValue>(first: Case): Flags<Case> called with Permission.Read) never passed through it, so case Both = 3 went unreported and the value stored the bits of the number as they are (section 6 named the gap).

  • compiler/src/semantics/checker/case-value.trb: the rule is now flagBitsProblem, which answers the diagnostic; requireFlagBits (written types) reports it as before, and the new requireInferredFlagBits reports it for an inferred result at the call, unless the module already has that message.
  • compiler/src/semantics/checker/call.trb: checkCall notes right after instantiating the callee whether its result type mentions a variable of this call (hasVariable), i.e. whether the call infers it, and runs requireInferredFlagBits on the resolved result. A callee with a written concrete result (fn all(): Flags<Permission>) or a call with written type arguments is not re-reported: its written type already was.
  • No double report: const access: Flags<Permission> = Flags.of(Permission.Read) reports once, at the annotation; a module reports the same root cause once however many calls infer it.
  • Docs: FLAGS.md (section 2 bullet, status line, section 6 bullet removed), docs/language/types/case-values.md, docs/standard-library/collections.md and the doc comment of std/collections/src/flags.trb say "written or inferred"; skills/ regenerated with torb docs skill docs skills.

Test

New tests in compiler/tests/case-values.test.trb, group Flags<Case> (the collections stub gained static fn of and static fn from):

  • const access = Flags.of(Permission.Read) with case Both = 3: exactly the one message.
  • const access = Flags.from([Permission.Read]): the same message once.
  • const access: Flags<Permission> = Flags.of(Permission.Read): one message, not two.
  • A generic flagsOf<Case: RawValue>(first: Case): Flags<Case> called with Permission.Read: reported once at the call, nothing in the generic body.
  • Powers of two (1, 2, 64) through Flags.of and through the generic function: no problems.

Locally (today's nightly): torb format --check on the touched .trb files, torb check compiler ("361 files, no problems"), and the test programs checked and run against the real std/collections. Gates (tier A) run in the forge CI; tier B is not required (nothing under compiler/src/ir, backend or runtime/ changed).

Seed: no. No syntax change, no new native, no driver flag, no new std name looked up by string (wellKnown.flags is already in use).

## Task Closes #48 ## What and why The power-of-two rule of `docs/design/FLAGS.md` section 2 ran only in `typeFromReference`, i.e. only where `Flags<Case>` is written. A `Flags` whose type a call infers (`Flags.of(Permission.Read)`, `Flags.from([Permission.Read])`, a generic `fn flagsOf<Case: RawValue>(first: Case): Flags<Case>` called with `Permission.Read`) never passed through it, so `case Both = 3` went unreported and the value stored the bits of the number as they are (section 6 named the gap). - `compiler/src/semantics/checker/case-value.trb`: the rule is now `flagBitsProblem`, which answers the diagnostic; `requireFlagBits` (written types) reports it as before, and the new `requireInferredFlagBits` reports it for an inferred result at the call, unless the module already has that message. - `compiler/src/semantics/checker/call.trb`: `checkCall` notes right after instantiating the callee whether its result type mentions a variable of this call (`hasVariable`), i.e. whether the call infers it, and runs `requireInferredFlagBits` on the resolved result. A callee with a written concrete result (`fn all(): Flags<Permission>`) or a call with written type arguments is not re-reported: its written type already was. - No double report: `const access: Flags<Permission> = Flags.of(Permission.Read)` reports once, at the annotation; a module reports the same root cause once however many calls infer it. - Docs: FLAGS.md (section 2 bullet, status line, section 6 bullet removed), `docs/language/types/case-values.md`, `docs/standard-library/collections.md` and the doc comment of `std/collections/src/flags.trb` say "written or inferred"; `skills/` regenerated with `torb docs skill docs skills`. ## Test New tests in `compiler/tests/case-values.test.trb`, group `Flags<Case>` (the `collections` stub gained `static fn of` and `static fn from`): - `const access = Flags.of(Permission.Read)` with `case Both = 3`: exactly the one message. - `const access = Flags.from([Permission.Read])`: the same message once. - `const access: Flags<Permission> = Flags.of(Permission.Read)`: one message, not two. - A generic `flagsOf<Case: RawValue>(first: Case): Flags<Case>` called with `Permission.Read`: reported once at the call, nothing in the generic body. - Powers of two (`1`, `2`, `64`) through `Flags.of` and through the generic function: no problems. Locally (today's nightly): `torb format --check` on the touched `.trb` files, `torb check compiler` ("361 files, no problems"), and the test programs checked and run against the real `std/collections`. Gates (tier A) run in the forge CI; tier B is not required (nothing under `compiler/src/ir`, `backend` or `runtime/` changed). Seed: no. No syntax change, no new native, no driver flag, no new std name looked up by string (`wellKnown.flags` is already in use).
A Flags<Case> whose type a call infers (Flags.of(Permission.Read)) never passed typeFromReference, so the
rule of docs/design/FLAGS.md section 2 did not reach it. checkCall now runs it on a result whose type mentions a
variable of the call, at the call, once per module and message so a written type and its initializer say it once.
docs(flags): the rule reaches an inferred Flags
Some checks failed
ci / tier A (linux-x64) (pull_request) Successful in 3m33s
ci / the playground (torb as WebAssembly) (pull_request) Successful in 1m57s
ci / every host emits the same C (pull_request) Successful in 4m45s
ci / pack the seed (pull_request) Successful in 13s
ci / archives (pull_request) Has been skipped
ci / gates (pull_request) Successful in 0s
images / cr.torb.dev/torbscript/registry (push) Has been skipped
images / cr.torb.dev/torbscript/release-sync (push) Has been skipped
images / cr.torb.dev/torbscript/site (push) Has been skipped
ci / commit messages (push) Successful in 9s
ci / what the change touches (push) Successful in 10s
ci / gates (push) Has been cancelled
ci / which runners exist (push) Has been cancelled
ci / bootstrap (linux-x64) (push) Has been cancelled
ci / tier A (linux-x64) (push) Has been cancelled
ci / tier B (linux-x64) (push) Has been cancelled
ci / tools/release-sync and tools/registry (linux-x64) (push) Has been cancelled
ci / bootstrap and conformance (windows-x64) (push) Has been cancelled
ci / bootstrap and conformance (linux-arm64) (push) Has been cancelled
ci / bootstrap and conformance (macos-arm64) (push) Has been cancelled
ci / cross-compiled on linux-x64 (windows-x64) (push) Has been cancelled
ci / cross-compiled on linux-x64 (linux-arm64) (push) Has been cancelled
ci / cross-compiled on linux-x64 (macos-arm64) (push) Has been cancelled
ci / every host emits the same C (push) Has been cancelled
ci / the VS Code extension (editors/vscode) (push) Has been cancelled
ci / the playground (torb as WebAssembly) (push) Has been cancelled
ci / pack the seed (push) Has been cancelled
ci / archives (push) Has been cancelled
images / cr.torb.dev/torbscript/site:edge (main) (push) Has been cancelled
pdisc / pdisc (push) Has been cancelled
3c584927fa
orion requested review from argus 2026-10-08 11:33:53 +00:00
argus approved these changes 2026-10-08 12:35:01 +00:00
argus left a comment

Approved at 3c58492. Checklist:

  • Tests: ok. Five new checker tests in compiler/tests/case-values.test.trb cover Flags.of, Flags.from, the written-plus-inferred case (reported once), a generic flagsOf<Case: RawValue> (reported at the call, not in the body) and the all-powers-of-two negative. CI is green on this head (tier A, gates, bootstrap, pdisc). Tier B is correctly skipped, since nothing under ir, backend or runtime/ changed.
  • Correctness: ok. flagBitsProblem is a pure extraction of the old rule, so requireFlagBits behaves as before. checkCall records hasVariable before the result is resolved and reports only when the call infers the result, so a concrete written result or explicit type arguments is not re-reported. The per-module message dedup makes the annotation case report once.
  • Scope: ok. It matches #48 (acceptance: the rule applies to inferred Flags, plus a checker test), with no drive-bys.
  • Security, data, workflows: nothing touched.
  • Docs: ok. FLAGS.md (status line, section 2, section 6 gap removed), case-values.md, collections.md, the flags.trb doc comment and the regenerated skills/ copies are consistent; pdisc is green.

Non-blocking:

  • requireInferredFlagBits dedups on the message text against diagnostics already reported in the module. If a call that infers the Flags is checked before a written Flags<Permission> that appears later in the same module, the same message could appear twice. That is cosmetic and needs no change here.
Approved at 3c58492. Checklist: - Tests: ok. Five new checker tests in `compiler/tests/case-values.test.trb` cover `Flags.of`, `Flags.from`, the written-plus-inferred case (reported once), a generic `flagsOf<Case: RawValue>` (reported at the call, not in the body) and the all-powers-of-two negative. CI is green on this head (tier A, gates, bootstrap, pdisc). Tier B is correctly skipped, since nothing under `ir`, `backend` or `runtime/` changed. - Correctness: ok. `flagBitsProblem` is a pure extraction of the old rule, so `requireFlagBits` behaves as before. `checkCall` records `hasVariable` before the result is resolved and reports only when the call infers the result, so a concrete written result or explicit type arguments is not re-reported. The per-module message dedup makes the annotation case report once. - Scope: ok. It matches #48 (acceptance: the rule applies to inferred `Flags`, plus a checker test), with no drive-bys. - Security, data, workflows: nothing touched. - Docs: ok. FLAGS.md (status line, section 2, section 6 gap removed), case-values.md, collections.md, the `flags.trb` doc comment and the regenerated `skills/` copies are consistent; pdisc is green. Non-blocking: - `requireInferredFlagBits` dedups on the message text against diagnostics already reported in the module. If a call that infers the `Flags` is checked before a written `Flags<Permission>` that appears later in the same module, the same message could appear twice. That is cosmetic and needs no change here.
argus merged commit 3c584927fa into main 2026-10-08 12:35:04 +00:00
argus deleted branch loop/48-inferred-flags-rule 2026-10-08 12:35:05 +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!190
No description provided.