Skip to content

feat(settings): carry blocks under catalogued keys as written - #413

Merged
e54-bot merged 2 commits into
mainfrom
wt-412-carried-blocks
Oct 9, 2026
Merged

e54-bot merged 2 commits into
mainfrom
wt-412-carried-blocks

Conversation

@e54-bot

@e54-bot e54-bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Carries a braced settings block under a catalogued non-list key as an opaque block, closing the canonical gap reported in #412:

  • Parser: Map Rotation { a } under lobby now parses to a SettingsNode::Group under the canonical mapRotation key instead of failing with only settings lists may use braces. ListMap/ListHero keep their typed-list meaning.
  • Emitter: a Group at a catalogued path emits <localized display name> { <opaque children> } — same block shape as uncatalogued keys, resolved name.
  • Check: check_emission/validate accept the group; its leaves are checked as opaque members, and it is reported through uncatalogued_members (a ProjectDefinedConstruct residual, no suggestion).

Motivation: the pinned OverPy oracle writes Map Rotation { a } for "lobby": {"mapRotation": ["a"]} (opy-rs#496) — an undeclared value under a translated catalogued key. Without this, neither opy-rs emission nor raw parse-back can represent the shape.

Verification

  • cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings — clean
  • cargo test --workspace --all-targets — all pass (281 lib + 166 integration + 30 cli)
  • cargo run -p workshop-rs --bin workshop-catalog-gen -- check — clean
  • New tests: issue_412_catalogued_key_block_round_trips_as_written (parse → validate → emit → re-parse parity, residual classification) and issue_412_catalogued_key_block_emits_the_display_name (programmatic tree, zh-CN localization)
  • Scalar-kind mismatches (e.g. Number under a Flag key) still reject, covered by existing tests

A braced block under a catalogued non-list settings key carries an undeclared value the same way an uncatalogued-key block does: the parser produces an opaque Group under the canonical key, the emitter writes it under the localized display name, and check accepts it with opaque leaf checking. The pinned source compilers emit this shape (opy-rs#496), so emission gains nothing the parser cannot read back.

Fixes #412

@e54-bot e54-bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the diff against issue #412 and verified end-to-end via the CLI in this worktree.

Verified correct

  • Parse: Map Rotation { a } under lobby produces Group{mapRotation} with opaque children; canonical name comes from entry.path.last(), cursor past { is correct. Verified for Enum (lobby.mapRotation), Flag (extensions.beamEffects), exact-path mode key (gamemodes.assault.roleLimit), and hero-context key (heroes.<team>.<hero>.health% under Mei emits the hero label).
  • Emit/check parity at every member position: walk_* dispatch mirrors emit_*; (Group, _) carried with opaque leaf checking at empty path on both sides.
  • uncatalogued_members reports the group once → ProjectDefinedConstruct, no suggestion — correct since the key itself is declared.
  • Localization: zh-CN emits 地图轮换 { a b }; member_display_name is used identically to typed members.
  • Round-trip is a fixed point (emitted output reparses and re-emits identically — verified via emit | emit).
  • Scalar-kind mismatch rejection unchanged (member::accept untouched; covered by existing tests).
  • Boundary check: under heroes.<team>, a catalogued-key block parses to a Group but check/emit reject it as unknown hero 'health%' — identical to uncatalogued groups at that path (positional hero-group dispatch predates this PR), so contract is internally consistent there.

Findings

  1. (minor, docs) docs/language-support/settings.md "Members outside the catalog" (~L131-148) enumerates the carried shapes — unknown-key leaf → Raw, unknown-key block → Group, catalogued key + undeclared scalar → RawValue — but omits the new shape: a braced block under a catalogued non-list key now parses to Group under the canonical key and emits via the localized display name. The section currently implies only unknown keys produce Groups. Repo AGENTS.md requires the owning durable doc to update in the same PR for supported-behavior changes. (Related: check.rs module doc L5-8 says "Members the catalog does not declare are carried as written" — now also catalogued-key blocks.)

  2. (minor, test) issue_412_catalogued_key_block_round_trips_as_written (tests/settings_pipeline.rs:1606) is named for round-trip and the issue acceptance explicitly requires "emitted Map Rotation { a } text parses, validates, and re-emits identically", but the test never reparses emitted — assert_check_compile_parity(source) reparses the source. Add parser::parse(&emitted) + validate/re-emit equality (or equivalent) to cover the criterion.

  3. (minor) parser.rs:276-279 — the _ => "only settings lists may use braces" arm inside the list-element match is now unreachable: the new early return guarantees entry.kind ∈ {ListMap, ListHero} there. Harmless (a catch-all is needed for exhaustiveness), but the dead error may mislead; consider unreachable! or restructuring so the code reflects that only list kinds reach this loop.

Verdict: findings

Address PR review findings: document the new carried shape in the
settings language-support page and the check module doc, assert the
emitted text reparses, validates, and re-emits identically in the #412
round-trip test, and mark the non-list brace arm unreachable.

Refs #412

@e54-bot e54-bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review of bd0633b against the three prior findings.

Prior findings — all resolved

  1. Docs: docs/language-support/settings.md:141-143 adds the catalogued-non-list-key block shape (SettingsNode::Group under the canonical key, emitted via localized display name, contents as written) — verified accurate against parser.rs:236-238 and emitter.rs:237-242. check.rs module doc L5-7 now names both block positions. Documentation impact satisfied in-PR per repo AGENTS.md.
  2. Test: issue_412_catalogued_key_block_round_trips_as_written now reparses emitted, validates, and asserts re-emit equality — the literal acceptance criterion. The chain also discriminates correctly: mapRotation is KeyKind::Enum, so a mis-parse to List would fail validate().
  3. parser.rs:276 unreachable! is sound: entry is an immutable &TableEntry bound before the LBrace branch, and the early return at :236-239 guarantees entry.kind is ListMap/ListHero at the elements loop. No input can reach it.

New-diff check (bd0633b): no regressions — doc text matches behavior, reparse assertions exercise the criterion, removed error string has no remaining references. Independently re-verified: check/emit (Group, _) symmetry with opaque leaf checking at empty path, carried_suggestion returning None for (Group, Some) (declared key, not a misspelling), and team-level hero-group dispatch identical for catalogued and uncatalogued blocks (pre-existing positional behavior).

Verification: cargo test -p workshop-rs — 281 lib + 166 integration tests, 0 failures; fmt/clippy/git diff --check clean.

LGTM — no findings.

@e54-bot
e54-bot merged commit e0e333f into main Oct 9, 2026
5 checks passed
@e54-bot
e54-bot deleted the wt-412-carried-blocks branch October 9, 2026 19:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants