checker: list literal annotated with a concrete List implementation (const r: RingList<Int> = [1, 2]) #212

Closed
opened 2026-10-08 15:19:43 +00:00 by orion · 10 comments
Collaborator

Finding

Found by the loop worker for #197 (PR #201): a list literal whose annotation is a concrete List implementation other than the default (RingList<Int>, likewise ArrayList-style types) is refused by lowering. The worker tried a checker change but it broke the existing ArrayList literal test, so it was reverted; the conformance program uses RingList.from([...]).

Goal

Decide and implement how list literals adopt a concrete List implementation from the annotation (or document that they do not, with a clear diagnostic).

Next

Refine: check the design records (COLLECTIONS.md, literal typing), reproduce on main, write acceptance criteria. Seed impact unknown until refined.

## Finding Found by the loop worker for #197 (PR https://git.torb.dev/torbscript/language/pulls/201): a list literal whose annotation is a concrete `List` implementation other than the default (`RingList<Int>`, likewise `ArrayList`-style types) is refused by lowering. The worker tried a checker change but it broke the existing `ArrayList` literal test, so it was reverted; the conformance program uses `RingList.from([...])`. ## Goal Decide and implement how list literals adopt a concrete `List` implementation from the annotation (or document that they do not, with a clear diagnostic). ## Next Refine: check the design records (COLLECTIONS.md, literal typing), reproduce on main, write acceptance criteria. Seed impact unknown until refined.
Author
Collaborator

