Skip to content

v2c §90's conditional quoting is lossy wherever BareWord does symbol-table fallback #79

Description

@rmichaelthomas

Found while fixing #77 (§82's escape hatch surviving canonical rendering). That one is closed. This is the same class of defect in a second position, and here it changes a value rather than a node kind.

Reproduction

remember a number called total with 75
remember a string called label with "total"
show label
output
as written total
after canonical rendering 75

The middle line renders canonically as remember a string called label with total. On re-parse the bare word is a BareWord, which does symbol-table fallback; total is in scope as a number, so the string literal becomes the number 75.

Why the §77 fix does not cover it

QuotedString and BareWord are different nodes with different evaluation semantics — BareWord falls back to the symbol table, QuotedString never does. §90 treats them as interchangeable spellings of the same value, which is true right up until a name in scope makes the bare form resolve.

§77's fix was narrow on purpose: the parser holds the predicate table, so it can mark that one collision and the renderer honours the mark. The symbol table is not available at parse time, so the same trick does not reach this case.

Why this is a decision, not a patch

Unlike §82-vs-§90, this is not a gap between two rules that never saw each other. §90 says single-word quotes drop by design, and three tests assert it by name:

  • tests/test_assign.py::test_render_round_trip_single_word_quote_drops
  • tests/test_integration_includes_remove.py::test_remove_renders_canonically
  • tests/test_renderer.py::test_render_quoted_string_with_uppercase_keeps_quotes

Closing it means reversing a locked decision about what canonical form is, and canonical renderings are a stored contract surface (Receipts, and any consumer that has persisted one).

Measured cost

Making QuotedString always render with quotes: 6 failures of 1899. Three are the genuine spec assertions above; three are regeneration artifacts (grammar/, the conformance fixture).

Options

  1. Reverse §90 for QuotedString — always quote. One line, six test updates, and every stored canonical form containing a quoted single word now differs from what the renderer would produce today.
  2. Mark the collision at analysis time — the analyzer holds the symbol table, so it could set the same shadows_a_predicate-style flag §77 introduced (call it what it is: shadows a name in scope). Preserves §90, closes the hole, but only for pipelines that analyze before rendering — and render() is called without analyze() in several places.
  3. Leave it and document the sharp edge — the collision requires a quoted string whose content exactly matches a name in scope, which is rare in practice.

My read: (2) is the right shape and matches the fix that just landed, but its coverage gap (render-without-analyze) is real and needs closing first or the fix is conditional. (1) is honest and complete but is a migration.

Not urgent

No known program hits it. Raised because it was found, measured, and would otherwise be re-found.

🤖 Generated with Claude Code

https://claude.ai/code/session_01E6qcDj1e1dEYvc8jLnpGZy

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions