feat(opy-compiler): lower non-control-flow primitives to canonical WIR - #54
Conversation
- Support OverPy implicit default global variables A-DX through the compiler at their fixed Workshop slots via the frontend default_var_index contract; used implicit slots are reserved for declared-variable allocation, explicit-index collisions fail with the source-attributed index-collision diagnostic, and variable tables are created in index order matching the pinned reference. - Implement the missing **= lexer/parser path (pinned OverPy 9.7.10 evidence); value-position power and indexed power modification now fail with stable source-attributed diagnostics at the pinned workshop-rs 0.1.5 emitter boundary instead of span-less emission errors. - Constrain the native compiler with the #46 oracle through the existing workshop-rs parser + roundtrip::equivalent machinery (both sides parsed as canonical WIR), replacing parallel manual WIR assertions. - Mirror the pinned oracle's observable lowering for not-on-comparison negation and literal zero-index reads (firstOf). - Fix the player indexed-assignment canonical signature (the player-variable value node is the first argument) and make auto-allocation fill free slots below early explicit indices. - Add the synthetic/issue-46-unsupported negative fixture with a stable source-attributed diagnostic, and correct the support-matrix span wording for indexed assignments.
Lower OPY global/player assignments, augmented assignments, array indexing assignments, value expressions, format calls, pass statements, and declaration initializers into canonical workshop-rs WIR with source and target span provenance. Closes #46
- Support OverPy implicit default global variables A-DX through the compiler at their fixed Workshop slots via the frontend default_var_index contract; used implicit slots are reserved for declared-variable allocation, explicit-index collisions fail with the source-attributed index-collision diagnostic, and variable tables are created in index order matching the pinned reference. - Implement the missing **= lexer/parser path (pinned OverPy 9.7.10 evidence); value-position power and indexed power modification now fail with stable source-attributed diagnostics at the pinned workshop-rs 0.1.5 emitter boundary instead of span-less emission errors. - Constrain the native compiler with the #46 oracle through the existing workshop-rs parser + roundtrip::equivalent machinery (both sides parsed as canonical WIR), replacing parallel manual WIR assertions. - Mirror the pinned oracle's observable lowering for not-on-comparison negation and literal zero-index reads (firstOf). - Fix the player indexed-assignment canonical signature (the player-variable value node is the first argument) and make auto-allocation fill free slots below early explicit indices. - Add the synthetic/issue-46-unsupported negative fixture with a stable source-attributed diagnostic, and correct the support-matrix span wording for indexed assignments.
3f40ab5 to
4bf9d52
Compare
Teakowa
left a comment
There was a problem hiding this comment.
Review verdict: changes required
The workshop-rs 0.1.8 rebaseline, canonical Raise To Power closure, source-level **= path, oracle-constrained native-vs-reference WIR comparison, provenance handling, and current CI are all directionally correct. I reviewed the remaining #46 surface as well rather than stopping at the power fix. The PR still has three correctness blockers before it can close #46.
Blockers
-
Implicit default player variables (
eventPlayer.A–eventPlayer.DX) are still missing inopy-compiler.The pinned OverPy reference uses the same
defaultVarNamesfor global and player variables. Its ownvariables.opytest uses undeclaredeventPlayer.A/eventPlayer.E, and the expected Workshop table assigns those player variables the same fixed slots (A=0,E=4) independently of the global table.The native frontend already lowers any
eventPlayer.<member>toHirExpr::PlayerVar, but this PR only collects/reservesimplicit_default_globals; player allocation is built solely from explicitplayervardeclarations andlower_value/assignment requireself.players.get(name). Valid OPY such aseventPlayer.A = 1therefore resolves in the frontend and then fails asunknown player variablein the compiler.Add the player-side equivalent of implicit default-variable discovery/allocation/collision handling, preserving fixed slots independently from globals. The oracle evidence should cover reads, writes, augmented assignment, and coexistence with explicit/auto-indexed
playervardeclarations. -
Hexadecimal OPY numeric literals leak their OPY source spelling into canonical Workshop emission.
The frontend intentionally preserves numeric source spelling in HIR.
opy-compilercurrently copies that text directly intowir::Value::Number, and theworkshop-rsemitter prints the stored text verbatim. The pinned OverPyvariables.opycorpus demonstrates that0x124BC/0x124are emitted as decimal Workshop numbers (74940/292), while scientific spelling such as1e10is retained.This means the newly claimed E2E numeric-literal surface is not correct for an already-supported OPY literal form. Normalize OPY-only numeric spellings at the OPY→WIR boundary to a Workshop-compatible/reference-equivalent spelling while preserving spellings that are valid and observable (
0.0,1e10, etc.). Add an oracle-backed hex case. -
The compiler now promotes non-OverPy
/////=syntax into a WrightKit-only dialect by treating it as Workshop Divide.The pinned OverPy 9.7.10 operator table contains
/,/=,%,%=,**,**=,min=, andmax=, but no//or//=. The native frontend currently lexes/parses//and//=, and this PR newly maps those HIR operators to canonicaldivide/ModifyOp::Divide. That changes a frontend divergence into successful end-to-end compilation of syntax the reference language does not support.Do not accept this as an alias. Remove the compiler normalization and make the unsupported source form fail explicitly; ideally correct the frontend surface as part of the same compatibility repair or track that frontend correction explicitly. Likewise, avoid speculative backend aliases such as
^,&&,||, or!unless they are part of a documented HIR contract backed by source-language evidence.
Major findings
-
= nullvariable initializers do not match the pinned initializer contract. OverPy treats a single literalnullor source text0initializer as the default variable state and emits no initializer action. This PR only suppresses textual numeric0;nullsynthesizes an unnecessary initialize rule/action. TreatExpr::Nullas the same default/no-op case and add evidence. Keep0.0distinct: the reference checks source text exactly and does emit it. -
Do not interpret #46 closure as full OverPy mutation-syntax closure yet. The pinned operator corpus also includes
min=,max=,A++, andA--. The current frontend does not expose those forms through this path, andworkshop-rs0.1.8's typedModifyOpcurrently has no Min/Max variants. This should be explicitly routed rather than silently lost: identify the Workshop-owned Min/Max WIR gap inworkshop-rs, and either keep the affected OPY forms as an explicit tracked gap or narrow the #46/roadmap claim. Do not duplicate the Workshop operation semantics inopy-rs. -
The indexed-assignment claim is broader than the executable surface. The compiler accepts an index whose immediate base is a global/player variable, while upstream assignment normalization supports deeper 2D/3D indexed writes. Either evidence and implement the intended nested surface in its owning follow-up, or describe #46 as single-level indexed variable assignment/modification rather than generic “array index assignments and modifications.”
Minor
compatibility/support-matrix.jsonstill carries the old snapshot text (42-fixture,35 matches, oldasOfCommit) while this PR updates the corpus/docs to 44 fixtures and adds new E2E rows. Since the matrix is the mechanically checked state source, refresh that snapshot metadata so it does not contradict the corpus it describes.
Verified good
- exact
workshop-rs = "=0.1.8"dependency baseline; - value-position
**, direct**=, and indexed**=use the canonical Workshop-owned contracts; - the #46 oracle test now genuinely constrains native compiler output by reparsing both native and pinned-oracle Workshop through
workshop-rsand comparing canonical WIR equivalence; - direct target-span vs indexed statement-span claims are now accurate;
- unsupported primitive lowering has a source-attributed negative fixture;
passis handled explicitly as a source-level no-op rather than silently swallowing arbitrary HIR;- no temporary Workshop IR or copied Workshop catalog semantics were introduced;
- #47 control flow and #42 receiver/member breadth remain outside this PR;
- CI run #112 is green on Rust 1.85/stable and the macOS/Windows JS runtime jobs.
After the three blockers and the claim/contract issues above are resolved, #54 should be suitable to close #46 and unblock #47.
Teakowa
left a comment
There was a problem hiding this comment.
Re-review verdict: implementation approved; metadata cleanup required before merge
The three prior correctness blockers are resolved on 8bf843f:
- implicit
eventPlayer.A–DXvariables now use an independent player namespace with fixed slots, allocation reservation, HIR validation, and collision diagnostics; the pinned oracle fixture coversA,E, andDXalongside declared player variables; - OPY hex literals are normalized at the OPY→WIR boundary (
0x124BC→74940,0x124→292) while ordinary numeric spellings remain preserved, and the same oracle-equivalence fixture constrains the result; /////=and speculative^,&&,||,!aliases no longer become a WrightKit-only compilable dialect; source-level tests require them to fail at the frontend boundary.
workshop-rs = "=0.1.8", power value/direct/indexed modification lowering, canonical WIR equivalence, provenance, and negative diagnostics remain correct. Hosted CI #113 is green on Rust stable/1.85 and the macOS/Windows JS-runtime jobs.
I found no new lowering/correctness blocker. Before merge, please make the authoritative claims match the intentionally deferred follow-ups:
- Narrow
compilation/opy-assignment-loweringwording to the executable #46 surface. It currently says generic “array index assignments and modifications” although #60 explicitly tracks deeper/multidimensional writes; call this single-level indexed variable assignment/modification. Likewise, either remove the broad “variable declaration initializers” claim or explicitly exclude thenulldefault-initializer quirk tracked by #58. - Reconcile syntax/support wording.
docs/opy/support-matrix.mdnow lists the supported operator surface but omits the newly supported**=. The machine rowsyntax/assignments-control-flowstill says generic “augmented ops”; narrow it to the evidenced subset (+= -= *= /= %= **=) or explicitly link the deferredmin=/max=and++/--gaps. - Reconcile issue/roadmap scope before closing #46. #46 still says “Complete global/player assignment and modification lowering”, and parent #41 says assignment/modification/indexing completes through #46, while #58/#59/#60 and workshop-rs#95 are intentionally non-gating follow-ups. Update #46/#41 to record those explicit non-goals/follow-ups so closing #46 does not contradict the authoritative roadmap.
The follow-up ownership is otherwise sensible: #58/#59/#60 belong to opy-rs; workshop-rs#95 correctly owns canonical Min/Max operations. For the cross-repo Min/Max path, link workshop-rs#95 to the existing opy-rs consumer stage (#42) or another already-authoritative consumer issue rather than leaving the integration step implicit.
After this small metadata/roadmap cleanup, I consider PR #54 ready to merge and close #46.
|
Addressed the re-review metadata/roadmap findings in 3d23ee9: narrowed #46 support claims to single-level indexed forms and non-null initializers, added **= to the support wording, narrowed the augmented-assignment row, updated #46/#41 non-goals and follow-ups, and linked workshop-rs#95 to opy-rs#42. Re-ran the full local evidence suite and hosted CI run 32692643686; all checks are green. |
Teakowa
left a comment
There was a problem hiding this comment.
Final review: merge-ready
Re-reviewed the full PR at 3d23ee9, including the previous correctness blockers, the follow-up routing, support claims, provenance, and the latest CI.
The prior merge blockers remain closed: implicit player A–DX fixed-slot semantics are oracle-backed, OPY hex literals normalize at the OPY→WIR boundary, non-OverPy operator aliases are rejected at the frontend boundary, and canonical power lowering remains on workshop-rs 0.1.8.
The final scope/metadata cleanup is also correct:
- #46 now explicitly scopes the evidenced augmented-operator subset, single-level indexed forms, and non-null initializer evidence, with #58/#59/#60/workshop-rs#95 non-gating.
- Parent #41 mirrors that boundary rather than treating #46 as complete mutation-syntax parity.
- workshop-rs#95 now identifies opy-rs#42 as the downstream consumer path.
- support-matrix and compatibility docs no longer claim
//,++/--,min=/max=, multidimensional indexed writes, or null-default initialization as completed #46 behavior;**=is explicitly listed. - Latest CI run #114 (
32692643686) is fully green on Rust stable, Rust 1.85.0, macOS, and Windows.
No remaining correctness, architecture, ownership, provenance, test, or support-claim blocker found within #46 scope. The human compatibility baseline still summarizes declaration initializer support broadly in its category row, but the detailed support docs and authoritative matrix explicitly route the null-default quirk to #58; I do not consider that a merge blocker.
Verdict: merge-ready. PR #54 can merge and close #46; proceed to #47 afterward.
Summary
Completes Issue #46 in the existing PR with workshop-rs v0.1.8 as the active dependency baseline.
The real fixture exercises:
The change remains limited to #46; control flow (#47) and catalog/member closure (#42) are not included.
Verification
Follow-up tracking for review findings intentionally outside #46: opy-rs #58 null initializers, #59 ++/-- assignments, #60 multidimensional indexed assignment, and wrightkit/workshop-rs #95 canonical min/max operations.
Fixes #46