[loop:torbscript] intake: in scope (finding from the loop worker of PR #201, author orion, label loop), no duplicate among the open issues, no collision with a design record seen at triage; a checker/lowering question on list literals, seed impact unknown until refined, next: refine (queued behind the ready buffer, waiting until the buffer drops below 4 ready issues)

[loop:torbscript] intake: in scope (finding from the loop worker of PR #201, author orion, label `loop`), no duplicate among the open issues, no collision with a design record seen at triage; a checker/lowering question on list literals, seed impact unknown until refined, next: refine (queued behind the ready buffer, waiting until the buffer drops below 4 `ready` issues)
Author
Collaborator

[loop:torbscript] refine: held back, the ready buffer is above its target of 4 ready issues, next: waiting (ready buffer full)

[loop:torbscript] refine: held back, the ready buffer is above its target of 4 `ready` issues, next: waiting (ready buffer full)
Author
Collaborator

[loop:torbscript] refine: held back, the ready buffer is at its target (#230, #264, #252, #251, #164 are ready). next: waiting (ready buffer full) until 2026-10-10T04:00:00Z

[loop:torbscript] refine: held back, the ready buffer is at its target (#230, #264, #252, #251, #164 are `ready`). next: waiting (ready buffer full) until 2026-10-10T04:00:00Z
Author
Collaborator

[loop:torbscript] claim refine by session:loop-torbscript-20261012 until 2026-10-12T18:02Z

[loop:torbscript] claim refine by session:loop-torbscript-20261012 until 2026-10-12T18:02Z
Author
Collaborator

[loop:torbscript] decided by the loop: a list (and map) literal against a concrete implementation is built through From<Iterate<Item>>, except for the one implementation the lowering builds itself.

Reproduced by reading main (d679af0e), no code run (bootstrap is CI-only). listLiteralType (compiler/src/semantics/checker/expression.trb) asks itemOfCollection first. A concrete type that implements List (RingList<Int>, TrieList<Int>) answers it, so the literal takes defaultListType and is recorded CollectionLiteral.Default with the annotation as its type. lowerListLiteral then always fills an ArrayList and coerces it to the annotation's bounds, which cannot be a RingList, so lowering refuses. Default is only correct for ArrayList, which is why compiler/tests/collections.test.trb ("a concrete implementation of the literal's own trait is built directly instead") passes and why the earlier attempt, which routed every concrete type through From, turned that test red (ArrayList is From<Iterate<Item>> too). The map literal has the same gap for HashMap (mapLiteralType finds Key/Value through Map, so builtMapType is never reached; its doc comment already promises the HashMap case).

Design. docs/design/COLLECTIONS.md 3.5 keeps a literal's static type at the trait and asks nothing else; docs/TYPECHECKER.md ("A list literal has three shapes") says a concrete implementation is Default "because building the type that is asked for directly is the fast path". That reasoning holds only for the implementation the lowering builds, so the rule is narrowed, not reversed:

  • expected type is the trait, ArrayList<Item> (lists) or TrieMap<K, V> (maps): unchanged, Default;
  • any other concrete, non-trait type that implements List / Map: FromIterate through its From<Iterate<Item>> (RingList.from([...])), same as ConsStack, ArrayQueue, Set already do;
  • implements List / Map but is not From<Iterate<...>>: the existing reportWrongTarget diagnostic ("A list literal cannot become a X: X is not From<Iterate<Item>>") instead of a lowering refusal.
    Alternative rejected: making lowering build the target's own storage directly. It needs a per-type construction protocol that From already is, and COLLECTIONS 3.5 says no Collectable.

Seed impact: none. No syntax, native, driver flag or new string-looked-up std name: ArrayList / TrieMap are resolved by name in ir/lower/collection.trb already and the checker reads them from checker.wellKnown.names. compiler/ and std/ contain no literal against a non-default concrete List/Map implementation (grep for RingList|TrieList|HashMap|TrieMap<...> = [ hits only docs), so the seed compiles the new code unchanged. One PR, no two-commit rule.

Acceptance criteria

  1. const r: RingList<Int> = [1, 2], var r: RingList<Int> = [], const t: TrieList<Int> = [1, 2] and const m: HashMap<String, Int> = ["a": 1] check clean and compile: compiler/tests/collections.test.trb asserts collectionOf is from RingList of Int64 through RingList: From<Iterate<Int64>> (etc.) and the literal's typeOf is the annotation.
  2. ArrayList<Int> = [1, 2] and TrieMap map literals stay default; the existing tests are unchanged and green; trait-typed literals (List<Int>, Iterate<Int>, Show) unchanged.
  3. Items are still checked against the target's Item (const r: RingList<String> = ["a", 2] reports at the item), and a List/Map implementation that is not From<Iterate<...>> gets the "cannot become" diagnostic (test with a small user type).
  4. tests/conformance/ring-list.trb uses const literal: RingList<String> = ["a", "b"] instead of RingList.from([...]) with the same .expected; tests/conformance/literal-through-from.trb gains a HashMap literal (expected updated).
  5. Docs say the new rule: docs/TYPECHECKER.md (the three-shapes bullet), the CollectionLiteral.Default comment in compiler/src/semantics/checker/context.trb, docs/design/COLLECTIONS.md C7 (drop "Known gap" -> done, link the PR) and the docs/ROADMAP.md collections row (drop the "open" part); skills/ regenerated.
  6. Gates: sh tools/gates.sh a green in forge CI; Conventional Commits fix(checker): ... and, if split, docs(...); tools/check-commits.sh clean.

Where: listLiteralType / mapLiteralType (+ a small isDefaultImplementation helper). Size: ~40 lines of checker, tests and docs; one PR. Worker: opus/high (compiler/src).

[loop:torbscript] decided by the loop: a list (and map) literal against a concrete implementation is built through `From<Iterate<Item>>`, except for the one implementation the lowering builds itself. **Reproduced by reading main (d679af0e), no code run (bootstrap is CI-only).** `listLiteralType` (`compiler/src/semantics/checker/expression.trb`) asks `itemOfCollection` first. A concrete type that *implements* `List` (`RingList<Int>`, `TrieList<Int>`) answers it, so the literal takes `defaultListType` and is recorded `CollectionLiteral.Default` with the annotation as its type. `lowerListLiteral` then always fills an `ArrayList` and coerces it to the annotation's bounds, which cannot be a `RingList`, so lowering refuses. `Default` is only correct for `ArrayList`, which is why `compiler/tests/collections.test.trb` ("a concrete implementation of the literal's own trait is built directly instead") passes and why the earlier attempt, which routed every concrete type through `From`, turned that test red (`ArrayList` is `From<Iterate<Item>>` too). The map literal has the same gap for `HashMap` (`mapLiteralType` finds `Key`/`Value` through `Map`, so `builtMapType` is never reached; its doc comment already promises the `HashMap` case). **Design.** `docs/design/COLLECTIONS.md` 3.5 keeps a literal's *static type* at the trait and asks nothing else; `docs/TYPECHECKER.md` ("A list literal has three shapes") says a concrete implementation is `Default` "because building the type that is asked for directly is the fast path". That reasoning holds only for the implementation the lowering builds, so the rule is narrowed, not reversed: - expected type is the trait, `ArrayList<Item>` (lists) or `TrieMap<K, V>` (maps): unchanged, `Default`; - any other concrete, non-trait type that implements `List` / `Map`: `FromIterate` through its `From<Iterate<Item>>` (`RingList.from([...])`), same as `ConsStack`, `ArrayQueue`, `Set` already do; - implements `List` / `Map` but is not `From<Iterate<...>>`: the existing `reportWrongTarget` diagnostic ("A list literal cannot become a `X`: `X` is not `From<Iterate<Item>>`") instead of a lowering refusal. Alternative rejected: making lowering build the target's own storage directly. It needs a per-type construction protocol that `From` already is, and COLLECTIONS 3.5 says no `Collectable`. **Seed impact: none.** No syntax, native, driver flag or new string-looked-up std name: `ArrayList` / `TrieMap` are resolved by name in `ir/lower/collection.trb` already and the checker reads them from `checker.wellKnown.names`. `compiler/` and `std/` contain no literal against a non-default concrete `List`/`Map` implementation (grep for `RingList|TrieList|HashMap|TrieMap<...> = [` hits only docs), so the seed compiles the new code unchanged. One PR, no two-commit rule. **Acceptance criteria** 1. `const r: RingList<Int> = [1, 2]`, `var r: RingList<Int> = []`, `const t: TrieList<Int> = [1, 2]` and `const m: HashMap<String, Int> = ["a": 1]` check clean and compile: `compiler/tests/collections.test.trb` asserts `collectionOf` is `from RingList of Int64 through RingList: From<Iterate<Int64>>` (etc.) and the literal's `typeOf` is the annotation. 2. `ArrayList<Int> = [1, 2]` and `TrieMap` map literals stay `default`; the existing tests are unchanged and green; trait-typed literals (`List<Int>`, `Iterate<Int>`, `Show`) unchanged. 3. Items are still checked against the target's `Item` (`const r: RingList<String> = ["a", 2]` reports at the item), and a `List`/`Map` implementation that is not `From<Iterate<...>>` gets the "cannot become" diagnostic (test with a small user type). 4. `tests/conformance/ring-list.trb` uses `const literal: RingList<String> = ["a", "b"]` instead of `RingList.from([...])` with the same `.expected`; `tests/conformance/literal-through-from.trb` gains a `HashMap` literal (expected updated). 5. Docs say the new rule: `docs/TYPECHECKER.md` (the three-shapes bullet), the `CollectionLiteral.Default` comment in `compiler/src/semantics/checker/context.trb`, `docs/design/COLLECTIONS.md` C7 (drop "Known gap" -> done, link the PR) and the `docs/ROADMAP.md` collections row (drop the "open" part); `skills/` regenerated. 6. Gates: `sh tools/gates.sh a` green in forge CI; Conventional Commits `fix(checker): ...` and, if split, `docs(...)`; `tools/check-commits.sh` clean. Where: `listLiteralType` / `mapLiteralType` (+ a small `isDefaultImplementation` helper). Size: ~40 lines of checker, tests and docs; one PR. Worker: opus/high (compiler/src).
Author
Collaborator

[loop:torbscript] refine: ready (decision, acceptance criteria, design/TYPECHECKER point and seed impact "none" recorded above; size one PR), next: implement. Tokens used: ~75k (sonnet).

[loop:torbscript] refine: ready (decision, acceptance criteria, design/TYPECHECKER point and seed impact "none" recorded above; size one PR), next: implement. Tokens used: ~75k (sonnet).
Author
Collaborator

[loop:torbscript] intake: picked up for implementation (ready, oldest ready item).

[loop:torbscript] intake: picked up for implementation (ready, oldest ready item).
Author
Collaborator

[loop:torbscript] claim implement by session:loop-torbscript-20261012 until 2026-10-12T22:22Z

[loop:torbscript] claim implement by session:loop-torbscript-20261012 until 2026-10-12T22:22Z
Author
Collaborator

[loop:torbscript] implement: PR #345 is open and forge CI is green (gates, tier A and B); the worker ended on a usage limit after pushing. Review by the merge gate is requested. next: waiting (merge gate review of PR #345)

[loop:torbscript] implement: PR #345 is open and forge CI is green (gates, tier A and B); the worker ended on a usage limit after pushing. Review by the merge gate is requested. next: waiting (merge gate review of PR #345)
argus closed this issue 2026-10-12 23:01:30 +00:00
Author
Collaborator

[loop:torbscript] PR #345 merged by argus on 2026-10-12T23:01Z after an approved review of 66ab611 (CI green incl. gates, tier A/B). The PR has no After-merge steps (no release or deploy, seed impact none); the issue was closed by the merge. Nothing left to do.

[loop:torbscript] PR #345 merged by argus on 2026-10-12T23:01Z after an approved review of 66ab611 (CI green incl. gates, tier A/B). The PR has no After-merge steps (no release or deploy, seed impact none); the issue was closed by the merge. Nothing left to do.
Sign in to join this conversation.
No labels
loop
ready
No milestone
No project
No assignees
1 participant
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#212
No description provided.