Repository navigation
ogar-loco: close the shared-core arity gap (50 → 90 covered) - #265
Conversation
The core NAMED 95 operations and gave stack arities for 50. The other 45
resolved to a legal FnIndex and then could not say what they consumed -- so a
body containing one could not be validated or segmented, and the failure
surfaced only in a consumer, far from the cause.
The doc already called these "real palette entries deliberately not yet
covered" with the standing invitation that "coverage grows in the core, once,
for everyone". This is that sweep: 40 newly covered, 5 deliberately refused.
## Where the numbers come from
Not judgement. Each arity is corroborated by the frontends' own declarations,
harvested from source: Blockly's block definitions (AdaWorldAPI/blockly
`blocks/*.ts`) and Apache-2.0 scratch-blocks (`src/blocks/*.ts`, the
`args0` input_value / input_statement counts). Where both frontends have the
block they agree; where only one does, that one decides. `control_wait`
declares one input, `control_stop` none, `logic_ternary` and `math_constrain`
three, `math_atan2` and `operator_random` two, `math_random_float` none.
Two conventions were READ from the existing table rather than invented:
- `VAR_GET => 0` ("leaves: they push, they do not consume") establishes that
an identifier rides as an immediate value byte, never a stack operand. So
VAR_SET / VAR_CHANGE / APPEND evaluate only the incoming value -- exactly
what both frontends declare, the variable being a field in each.
- The two-quantity split governs PROC_DEF: it evaluates nothing (arity 0) and
REFERENCES its body (body_refs 1), which is why it also lands in the
body_refs table. Scratch's `procedures_definition` independently declares
zero value inputs and one statement input.
## The list family, and why one table serves two frontends
Blockly passes a list EXPRESSION (`lists_length` takes a LIST input); Scratch
names a global list in a FIELD (`data_lengthoflist` declares zero inputs). On
a stack machine these converge: Scratch's field lowers to an explicit push of
the list reference -- the shape VAR_GET already has -- and the op pops it. So
the list is counted as an ordinary operand, and no second opcode family is
needed for the second frontend.
## What stays refused, and why that is not an oversight
Five entries: RETURN, NUMBER_PROPERTY, PROMPT, LIST_WITH, PROC_CALL. Every
one is genuinely VARIADIC -- a void return evaluates nothing and a value
return evaluates one; `divisible by` takes a divisor the other number
properties do not; `text_prompt` carries its message as a field where
`text_prompt_ext` makes it an operand; a list literal and a procedure call
hold as many items/arguments as their mutator declares. These want an arity
that is a property of the CALL rather than the function, which is a design
question and not a table row. Guessing would be worse than refusing: a wrong
arity desynchronizes the stack and reattributes every later operand.
## The enumeration that was missing
`every_named_function_has_an_arity_except_the_documented_variadics` walks the
whole byte range instead of a hand-listed set, so a newly minted constant with
no arity row fails HERE immediately rather than years later in a consumer --
which is exactly how this gap was found. Verified as a real falsifier by
deleting one arity row: it fails naming the exact opcode.
Three existing tests pinned WAIT as deliberately-uncovered and failed as
designed; each is re-pinned onto the still-variadic set and gained a can-fire
half, so "uncovered" keeps discriminating instead of labelling whatever
happens to be missing.
cargo test --workspace: 78 test targets, 0 failures. clippy clean.
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_f1670101-9862-4528-b307-353f6000365e) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a6eccf126b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| FnIndex::WAIT | FnIndex::WAIT_UNTIL => 1, | ||
| FnIndex::STOP => 0, |
There was a problem hiding this comment.
Mark newly covered commands as non-pushing
Once these opcodes receive arities, shared_core::pushes_result also starts covering them but falls through to Some(true) because its non-pushing match was not extended. Consequently statement commands such as WAIT, WAIT_UNTIL, STOP, PRINT, APPEND, variable/list mutations, and PROC_DEF leave fictitious stack values during statement_bounds; for example, two consecutive STOP calls now produce DanglingOperands { depth: 2 } instead of two statement boundaries. Add every newly covered command that does not return a value to the non-pushing set.
Useful? React with 👍 / 👎.
The core named 95 operations and gave stack arities for 50. The other 45 resolved to a legal
FnIndexand then could not say what they consumed — so a body containing one could not be validated or segmented, and the failure surfaced only in a consumer, far from the cause.shared_core::stack_arity's own doc already called these "real palette entries deliberately not yet covered", with the standing invitation that "coverage grows in the core, once, for everyone." This is that sweep: 40 newly covered, 5 deliberately refused.Found from the consumer side: mapping a second frontend (Scratch) onto the same core hit 21 of them, and 37 Blockly block types land on the same arity-less bytes — so it predates Scratch and was never Scratch-specific.
Where the numbers come from — not judgement
Each arity is corroborated by the frontends' own declarations, harvested from source: Blockly's block definitions (
AdaWorldAPI/blockly,blocks/*.ts) and Apache-2.0scratch-blocks(src/blocks/*.ts,args0input_value/input_statementcounts). Where both frontends have the block they agree; where only one does, that one decides.Two conventions were read from the existing table, not invented:
VAR_GET => 0— "leaves: they push, they do not consume" — establishes that an identifier rides as an immediate value byte, never a stack operand. SoVAR_SET/VAR_CHANGE/APPENDevaluate only the incoming value, which is exactly what both frontends declare (the variable is a field in each).PROC_DEF: it evaluates nothing (arity 0) and references its body (body_refs1). Scratch'sprocedures_definitionindependently declares zero value inputs and one statement input.The list family — one table, two frontends
Blockly passes a list expression (
lists_lengthtakes a LIST input); Scratch names a global list in a field (data_lengthoflistdeclares zero inputs). On a stack machine these converge: Scratch's field lowers to an explicit push of the list reference — the shapeVAR_GETalready has — and the op pops it. So the list is counted as an ordinary operand, and no second opcode family is needed for the second frontend.What stays refused, and why that is not an oversight
RETURNNUMBER_PROPERTYeven/odd/primetake the number alone;divisible bytakes a divisorPROMPTtext_promptcarries its message as a field,text_prompt_extas an operandLIST_WITHPROC_CALLThese want an arity that is a property of the call, not the function — a design question, not a table row. Guessing would be worse than refusing: a wrong arity doesn't produce a slightly-wrong result, it desynchronizes the stack and reattributes every later operand.
The enumeration that was missing
every_named_function_has_an_arity_except_the_documented_variadicswalks the whole byte range instead of a hand-listed set, so a newly minted constant with no arity row fails here immediately rather than years later in a consumer — which is exactly how this gap was found. Verified as a real falsifier by deleting one arity row: it fails naming the exact opcode ([(38, Some("TERNARY"))]).Three existing tests pinned
WAITas deliberately-uncovered and failed as designed. Each is re-pinned onto the still-variadic set and gained a can-fire half, so "uncovered" keeps discriminating instead of becoming a label for whatever happens to be missing.Verification
cargo test --workspace: 78 test targets, 0 failures.clippy --workspace --all-targetsclean. Downstream, blockly-rs's pinned gap count drops 21 → 1 (PROC_CALL, still variadic) — its pin fired exactly as designed and was re-pinned deliberately.Generated by Claude Code