format, pointers, bugc: specify and implement two-sorted (integer/bytes) expression evaluation - #286
Merged
Merged
Conversation
Contributor
|
The width of an expression's value was undefined for numeric literals, arithmetic results, and odd-digit hex, yet $concat and $keccak256 depend on operand widths, so a compiler and a debugger could disagree on the bytes produced. Expressions now evaluate to one of two sorts: an unbounded non-negative integer with no width, or bytes with a definite width. JSON numbers, $wordsize, variables, lookups, arithmetic, and odd-digit hex are integers; even-digit hex, $read, the resize forms, $keccak256, and $concat are bytes. Where an integer is expected a bytes value is read big-endian; where bytes are expected ($concat and $keccak256 operands) a bare integer is an error, and the resize forms are the only bridge. The examples that hashed bare integers are corrected to word-size their operands.
The evaluator used one width-carrying representation for everything and gave a JSON-number literal its minimal width, so literal 0 was zero bytes and a $keccak256 over a bare slot hashed the wrong preimage. evaluate() now returns a tagged Value, integer or bytes, with the sort rules the schema states; arithmetic coerces operands to integers; $concat and $keccak256 throw on a bare-integer operand with a message naming it and suggesting $wordsized or $sizedN. Variables carry the sort of their defining expression. The change is internal: evaluate is not exported from the package index and Cursor.Region is unchanged. The previously skipped keccak test is reinstated, every sort rule and coercion direction is tested, and a new test evaluates every $keccak256 and $concat occurrence in the pointer schema examples so an example without widths fails the suite. The implementation guide that lists the evaluator's functions by name is updated to the new shape, since its code listings would otherwise break the site build.
The slot-computation helpers emitted $keccak256 with a bare slot operand, which the two-sorted semantics reject. They now word-size it, passing a nested $keccak256 through since it is already 32-byte bytes. These helpers have no production callers yet, so emitted debug info was already valid; the helpers and their pinned tests are corrected so that any future use is too.
The spec page said values are "assumed to be left-padded to the bytes width appropriate for the context", which the schema now contradicts. It states the two sorts, the even/odd hex rule, and the width-bearing requirement, and each form's prose names its result sort. The expressions guide gains an "Integers and bytes" section and word-sizes the operands in its hashing examples.
gnidan
force-pushed
the
architect-expression-semantics
branch
from
September 17, 2026 01:43
9a9c742 to
a915671
Compare
gnidan
added a commit
that referenced
this pull request
Sep 17, 2026
CHANGELOG.md at the repository root records changes to the schemas under schemas/. The schemas ship inside @ethdebug/format, so the file is keyed by that package's version. Each entry gives a summary and the pull request, then three sub-items: the schemas the change touches, what it means for producers, and what it means for consumers. The producer and consumer lines each start with one of three fixed prefixes: - "no change needed." when nothing valid becomes invalid or changes meaning for that party - "optional:" when the change adds a capability that obliges nobody - "required:" when earlier valid output stops validating, the specification adds a must, or the meaning of valid data changes; the line names the keyword or prose that imposes the obligation The file has an entry for every pull request that changed schemas/ between the 0.1.0-0 publication and the 0.1.0-1 tag, each checked against the schema diff of its pull request, and an Unreleased section for the changes merged since (#284, #286).
gnidan
added a commit
that referenced
this pull request
Sep 17, 2026
CHANGELOG.md at the repository root records changes to the schemas under schemas/. The schemas ship inside @ethdebug/format, so the file is keyed by that package's version. Each entry gives a summary and the pull request, then three sub-items: the schemas the change touches, what it means for producers, and what it means for consumers. The producer and consumer lines each start with one of three fixed prefixes: - "no change needed." when nothing valid becomes invalid or changes meaning for that party - "optional:" when the change adds a capability that obliges nobody - "required:" when earlier valid output stops validating, the specification adds a must, or the meaning of valid data changes; the line names the keyword or prose that imposes the obligation An impact line states the net effect for a party that moves from the previous published version to the version of its section. A change inside a schema that is new in that version obliges nobody, and an obligation that a later change in the same version reverses does not appear. Sections are only Added and Changed; they do not signal obligations, the prefixes do. The file has an entry for every pull request that changed schemas/ between the 0.1.0-0 publication and the 0.1.0-1 tag, each checked against the schema trees of those two versions, and an Unreleased section for the changes merged since (#284, #286).
gnidan
added a commit
that referenced
this pull request
Sep 17, 2026
* docs: add a changelog for the specification CHANGELOG.md at the repository root records changes to the schemas under schemas/. The schemas ship inside @ethdebug/format, so the file is keyed by that package's version. Each entry gives a summary and the pull request, then three sub-items: the schemas the change touches, what it means for producers, and what it means for consumers. The producer and consumer lines each start with one of three fixed prefixes: - "no change needed." when nothing valid becomes invalid or changes meaning for that party - "optional:" when the change adds a capability that obliges nobody - "required:" when earlier valid output stops validating, the specification adds a must, or the meaning of valid data changes; the line names the keyword or prose that imposes the obligation An impact line states the net effect for a party that moves from the previous published version to the version of its section. A change inside a schema that is new in that version obliges nobody, and an obligation that a later change in the same version reverses does not appear. Sections are only Added and Changed; they do not signal obligations, the prefixes do. The file has an entry for every pull request that changed schemas/ between the 0.1.0-0 publication and the 0.1.0-1 tag, each checked against the schema trees of those two versions, and an Unreleased section for the changes merged since (#284, #286). * web: show the specification changelog on the docs site The page at /spec/changelog imports the root CHANGELOG.md, so the site and the repository always show the same text. The file must stay valid MDX: no HTML comments, and no bare "<" or "{" outside code. * docs: add a changelog to each published package Each of the seven public packages gets a CHANGELOG.md keyed by its own version, because the packages are versioned independently. Entries describe changes that a user of the package can see. @ethdebug/format and @ethdebug/pointers were on npm at 0.1.0-0, so their files carry the history from that publication to 0.1.0-1, reconstructed from a comparison of the published tarballs. The other five packages were first published at 0.1.0-1. * packages: ship CHANGELOG.md in the published tarballs npm always includes package.json, README* and LICENSE* in a tarball, but not CHANGELOG.md. Each public package now lists the file in "files", and the tarball allow-list in bin/packlist.ts accepts CHANGELOG* so that bin/check-tarballs.ts does not reject it. * ci: require a changelog entry for schema and package changes bin/check-changelog.ts compares a pull request with its base branch. It fails when the diff changes schemas/ without the root CHANGELOG.md, or changes the src/, bin/ or package.json of a public package without that package's CHANGELOG.md. Colocated test files do not count. The label "changelog: skip" exempts a pull request. The job has its own workflow that also runs on "labeled" and "unlabeled" events: Actions cannot filter those events by label name, so in ci.yml each label change would run the whole suite again. * bin: check the format of the specification changelog Each "Producers:" and "Consumers:" sub-item in the root CHANGELOG.md must start with "no change needed.", "optional:", or "required:", and each section heading must be "Added" or "Changed". The check reads the file on every run, whether or not the diff changes schemas/, and reports each problem with its line number. The bullets in the intro that describe the sub-items are not sub-items, and the check ignores them. * docs: add the changelog cut to the release steps Before the version bump, the operator renames "## Unreleased" to the new version and date in the root file and in the file of each package that Lerna will bump, and leaves an empty Unreleased heading above it. "yarn lerna changed" shows which packages those are. A package that is bumped only because a dependency changed gets one "Changed" entry that names the dependency, so that each published version has a section of its own. In the root file the operator also reconciles the Unreleased entries against the previous published version, because the impact lines state the net effect between published versions.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pointer expressions had an unstated gap: the width of a value was undefined for numeric literals, arithmetic results, and odd-digit hex, yet
$concatand$keccak256depend on operand widths — so a compiler and a debugger could legitimately disagree on the bytes produced. This specifies the evaluation model and brings the reference implementation, the compiler, and the docs along with it.Semantics
Expressions evaluate to a value of one of two sorts:
The forms are sorted accordingly. Integers: a JSON-number literal,
$wordsize, a variable or lookup, arithmetic ($sum/$difference/$product/$quotient/$remainder), and an odd-digit hex literal (which has no whole-byte width). Bytes: an even-digit hex literal (width = bytes written),$read(width = region length),$sizedN/$wordsized(width =N/ word size),$keccak256(width 32), and$concat(width = sum of operand widths).Coercion runs one way. Where an integer is expected — arithmetic operands, a list
count, a segmentslot/offset/length— a bytes value is read as the non-negative integer its bytes encode (big-endian). Where bytes are expected — the operands of$concatand$keccak256— the operand must be width-bearing; a bare integer there is an error and must be given a width with$sizedN/$wordsizedfirst. The resize forms are the only bridge from an integer to bytes; there is no implicit widening.This codifies what the mapping/string pointer examples already do (they word-size a key and slot before hashing). Two schema examples that hashed bare integers are corrected: the
keccak256definition's example and the expression schema's root-level example.Reference implementation
@ethdebug/pointersnow evaluates to a taggedValue— integer or bytes — with the sort rules above, coerces where an integer is expected, and throws on a bare-integer operand to$concator$keccak256with a message naming the operand and suggesting$wordsized/$sizedN. Variables carry the sort of their defining expression. This is package-internal:evaluateis not exported from the package index, and the publicCursor.Regionshape is unchanged, so no consumers needed edits. The previous single-width implementation gave a JSON-number literal minimal width (literal0was zero bytes), so a$keccak256over a bare slot silently hashed the wrong preimage; that can no longer happen.The evaluator's previously skipped keccak test is reinstated, and the evaluator test file now covers every sort rule, both coercion directions, and the error cases. A new test evaluates every
$keccak256/$concatoccurrence in the pointer schema examples so an example without operand widths fails the suite.Compiler
bugc's slot-computation helpers emitted
$keccak256with a bare slot operand. They now word-size it, with a nested$keccak256passed through since it is already 32-byte bytes. These helpers have no production callers today — bugc emits a mapping variable as its base slot — so emitted debug info was already valid; the helpers and their pinned tests are corrected so that any future use is too.Docs
The expression spec page no longer says values are "assumed to be left-padded to the bytes width appropriate for the context"; it states the two sorts, the even/odd hex rule, and the width-bearing requirement. The pointer expressions guide gains an "Integers and bytes" section and word-sizes the operands in its hashing examples. The evaluation implementation guide follows the new evaluator's shape.
Note: the integer/bytes sorting discipline is meant to carry over into a future extraction of the expression language into a shared facility.