Canonical comments v1
September 10, 2026 ยท View on GitHub
Status: implemented bounded profile; HOSTED GREEN under the v0.4.0 release baseline. Historical local, authoring-time, ignored, device/simulator, or separately provisioned evidence below retains its narrower scope; public promotion, registry publication and broader product completion remain separately gated.
Audience: language users, coding agents, and compiler contributors.
Purpose
Canonical formatting projected the program back to text from the syntax tree,
and the syntax tree never held comments, so semaprax fmt deleted every //
comment a person wrote. The agent quick reference had to warn that nothing
after // survives. This version keeps comments through formatting. It does
so by position rather than by changing the syntax tree: the lexer records
where each comment was, the formatter maps each position onto the item it
belongs to, and prints it there.
What is preserved
Every // comment of a file that parses is printed exactly once, with its text
after // kept verbatim except for trailing whitespace. Formatting is
idempotent: formatting a formatted file changes nothing, so a canonical file
with comments passes fmt --check. A file without comments formats to exactly
the bytes it formatted to before this version.
Where a comment goes
The formatter's items are module uses, declarations, record and class fields,
variant cases, resource lifecycles, class methods, block statements, and the
tail expression of every block. Items nest: a function's body is a sequence of
items inside the function; a while or unsafe body inside a statement.
- A comment before the
moduleline stays at the top of the file. - A comment on its own line before an item is printed above that item, above
its
@idattribute, indented to the item's depth. - A comment on the line directly after the previous item, or trailing a token on the same line, belongs to that previous item and is printed on its own line right after it. The blank line canonical formatting places between declarations therefore never separates a comment from the item it followed, and a same-line comment moves to the next line once, then stays.
- A comment inside an item but outside its body, such as one inside a signature, a contract line, or a single-line expression, is printed above the item.
- A comment after the last item of a block that is not on the line directly after that item is printed before the block's closing brace. One after the last declaration of the file is printed at the end of the file.
Because canonical formatting orders declarations by kind rather than by source position, a comment travels with the declaration it leads or trails, not with its line number.
Routes that preserve comments
fmt, for one file or for every source of a project directory or manifest,
restores comments as described above.
The single-file semantic patch routes (patch, patch-with-evidence, and
patch-with-evidence-v2) publish the patched file through the same
projection: the candidate text is the source with the patch's exact edits
applied, parsed with its comments, and rendered canonically, so every comment
of the file survives and the comment above a renamed declaration stays above
it. The graph revision and the candidate revision are computed from the syntax
tree as before and do not see comments; the evidence source digest binds the
exact source bytes, which already included the comments. A patched file is
canonical, so fmt --check accepts it.
Non-claims
Whether workspace-level transactions (workspace-apply, candidate and
generation publication) and Project rename transactions preserve comments is
not claimed by this version. Semantic Patch v3 identity assignment renders its
candidate without comments. The semantic graph, HIR, diagnostics, and every
backend ignore comments;
graph output is byte-identical with or without them. Comments are not
documentation attached to declarations, are not part of a declaration's
identity, and are not visible to context. Block comments (/* */) are not
part of the language.
Evidence
Unit cases in src/format/comments.rs pin the exact placement of header,
leading, trailing, in-signature, block-end, field, method, and loop-body
comments and the idempotence and comment-free-identity properties.
tests/projections/fmt_comments.rs exercises the CLI: fmt rewrites a
commented file to the pinned bytes, fmt --check accepts the result, a second
fmt changes nothing, graph of the commented and comment-free files is
byte-identical, and fmt <dir> formats every manifest source in manifest
order, names each drifting file under --check, and writes nothing when one
file does not parse. tests/semantic/patch.rs pins that a rename patch keeps
every comment of a commented file, keeps the renamed function's leading
comment above it, returns the comment-free revision, and leaves a file fmt --check accepts. The examples and documentation gates keep the comment-free
canonical form unchanged.