diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 38f9163..bbc487e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,7 @@ name: CI on: + workflow_dispatch: push: branches: ["main"] paths-ignore: @@ -30,6 +31,56 @@ jobs: cache-shared-key: linux-quality all-features: true + compiler-compatibility: + name: Compiler compatibility gate + needs: rust-quality + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@v7 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + + - name: Restore Rust cache + uses: Swatinem/rust-cache@v2 + with: + shared-key: linux-quality + cache-targets: false + cache-all-crates: false + cache-workspace-crates: false + cache-bin: false + save-if: false + cache-on-failure: false + + - name: Build public compiler CLI + run: cargo build --locked -p opy-cli --bin opy-cli + + - name: Build internal compatibility evidence target + run: cargo build --locked -p opy-cli --features compatibility --bin opy-compat + + - name: Run compatibility harness tests + run: python3 -B -m unittest discover -s compatibility/tests + + - name: Run native compiler compatibility gate + run: >- + python3 -B compatibility/run_native.py + --binary target/debug/opy-cli + --semantic-binary target/debug/opy-compat + --results target/opy-compiler-results + --report target/opy-compiler-report.json + + - name: Upload compiler compatibility report + if: always() + uses: actions/upload-artifact@v4 + with: + name: opy-compiler-compatibility-report + path: target/opy-compiler-report.json + if-no-files-found: ignore + macro-runtime-ports: name: JS runtime on ${{ matrix.os }} needs: rust-quality diff --git a/Cargo.lock b/Cargo.lock index fba441b..ea2e76d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -370,9 +370,11 @@ version = "0.1.4" dependencies = [ "clap", "clap_complete", + "opy-compiler", "opy-rs", "serde", "serde_json", + "workshop-rs", ] [[package]] @@ -381,6 +383,7 @@ version = "0.1.4" dependencies = [ "opy-macro-js", "opy-rs", + "serde", "serde_json", "workshop-rs", ] diff --git a/README.md b/README.md index 347ed06..ce13c63 100644 --- a/README.md +++ b/README.md @@ -54,10 +54,13 @@ maintaining a second raw Workshop implementation. ## CLI and library -The standalone CLI currently exposes the Workshop-independent tooling surface: +The standalone CLI exposes both Workshop-independent tooling and the bounded +Workshop compiler surface: ```sh opy-cli check main.opy +opy-cli compile main.opy +opy-cli compile --format json main.opy opy-cli inspect main.opy opy-cli support --json opy-cli completion bash @@ -90,7 +93,7 @@ compatibility corpus and pinned OverPy reference evidence. | Receiver/member functions | 🟡 Partial | Declared members work; full member breadth is not yet complete | | Enums & constants | 🟡 Partial | Declared domains resolve; full domain breadth is not yet complete | | Advanced directives, translations & optimizer controls | 🟡 Partial | Source state exists; Workshop-dependent effects remain incomplete | -| OPY → Workshop compilation | 🟡 Partial | The compiler boundary exists and lowering is expanding through `workshop-rs`; full real-project compilation is not yet claimed | +| OPY → Workshop compilation | 🟡 Partial | The versioned library/CLI compile contract and bounded lowering surface are supported; remaining corpus gaps stay explicit | | Workshop → OPY reconstruction | ⏳ Not yet | Will consume canonical `workshop-rs` semantics and remain owned by `opy-rs` | Exact per-feature evidence remains available in the diff --git a/compatibility/README.md b/compatibility/README.md index 7e1a157..a11eeff 100644 --- a/compatibility/README.md +++ b/compatibility/README.md @@ -52,10 +52,14 @@ closures. See `compatibility/fixtures/README.md` for the complete provenance record; the current inventory is the per-fixture metadata discovered and validated by [`run_oracle.py`](run_oracle.py). -`oracle.json` captures the pinned oracle identity, source hash, compile status, -exit code, normalized diagnostics, normalized Workshop text, and normalized -output hash. The runner normalizes line endings, trailing whitespace, and final -newline presentation only; it does not remove Workshop operations or values. +`oracle.json` captures the pinned oracle identity, the complete resolved OPY +project input manifest (relative source paths and per-file SHA-256 hashes), its +canonical project digest, compile status, exit code, normalized diagnostics, +normalized Workshop text, and normalized output hash. The runner normalizes +line endings, trailing whitespace, and final newline presentation only; it does +not remove Workshop operations or values. The project manifest covers every +`.opy` file in the fixture directory except explicitly listed minimized +regression snippets, so changes to included sources invalidate the snapshot. `differential-expectations.json` is the independent native-side expectation record. Each fixture has a native outcome, an expected relationship to the @@ -64,6 +68,34 @@ The differential runner derives `unexpected-divergence`, `regression`, and `inconclusive` results from those records; it never treats a reference-success / native-failure case as a match. +`compiler-expectations.json` is a separate contract for the #38 compiler +surface. It does not replace or weaken the source/frontend expectations above: +its entries declare the compiler status, normalized-output/direct semantic-WIR +or diagnostic contract, evidence, and concrete owner for each fixture. A +non-match must cite both its pinned oracle snapshot and fixture provenance; +parent issue #8 is not a durable gap owner. + +For `semantic-wir`, `run_native.py` invokes the public `opy-cli compile +--format json` contract first, then invokes the feature-gated internal +`opy-compat` target with the same source project and oracle snapshot. +`opy-compat` parses only the oracle Workshop text into canonical WIR and +compares it directly with the native WIR produced by lowering through +`workshop-rs::roundtrip::equivalent`. It emits top-level harness +`compatibility.semanticWIR` evidence with both project digests. A text reparse +of the native emission is not accepted as semantic-WIR evidence, and the +public compiler API remains oracle-free. + +`run_native.py` drives the built public compiler contract for every fixture, +writes ephemeral producer results under `target/`, and delegates compiler +expectation loading, full project-input validation, stage comparison, and +blocking classification to `diff.py`. Only the durable, evidence-backed +entries in `compiler-expectations.json` can produce `known-gap` or +`unsupported` in this report. +Expectation mismatches are reported as `unexpected-divergence` or `regression`. +The command exits non-zero for those mismatches and, by default, for +`inconclusive` cases; use `--allow-inconclusive` only for a local report that is +explicitly not an acceptance gate. + Real-world fixtures retain the complete project as the integration case. A fixture may also declare `regressions` pointing to minimized snippets under the same directory. Each snippet records its source path, parent source, @@ -81,6 +113,11 @@ definitions, and validation remain owned by `workshop-rs`. python3 -m unittest discover -s compatibility/tests python3 compatibility/run_oracle.py --update python3 compatibility/run_oracle.py +cargo build --locked -p opy-cli --bin opy-cli +cargo build --locked -p opy-cli --features compatibility --bin opy-compat +python3 compatibility/run_native.py \ + --binary target/debug/opy-cli \ + --semantic-binary target/debug/opy-compat python3 compatibility/diff.py --results /path/to/results --report compatibility/report.json ``` @@ -150,27 +187,30 @@ python3 compatibility/diff.py \ (`crates/opy-rs/tests/differential.rs`) against the recorded oracle snapshots directly. -The producer must write a result with the same schema as the oracle's -`compile` record and the fixture id. It may additionally provide a top-level -`semantic` JSON value when a runtime-sensitive scenario has been executed. -The command template is tokenized without a shell; `{fixture_id}`, `{source}`, -and `{result}` are the only substitutions. +The producer must write a result with the same compile-focused schema as the +oracle and the fixture id. A compatibility runner may additionally provide a +top-level `compatibility.semanticWIR` value when a compiler contract requires +canonical-WIR evidence; this field is not part of `opy-cli`'s public compile +report. The command template is tokenized without a shell; `{fixture_id}`, +`{source}`, and `{result}` are the only substitutions. The report separates these stages: * `compile-status` and `diagnostics`; * `exact-output`, using the unnormalized Workshop text; * `normalized-output`, using the versioned snapshot normalization; and -* `semantic`, when both producers provide semantic evidence. +* `semantic-wir`, when the isolated compatibility harness provides direct + native-WIR evidence. It also separates relationship outcomes: `match`, `known-gap`, `unsupported`, `unexpected-divergence`, `regression`, and `inconclusive`. Known gaps are reviewable evidence and are not counted as successful parity. An exact-output difference with a normalized-output match is reported as a -presentation difference. A normalized-output or semantic regression exits 1. -Missing producer results or unavailable semantic evidence are `inconclusive` -and exit 2 by default, so a CI job cannot silently pass without a producer. +presentation difference. A normalized-output or semantic-WIR regression exits +1. Missing producer results or unavailable semantic evidence are +`inconclusive` and exit 2 by default, so a CI job cannot silently pass without +a producer. Use `--allow-inconclusive` only for local contract checks. The opy-rs producer side of the differential contract is the native Rust diff --git a/compatibility/compiler-expectations.json b/compatibility/compiler-expectations.json new file mode 100644 index 0000000..1a23182 --- /dev/null +++ b/compatibility/compiler-expectations.json @@ -0,0 +1,690 @@ +{ + "schemaVersion": 1, + "contract": "compiler", + "description": "Independent compiler outcome expectations for the #38 OPY-to-Workshop compatibility baseline. Source/frontend expectations remain in differential-expectations.json.", + "evidencePolicy": { + "native": "The compiler result is compared with this separately reviewed baseline; it never creates its own expectation.", + "classification": "match requires normalized output, direct canonical-WIR equivalence, or a stable diagnostic contract. known-gap and unsupported require pinned oracle and fixture provenance evidence plus a concrete follow-up owner.", + "semanticWIR": "The runner compares native WIR directly with the pinned oracle WIR through workshop-rs::roundtrip::equivalent and verifies both complete project input digests. It never reparses native Workshop text as a substitute for native WIR." + }, + "cases": [ + { + "fixture": "synthetic/basic-rule", + "nativeStatus": "success", + "classification": "match", + "comparison": "normalized-output", + "evidence": [ + "test:opy-cli::compile_text_prints_only_final_workshop_to_stdout" + ], + "owner": "opy-rs#38", + "note": "Minimal OPY rule emits the pinned Workshop output through the public compiler and CLI." + }, + { + "fixture": "synthetic/chase-enums", + "nativeStatus": "success", + "classification": "match", + "comparison": "normalized-output", + "evidence": [ + "test:opy-compiler-issue-42-oracle::catalog_enum_members_lower_and_validate" + ], + "owner": "opy-rs#38", + "note": "Catalog enum members emit through the canonical catalog-backed compiler slice." + }, + { + "fixture": "synthetic/declarations-numbers", + "nativeStatus": "success", + "classification": "match", + "comparison": "normalized-output", + "evidence": [ + "oracle:synthetic/declarations-numbers/oracle.json" + ], + "owner": "opy-rs#38", + "note": "The declaration fixture has a stable normalized Workshop result under the bounded compiler surface." + }, + { + "fixture": "synthetic/for-range-agentlab", + "nativeStatus": "success", + "classification": "match", + "comparison": "normalized-output", + "evidence": [ + "oracle:synthetic/for-range-agentlab/oracle.json" + ], + "owner": "opy-rs#38", + "note": "The supported global range binder has a stable normalized Workshop result." + }, + { + "fixture": "synthetic/issue-29-main-file", + "nativeStatus": "success", + "classification": "match", + "comparison": "normalized-output", + "evidence": [ + "oracle:synthetic/issue-29-main-file/oracle.json" + ], + "owner": "opy-rs#38", + "note": "mainFile scope remains covered by the bounded compiler output contract." + }, + { + "fixture": "synthetic/issue-35-integration", + "nativeStatus": "success", + "classification": "match", + "comparison": "normalized-output", + "evidence": [ + "test:opy-compiler::vertical_slice_preserves_source_files_spans_and_emits_workshop" + ], + "owner": "opy-rs#38", + "note": "Issue #35 is the public source-to-WIR-to-Workshop vertical slice." + }, + { + "fixture": "synthetic/receiver-calls", + "nativeStatus": "success", + "classification": "match", + "comparison": "normalized-output", + "evidence": [ + "test:opy-compiler-issue-42-oracle::catalog_backed_receiver_calls_match_the_pinned_oracle" + ], + "owner": "opy-rs#38", + "note": "The exercised catalog-backed receiver call is canonical-WIR equivalent to the pinned oracle." + }, + { + "fixture": "synthetic/control-flow", + "nativeStatus": "success", + "classification": "known-gap", + "comparison": "semantic-wir", + "evidence": [ + "oracle:synthetic/control-flow/oracle.json", + "provenance:synthetic/control-flow/fixture.json" + ], + "owner": "opy-rs#89", + "note": "Direct native-WIR comparison records a residual control-flow divergence; follow-up #89 tracks the lowering gap.", + "semanticEquivalent": false + }, + { + "fixture": "synthetic/issue-33-switch-break", + "nativeStatus": "success", + "classification": "known-gap", + "comparison": "semantic-wir", + "evidence": [ + "oracle:synthetic/issue-33-switch-break/oracle.json", + "provenance:synthetic/issue-33-switch-break/fixture.json" + ], + "owner": "opy-rs#89", + "note": "Direct native-WIR comparison records a residual nested-switch-break divergence; follow-up #89 tracks the lowering gap.", + "semanticEquivalent": false + }, + { + "fixture": "synthetic/issue-40-structural", + "nativeStatus": "success", + "classification": "match", + "comparison": "semantic-wir", + "semanticEquivalent": true, + "evidence": [ + "oracle:synthetic/issue-40-structural/oracle.json", + "provenance:synthetic/issue-40-structural/fixture.json", + "test:opy-compiler::issue_40_oracle_fixture_and_wir_lowering_agree" + ], + "owner": "opy-rs#38", + "note": "Issue #40 structural lowering is independently checked against the pinned oracle and WIR invariants." + }, + { + "fixture": "synthetic/issue-46-primitives", + "nativeStatus": "success", + "classification": "known-gap", + "comparison": "semantic-wir", + "evidence": [ + "oracle:synthetic/issue-46-primitives/oracle.json", + "provenance:synthetic/issue-46-primitives/fixture.json" + ], + "owner": "opy-rs#89", + "note": "Direct native-WIR comparison records a residual #46 primitive-lowering divergence; follow-up #89 tracks the lowering gap.", + "semanticEquivalent": false + }, + { + "fixture": "synthetic/issue-47-control-flow", + "nativeStatus": "success", + "classification": "known-gap", + "comparison": "semantic-wir", + "evidence": [ + "oracle:synthetic/issue-47-control-flow/oracle.json", + "provenance:synthetic/issue-47-control-flow/fixture.json" + ], + "owner": "opy-rs#89", + "note": "Direct native-WIR comparison records a residual #47 control-flow divergence; follow-up #89 tracks the lowering gap.", + "semanticEquivalent": false + }, + { + "fixture": "synthetic/issue-47-switch-order", + "nativeStatus": "success", + "classification": "known-gap", + "comparison": "semantic-wir", + "evidence": [ + "oracle:synthetic/issue-47-switch-order/oracle.json", + "provenance:synthetic/issue-47-switch-order/fixture.json" + ], + "owner": "opy-rs#89", + "note": "Direct native-WIR comparison records a residual authored-switch-order divergence; follow-up #89 tracks the lowering gap.", + "semanticEquivalent": false + }, + { + "fixture": "synthetic/issue-47-switch-structured-target", + "nativeStatus": "success", + "classification": "known-gap", + "comparison": "semantic-wir", + "evidence": [ + "oracle:synthetic/issue-47-switch-structured-target/oracle.json", + "provenance:synthetic/issue-47-switch-structured-target/fixture.json" + ], + "owner": "opy-rs#89", + "note": "Direct native-WIR comparison records a residual structured-switch-target divergence; follow-up #89 tracks the lowering gap.", + "semanticEquivalent": false + }, + { + "fixture": "synthetic/issue-47-do-while-shapes", + "nativeStatus": "success", + "classification": "match", + "comparison": "semantic-wir", + "semanticEquivalent": true, + "evidence": [ + "oracle:synthetic/issue-47-do-while-shapes/oracle.json", + "provenance:synthetic/issue-47-do-while-shapes/fixture.json", + "test:opy-compiler-issue-47-oracle::issue_47_do_while_break_shapes_match_the_pinned_oracle" + ], + "owner": "opy-rs#38", + "note": "Supported do-while break shapes are independently checked through canonical WIR equivalence." + }, + { + "fixture": "synthetic/settings", + "nativeStatus": "success", + "classification": "match", + "comparison": "semantic-wir", + "semanticEquivalent": true, + "evidence": [ + "oracle:synthetic/settings/oracle.json", + "provenance:synthetic/settings/fixture.json", + "test:opy-compiler::settings_lower_through_workshop_owned_emission" + ], + "owner": "opy-rs#38", + "note": "Settings emission is covered by the compiler's Workshop-owned structural comparison." + }, + { + "fixture": "synthetic/issue-47-do-while-invalid-placement", + "nativeStatus": "failure", + "classification": "match", + "comparison": "diagnostic-code", + "evidence": [ + "test:opy-compiler-issue-47-oracle::issue_47_invalid_do_while_placement_is_source_attributed" + ], + "owner": "opy-rs#38", + "note": "Invalid do-while placement is a stable source-attributed compiler failure.", + "failureClass": "frontend", + "diagnosticCode": "do-while-placement" + }, + { + "fixture": "synthetic/issue-46-unsupported", + "nativeStatus": "failure", + "classification": "unsupported", + "comparison": "diagnostic-code", + "evidence": [ + "oracle:synthetic/issue-46-unsupported/oracle.json", + "provenance:synthetic/issue-46-unsupported/fixture.json", + "test:opy-compiler-issue-46-oracle::issue_46_unsupported_primitive_fails_with_stable_source_attribution" + ], + "owner": "workshop-rs#123", + "note": "The compiler deliberately rejects the dict-indexed primitive because canonical WIR has no lossless representation.", + "failureClass": "integration", + "diagnosticCode": "unsupported-integration-surface" + }, + { + "fixture": "synthetic/issue-47-unsupported", + "nativeStatus": "failure", + "classification": "unsupported", + "comparison": "diagnostic-code", + "evidence": [ + "oracle:synthetic/issue-47-unsupported/oracle.json", + "provenance:synthetic/issue-47-unsupported/fixture.json", + "test:opy-compiler-issue-47-oracle::issue_47_nested_switch_break_is_source_attributed_when_not_representable" + ], + "owner": "workshop-rs#123", + "note": "The compiler deliberately rejects the nested conditional switch break because the released WIR has no equivalent carrier.", + "failureClass": "integration", + "diagnosticCode": "unsupported-integration-surface" + }, + { + "fixture": "synthetic/issue-47-switch-multiple-break", + "nativeStatus": "failure", + "classification": "unsupported", + "comparison": "diagnostic-code", + "evidence": [ + "oracle:synthetic/issue-47-switch-multiple-break/oracle.json", + "provenance:synthetic/issue-47-switch-multiple-break/fixture.json", + "test:opy-compiler-issue-47-oracle::issue_47_multiple_switch_breaks_are_not_silently_dropped" + ], + "owner": "workshop-rs#123", + "note": "The compiler deliberately rejects later-reachable multiple switch breaks because the released WIR has one canonical else carrier.", + "failureClass": "integration", + "diagnosticCode": "unsupported-integration-surface" + }, + { + "fixture": "census/workshop-feature-census", + "nativeStatus": "success", + "classification": "match", + "comparison": "normalized-output", + "evidence": [ + "oracle:census/workshop-feature-census/oracle.json", + "provenance:census/workshop-feature-census/fixture.json", + "support-matrix:compilation/end-to-end" + ], + "owner": "opy-rs#38", + "note": "The bounded feature census matches the pinned oracle through the normalized compiler output contract." + }, + { + "fixture": "real-world/6v6-adjustments", + "nativeStatus": "failure", + "classification": "known-gap", + "comparison": "diagnostic-code", + "evidence": [ + "oracle:real-world/6v6-adjustments/oracle.json", + "provenance:real-world/6v6-adjustments/fixture.json" + ], + "owner": "opy-rs#88", + "note": "The full real-world project remains outside the #38 compiler baseline and currently stops at a frontend lexical gap.", + "failureClass": "frontend", + "diagnosticCode": "lex-error" + }, + { + "fixture": "real-world/overpy-broken-weapons", + "nativeStatus": "failure", + "classification": "known-gap", + "comparison": "diagnostic-code", + "evidence": [ + "oracle:real-world/overpy-broken-weapons/oracle.json", + "provenance:real-world/overpy-broken-weapons/fixture.json" + ], + "owner": "opy-rs#88", + "note": "The full real-world project remains outside the #38 compiler baseline and currently stops at a frontend parse gap.", + "failureClass": "frontend", + "diagnosticCode": "parse-error" + }, + { + "fixture": "real-world/overpy-cake", + "nativeStatus": "success", + "classification": "known-gap", + "comparison": "compiler-contract", + "evidence": [ + "oracle:real-world/overpy-cake/oracle.json", + "provenance:real-world/overpy-cake/fixture.json" + ], + "owner": "workshop-rs#124", + "note": "The pinned oracle cannot be parsed by the released workshop-rs catalog because `Visible To` is ambiguous across domains; workshop-rs#124 owns the canonical domain disambiguation before semantic-WIR parity can be evaluated." + }, + { + "fixture": "real-world/overpy-client-to-server", + "nativeStatus": "failure", + "classification": "known-gap", + "comparison": "diagnostic-code", + "evidence": [ + "oracle:real-world/overpy-client-to-server/oracle.json", + "provenance:real-world/overpy-client-to-server/fixture.json" + ], + "owner": "opy-rs#88", + "note": "The full real-world project remains outside the #38 compiler baseline and currently stops at a frontend parse gap.", + "failureClass": "frontend", + "diagnosticCode": "parse-error" + }, + { + "fixture": "real-world/overpy-cronch", + "nativeStatus": "failure", + "classification": "known-gap", + "comparison": "diagnostic-code", + "evidence": [ + "oracle:real-world/overpy-cronch/oracle.json", + "provenance:real-world/overpy-cronch/fixture.json" + ], + "owner": "opy-rs#59", + "note": "The full real-world project remains outside the #38 compiler baseline and currently stops at a frontend parse gap.", + "failureClass": "frontend", + "diagnosticCode": "parse-error" + }, + { + "fixture": "real-world/overpy-crosshair", + "nativeStatus": "failure", + "classification": "known-gap", + "comparison": "diagnostic-code", + "evidence": [ + "oracle:real-world/overpy-crosshair/oracle.json", + "provenance:real-world/overpy-crosshair/fixture.json" + ], + "owner": "opy-rs#88", + "note": "The full real-world project remains outside the #38 compiler baseline and currently stops at a frontend parse gap.", + "failureClass": "frontend", + "diagnosticCode": "parse-error" + }, + { + "fixture": "real-world/overpy-inputhud", + "nativeStatus": "failure", + "classification": "known-gap", + "comparison": "diagnostic-code", + "evidence": [ + "oracle:real-world/overpy-inputhud/oracle.json", + "provenance:real-world/overpy-inputhud/fixture.json" + ], + "owner": "opy-rs#88", + "note": "The full real-world project remains outside the #38 compiler baseline and currently stops at a frontend parse gap.", + "failureClass": "frontend", + "diagnosticCode": "parse-error" + }, + { + "fixture": "real-world/overpy-meipocalypse", + "nativeStatus": "failure", + "classification": "known-gap", + "comparison": "diagnostic-code", + "evidence": [ + "oracle:real-world/overpy-meipocalypse/oracle.json", + "provenance:real-world/overpy-meipocalypse/fixture.json" + ], + "owner": "opy-rs#88", + "note": "The full real-world project remains outside the #38 compiler baseline and currently stops at a settings-placement gap.", + "failureClass": "frontend", + "diagnosticCode": "settings-placement" + }, + { + "fixture": "real-world/overpy-parabola", + "nativeStatus": "failure", + "classification": "known-gap", + "comparison": "diagnostic-code", + "evidence": [ + "oracle:real-world/overpy-parabola/oracle.json", + "provenance:real-world/overpy-parabola/fixture.json" + ], + "owner": "opy-rs#88", + "note": "The full real-world project remains outside the #38 compiler baseline and currently stops at a frontend parse gap.", + "failureClass": "frontend", + "diagnosticCode": "parse-error" + }, + { + "fixture": "real-world/overpy-pixelart", + "nativeStatus": "success", + "classification": "known-gap", + "comparison": "semantic-wir", + "evidence": [ + "oracle:real-world/overpy-pixelart/oracle.json", + "provenance:real-world/overpy-pixelart/fixture.json" + ], + "owner": "opy-rs#88", + "note": "The source compiles, but full real-world Workshop parity is outside the declared #38 baseline.", + "semanticEquivalent": false + }, + { + "fixture": "real-world/overpy-santa", + "nativeStatus": "failure", + "classification": "known-gap", + "comparison": "diagnostic-code", + "evidence": [ + "oracle:real-world/overpy-santa/oracle.json", + "provenance:real-world/overpy-santa/fixture.json" + ], + "owner": "opy-rs#88", + "note": "The full real-world project remains outside the #38 compiler baseline and currently stops at a frontend parse gap.", + "failureClass": "frontend", + "diagnosticCode": "parse-error" + }, + { + "fixture": "real-world/overpy-zencopter", + "nativeStatus": "failure", + "classification": "known-gap", + "comparison": "diagnostic-code", + "evidence": [ + "oracle:real-world/overpy-zencopter/oracle.json", + "provenance:real-world/overpy-zencopter/fixture.json" + ], + "owner": "opy-rs#88", + "note": "The full real-world project remains outside the #38 compiler baseline and currently stops at a frontend lexical gap.", + "failureClass": "frontend", + "diagnosticCode": "lex-error" + }, + { + "fixture": "real-world/ow1-emulator", + "nativeStatus": "failure", + "classification": "known-gap", + "comparison": "diagnostic-code", + "evidence": [ + "oracle:real-world/ow1-emulator/oracle.json", + "provenance:real-world/ow1-emulator/fixture.json" + ], + "owner": "opy-rs#88", + "note": "The full real-world project remains outside the #38 compiler baseline and currently stops at a frontend lexical gap.", + "failureClass": "frontend", + "diagnosticCode": "lex-error" + }, + { + "fixture": "synthetic/chase-condition-agentlab", + "nativeStatus": "success", + "classification": "known-gap", + "comparison": "compiler-contract", + "evidence": [ + "oracle:synthetic/chase-condition-agentlab/oracle.json", + "provenance:synthetic/chase-condition-agentlab/fixture.json" + ], + "owner": "workshop-rs#124", + "note": "The pinned oracle cannot be parsed by the released workshop-rs catalog because `None` is ambiguous across domains; workshop-rs#124 owns the canonical domain disambiguation before semantic-WIR parity can be evaluated." + }, + { + "fixture": "synthetic/chase-keywords", + "nativeStatus": "failure", + "classification": "known-gap", + "comparison": "diagnostic-code", + "evidence": [ + "oracle:synthetic/chase-keywords/oracle.json", + "provenance:synthetic/chase-keywords/fixture.json" + ], + "owner": "opy-rs#88", + "note": "The source semantic form is known, while the current canonical integration surface lacks a complete compiler contract.", + "failureClass": "integration", + "diagnosticCode": "unsupported-integration-surface" + }, + { + "fixture": "synthetic/declarations-rules", + "nativeStatus": "success", + "classification": "known-gap", + "comparison": "semantic-wir", + "evidence": [ + "oracle:synthetic/declarations-rules/oracle.json", + "provenance:synthetic/declarations-rules/fixture.json" + ], + "owner": "opy-rs#88", + "note": "The source declaration surface exceeds the independently evidenced compiler baseline.", + "semanticEquivalent": false + }, + { + "fixture": "synthetic/expressions-values", + "nativeStatus": "success", + "classification": "known-gap", + "comparison": "semantic-wir", + "evidence": [ + "oracle:synthetic/expressions-values/oracle.json", + "provenance:synthetic/expressions-values/fixture.json" + ], + "owner": "opy-rs#88", + "note": "The broad expression fixture exceeds the independently evidenced compiler baseline.", + "semanticEquivalent": false + }, + { + "fixture": "synthetic/diagnostics", + "nativeStatus": "failure", + "classification": "known-gap", + "comparison": "diagnostic-code", + "evidence": [ + "oracle:synthetic/diagnostics/oracle.json", + "provenance:synthetic/diagnostics/fixture.json" + ], + "owner": "opy-rs#88", + "note": "The source negative fixture is retained, but its compiler-facing diagnostic contract is outside the #38 baseline.", + "failureClass": "frontend", + "diagnosticCode": "parse-error" + }, + { + "fixture": "synthetic/issue-28-invalid-syntax", + "nativeStatus": "failure", + "classification": "known-gap", + "comparison": "diagnostic-code", + "evidence": [ + "oracle:synthetic/issue-28-invalid-syntax/oracle.json", + "provenance:synthetic/issue-28-invalid-syntax/fixture.json" + ], + "owner": "opy-rs#88", + "note": "The source negative fixture is retained, but its compiler-facing diagnostic contract is outside the #38 baseline.", + "failureClass": "frontend", + "diagnosticCode": "parse-error" + }, + { + "fixture": "synthetic/issue-28-string-modifiers", + "nativeStatus": "failure", + "classification": "known-gap", + "comparison": "diagnostic-code", + "evidence": [ + "oracle:synthetic/issue-28-string-modifiers/oracle.json", + "provenance:synthetic/issue-28-string-modifiers/fixture.json" + ], + "owner": "opy-rs#88", + "note": "The source modifier surface exceeds the independently evidenced compiler baseline.", + "failureClass": "integration", + "diagnosticCode": "unsupported-integration-surface" + }, + { + "fixture": "synthetic/issue-28-syntax", + "nativeStatus": "failure", + "classification": "known-gap", + "comparison": "diagnostic-code", + "evidence": [ + "oracle:synthetic/issue-28-syntax/oracle.json", + "provenance:synthetic/issue-28-syntax/fixture.json" + ], + "owner": "opy-rs#88", + "note": "The broad syntax fixture exceeds the independently evidenced compiler baseline.", + "failureClass": "integration", + "diagnosticCode": "unsupported-integration-surface" + }, + { + "fixture": "synthetic/issue-29-directives", + "nativeStatus": "failure", + "classification": "known-gap", + "comparison": "diagnostic-code", + "evidence": [ + "oracle:synthetic/issue-29-directives/oracle.json", + "provenance:synthetic/issue-29-directives/fixture.json" + ], + "owner": "opy-rs#88", + "note": "Optimizer/backend directive execution remains outside the declared compiler baseline.", + "failureClass": "integration", + "diagnosticCode": "backend-directive-unsupported" + }, + { + "fixture": "synthetic/issue-29-invalid", + "nativeStatus": "failure", + "classification": "known-gap", + "comparison": "diagnostic-code", + "evidence": [ + "oracle:synthetic/issue-29-invalid/oracle.json", + "provenance:synthetic/issue-29-invalid/fixture.json" + ], + "owner": "opy-rs#88", + "note": "The source negative directive fixture is retained, but its compiler-facing diagnostic contract is outside the #38 baseline.", + "failureClass": "frontend", + "diagnosticCode": "parse-error" + }, + { + "fixture": "synthetic/issue-31-negative", + "nativeStatus": "failure", + "classification": "known-gap", + "comparison": "diagnostic-code", + "evidence": [ + "oracle:synthetic/issue-31-negative/oracle.json", + "provenance:synthetic/issue-31-negative/fixture.json" + ], + "owner": "opy-rs#88", + "note": "The source translation negative fixture is retained, but broader translation parity is outside the #38 baseline.", + "failureClass": "frontend", + "diagnosticCode": "translations-invalid" + }, + { + "fixture": "synthetic/issue-31-nested-scope", + "nativeStatus": "success", + "classification": "known-gap", + "comparison": "semantic-wir", + "evidence": [ + "oracle:synthetic/issue-31-nested-scope/oracle.json", + "provenance:synthetic/issue-31-nested-scope/fixture.json" + ], + "owner": "opy-rs#88", + "note": "Optimizer scope is represented in source state, but optimizer execution is outside the compiler baseline.", + "semanticEquivalent": false + }, + { + "fixture": "synthetic/issue-31-positive", + "nativeStatus": "failure", + "classification": "known-gap", + "comparison": "diagnostic-code", + "evidence": [ + "oracle:synthetic/issue-31-positive/oracle.json", + "provenance:synthetic/issue-31-positive/fixture.json" + ], + "owner": "opy-rs#88", + "note": "The positive optimizer/directive fixture exceeds the independently evidenced compiler baseline.", + "failureClass": "integration", + "diagnosticCode": "unsupported-integration-surface" + }, + { + "fixture": "synthetic/issue-33-f-string", + "nativeStatus": "failure", + "classification": "known-gap", + "comparison": "diagnostic-code", + "evidence": [ + "oracle:synthetic/issue-33-f-string/oracle.json", + "provenance:synthetic/issue-33-f-string/fixture.json" + ], + "owner": "opy-rs#88", + "note": "The broader f-string lowering surface exceeds the independently evidenced compiler baseline.", + "failureClass": "integration", + "diagnosticCode": "unsupported-integration-surface" + }, + { + "fixture": "synthetic/issue-33-lambda-negative", + "nativeStatus": "failure", + "classification": "known-gap", + "comparison": "diagnostic-code", + "evidence": [ + "oracle:synthetic/issue-33-lambda-negative/oracle.json", + "provenance:synthetic/issue-33-lambda-negative/fixture.json" + ], + "owner": "opy-rs#88", + "note": "The source lambda negative fixture is retained, but its compiler-facing diagnostic contract is outside the #38 baseline.", + "failureClass": "frontend", + "diagnosticCode": "lambda-context" + }, + { + "fixture": "synthetic/preprocessing", + "nativeStatus": "success", + "classification": "known-gap", + "comparison": "semantic-wir", + "evidence": [ + "oracle:synthetic/preprocessing/oracle.json", + "provenance:synthetic/preprocessing/fixture.json" + ], + "owner": "opy-rs#85", + "note": "Included macro expansion now reaches canonical WIR through #85, but the pinned oracle's macro-emission semantics remain a separately tracked compiler gap.", + "semanticEquivalent": false + }, + { + "fixture": "synthetic/receiver-playervar", + "nativeStatus": "success", + "classification": "known-gap", + "comparison": "semantic-wir", + "evidence": [ + "oracle:synthetic/receiver-playervar/oracle.json", + "provenance:synthetic/receiver-playervar/fixture.json" + ], + "owner": "opy-rs#88", + "note": "The source receiver expression is preserved, but complete Workshop member lowering is outside the #38 baseline.", + "semanticEquivalent": false + } + ] +} diff --git a/compatibility/diff.py b/compatibility/diff.py index ec5eeb0..f46faf8 100644 --- a/compatibility/diff.py +++ b/compatibility/diff.py @@ -4,7 +4,9 @@ from __future__ import annotations import argparse +import hashlib import json +import re import shlex import subprocess import sys @@ -12,14 +14,24 @@ from pathlib import Path from typing import Any +import input_identity + ROOT = Path(__file__).resolve().parents[1] DEFAULT_FIXTURES = ROOT / "compatibility" / "fixtures" DEFAULT_REPORT = ROOT / "compatibility" / "report.json" DEFAULT_EXPECTATIONS = ROOT / "compatibility" / "differential-expectations.json" +DEFAULT_COMPILER_EXPECTATIONS = ROOT / "compatibility" / "compiler-expectations.json" EXPECTED_NATIVE_STATUSES = {"success", "failure"} EXPECTED_CLASSIFICATIONS = {"match", "known-gap", "unsupported"} +EXPECTED_COMPILER_COMPARISONS = { + "normalized-output", + "semantic-wir", + "diagnostic-code", + "compiler-contract", +} +CONCRETE_GAP_OWNER = re.compile(r"(?:opy-rs|workshop-rs)#[1-9][0-9]*") class DiffError(RuntimeError): @@ -107,6 +119,116 @@ def load_expectations(path: Path = DEFAULT_EXPECTATIONS) -> dict[str, dict[str, return by_fixture +def load_compiler_expectations( + path: Path = DEFAULT_COMPILER_EXPECTATIONS, +) -> dict[str, dict[str, Any]]: + data = load_json(path) + if data.get("schemaVersion") != 1 or data.get("contract") != "compiler": + raise DiffError(f"unsupported compiler expectation schema: {path}") + cases = data.get("cases") + if not isinstance(cases, list) or not cases: + raise DiffError(f"compiler expectations must contain cases: {path}") + + by_fixture: dict[str, dict[str, Any]] = {} + for case in cases: + if not isinstance(case, dict): + raise DiffError(f"compiler expectation must be an object: {path}") + fixture = case.get("fixture") + if not isinstance(fixture, str) or not fixture: + raise DiffError(f"compiler expectation fixture is invalid: {path}") + if fixture in by_fixture: + raise DiffError(f"duplicate compiler expectation: {fixture}") + native_status = case.get("nativeStatus") + if native_status not in EXPECTED_NATIVE_STATUSES: + raise DiffError(f"{fixture}: nativeStatus must be success or failure") + classification = case.get("classification") + if classification not in EXPECTED_CLASSIFICATIONS: + raise DiffError( + f"{fixture}: classification must be match, known-gap, or unsupported" + ) + comparison = case.get("comparison") + if comparison not in EXPECTED_COMPILER_COMPARISONS: + raise DiffError( + f"{fixture}: comparison must be normalized-output, semantic-wir, " + "diagnostic-code, or compiler-contract" + ) + evidence = case.get("evidence") + if not isinstance(evidence, list) or not evidence or not all( + isinstance(item, str) and item for item in evidence + ): + raise DiffError(f"{fixture}: evidence must be a non-empty string array") + owner = case.get("owner") + if not isinstance(owner, str) or not CONCRETE_GAP_OWNER.fullmatch(owner): + raise DiffError( + f"{fixture}: owner must be a concrete opy-rs or workshop-rs issue" + ) + note = case.get("note") + if not isinstance(note, str) or not note: + raise DiffError(f"{fixture}: note must be a non-empty string") + if comparison in {"normalized-output", "semantic-wir"} and native_status != "success": + raise DiffError(f"{fixture}: output comparisons require nativeStatus success") + if comparison == "semantic-wir": + semantic_equivalent = case.get("semanticEquivalent") + if not isinstance(semantic_equivalent, bool): + raise DiffError( + f"{fixture}: semantic-wir requires boolean semanticEquivalent" + ) + required_evidence = { + f"oracle:{fixture}/oracle.json", + f"provenance:{fixture}/fixture.json", + } + missing_evidence = sorted(required_evidence - set(evidence)) + if missing_evidence: + raise DiffError( + f"{fixture}: semantic-wir expectations require concrete evidence: " + + ", ".join(missing_evidence) + ) + if classification == "match" and not semantic_equivalent: + raise DiffError( + f"{fixture}: a semantic-wir match requires semanticEquivalent=true" + ) + if classification == "known-gap" and semantic_equivalent: + raise DiffError( + f"{fixture}: a semantic-wir known-gap requires semanticEquivalent=false" + ) + if classification != "match": + required_evidence = { + f"oracle:{fixture}/oracle.json", + f"provenance:{fixture}/fixture.json", + } + missing_evidence = sorted(required_evidence - set(evidence)) + if missing_evidence: + raise DiffError( + f"{fixture}: non-match expectations require concrete evidence: " + + ", ".join(missing_evidence) + ) + fixtures_root = path.parent / "fixtures" + oracle_path = fixtures_root / fixture / "oracle.json" + provenance_path = fixtures_root / fixture / "fixture.json" + oracle = load_json(oracle_path) + provenance = load_json(provenance_path) + if oracle.get("fixture") != fixture: + raise DiffError(f"{fixture}: oracle evidence fixture does not match") + if provenance.get("id") != fixture: + raise DiffError(f"{fixture}: provenance evidence fixture does not match") + try: + expected_input = input_identity.project_input( + fixtures_root / fixture, + provenance, + ) + except input_identity.InputIdentityError as error: + raise DiffError(f"{fixture}: invalid provenance source graph: {error}") from error + if oracle.get("input") != expected_input: + raise DiffError(f"{fixture}: oracle evidence input graph is stale") + if comparison == "diagnostic-code": + if not isinstance(case.get("diagnosticCode"), str) or not case["diagnosticCode"]: + raise DiffError(f"{fixture}: diagnostic-code requires diagnosticCode") + if not isinstance(case.get("failureClass"), str) or not case["failureClass"]: + raise DiffError(f"{fixture}: diagnostic-code requires failureClass") + by_fixture[fixture] = case + return by_fixture + + def require_result_shape(result: dict[str, Any], label: str) -> None: if result.get("schemaVersion") != 1: raise DiffError(f"{label}: unsupported or missing schemaVersion") @@ -124,12 +246,31 @@ def require_result_shape(result: dict[str, Any], label: str) -> None: raise DiffError(f"{label}: compile.diagnostics must be an array") if not isinstance(compile_result["workshop"], str): raise DiffError(f"{label}: compile.workshop must be a string") + if "semanticWIR" in compile_result: + raise DiffError(f"{label}: compatibility evidence must not be part of compile result") def result_path(results_root: Path, fixture_id: str) -> Path: return results_root / fixture_id / "result.json" +def validate_project_input( + fixtures_root: Path, + fixture_id: str, + metadata: dict[str, Any], + result: dict[str, Any], + label: str, +) -> dict[str, Any]: + try: + expected = input_identity.project_input(fixtures_root / fixture_id, metadata) + except input_identity.InputIdentityError as error: + raise DiffError(f"{fixture_id}: invalid source graph: {error}") from error + actual = result.get("input") + if actual != expected: + raise DiffError(f"{label} {fixture_id}: input source graph does not match fixture") + return expected + + def run_producer( command_template: str, fixture_id: str, @@ -288,12 +429,10 @@ def compare_fixture( require_result_shape(producer, f"producer {fixture_id}") if producer["fixture"] != fixture_id: raise DiffError(f"producer result for {fixture_id} has fixture {producer['fixture']!r}") - oracle_input = oracle.get("input") - producer_input = producer.get("input") - if not isinstance(oracle_input, dict) or not isinstance(producer_input, dict): - raise DiffError(f"{fixture_id}: both results must include input metadata") - if oracle_input.get("sha256") != producer_input.get("sha256"): - raise DiffError(f"{fixture_id}: producer input hash does not match oracle input") + validate_project_input(fixtures_root, fixture_id, metadata, oracle, "oracle") + validate_project_input(fixtures_root, fixture_id, metadata, producer, "producer") + oracle_input = oracle["input"] + producer_input = producer["input"] stages = compare_stage(oracle, producer) regression_stages = [item["name"] for item in stages if item["outcome"] == "regression"] @@ -338,7 +477,202 @@ def compare_fixture( } -def build_report(results: list[dict[str, Any]]) -> dict[str, Any]: +def compare_compiler_fixture( + fixtures_root: Path, + fixture_id: str, + results_root: Path, + expectations: dict[str, dict[str, Any]], +) -> dict[str, Any]: + metadata_path = fixtures_root / fixture_id / "fixture.json" + oracle_path = fixtures_root / fixture_id / "oracle.json" + metadata = load_json(metadata_path) + expectation = expectations.get(fixture_id) + if expectation is None: + raise DiffError(f"missing compiler expectation: {fixture_id}") + oracle = load_json(oracle_path) + require_result_shape(oracle, f"oracle {fixture_id}") + if oracle["fixture"] != fixture_id: + raise DiffError(f"oracle {oracle_path}: fixture id does not match path") + + path = result_path(results_root, fixture_id) + if not path.is_file(): + return { + "fixture": fixture_id, + "category": metadata.get("category", "unknown"), + "status": "inconclusive", + "expectedClassification": expectation["classification"], + "expectedNativeStatus": expectation["nativeStatus"], + "evidence": expectation["evidence"], + "owner": expectation["owner"], + "reason": f"missing producer result: {path}", + "stages": [], + } + producer = load_json(path) + require_result_shape(producer, f"producer {fixture_id}") + if producer["fixture"] != fixture_id: + raise DiffError(f"producer result for {fixture_id} has fixture {producer['fixture']!r}") + validate_project_input(fixtures_root, fixture_id, metadata, oracle, "oracle") + validate_project_input(fixtures_root, fixture_id, metadata, producer, "producer") + oracle_input = oracle["input"] + producer_input = producer["input"] + + native_compile = producer["compile"] + expected_status = expectation["nativeStatus"] + native_status = native_compile["status"] + stages = [ + stage( + "compile-status", + "match" if native_status == expected_status else "regression", + expected=expected_status, + producer=native_status, + ) + ] + contract = expectation["comparison"] + if native_status != expected_status: + status = "unexpected-divergence" + elif contract == "normalized-output": + oracle_output = oracle["compile"]["workshop"] + producer_output = native_compile["workshop"] + stages.append( + stage( + "normalized-output", + "match" if oracle_output == producer_output else "regression", + oracleSha256=_sha256(oracle_output), + producerSha256=_sha256(producer_output), + ) + ) + status = "match" if oracle_output == producer_output else "regression" + elif contract == "semantic-wir": + compatibility = producer.get("compatibility") + semantic = compatibility.get("semanticWIR") if isinstance(compatibility, dict) else None + oracle_input_sha256 = oracle["input"].get("sha256") + producer_input_sha256 = producer["input"].get("sha256") + if not isinstance(semantic, dict): + stages.append( + stage( + "semantic-wir", + "inconclusive", + reason="producer did not emit executable canonical-WIR evidence", + ) + ) + status = "inconclusive" + else: + input_matches = semantic.get("inputSha256") == producer_input_sha256 + reference_matches = ( + semantic.get("referenceInputSha256") == oracle_input_sha256 + ) + algorithm_matches = ( + semantic.get("schemaVersion") == 1 + and semantic.get("algorithm") + == "workshop-rs::roundtrip::equivalent" + ) + equivalent = semantic.get("equivalent") is True + reference_error = semantic.get("referenceError") + reference_parsed = reference_error is None + evidence_matches = ( + reference_parsed + and input_matches + and reference_matches + and algorithm_matches + and equivalent == expectation["semanticEquivalent"] + ) + expected_equivalent = expectation["semanticEquivalent"] + if not reference_parsed: + semantic_status = "inconclusive" + elif evidence_matches and expected_equivalent: + semantic_status = "match" + elif evidence_matches: + semantic_status = "accepted-gap" + else: + semantic_status = "regression" + stages.append( + stage( + "semantic-wir", + semantic_status, + algorithm=semantic.get("algorithm"), + inputSha256=semantic.get("inputSha256"), + referenceInputSha256=semantic.get("referenceInputSha256"), + equivalent=semantic.get("equivalent"), + expectedEquivalent=expected_equivalent, + referenceError=reference_error, + ) + ) + status = ( + "inconclusive" + if not reference_parsed + else "match" + if evidence_matches and expectation["classification"] == "match" + else expectation["classification"] + if evidence_matches + else "regression" + ) + elif contract == "diagnostic-code": + expected_class = expectation.get("failureClass") + expected_code = expectation.get("diagnosticCode") + actual_codes = [item.get("code") for item in native_compile["diagnostics"]] + class_matches = ( + expected_class is None or native_compile.get("failureClass") == expected_class + ) + code_matches = expected_code in actual_codes + stages.append( + stage( + "diagnostic-code", + "match" if class_matches and code_matches else "regression", + expectedClass=expected_class, + producerClass=native_compile.get("failureClass"), + expectedCode=expected_code, + producerCodes=actual_codes, + ) + ) + status = "match" if class_matches and code_matches else "regression" + else: + stages.append( + stage( + "compiler-contract", + "accepted-gap", + reason="compiler parity is outside the declared baseline", + evidence=expectation["evidence"], + ) + ) + status = expectation["classification"] + + if expectation["classification"] != "match" and status == "match": + status = expectation["classification"] + inconclusive_reason = next( + ( + item.get("reason") or item.get("referenceError") + for item in stages + if item["outcome"] == "inconclusive" + ), + None, + ) + return { + "fixture": fixture_id, + "category": metadata.get("category", "unknown"), + "status": status, + "expectedClassification": expectation["classification"], + "expectedNativeStatus": expected_status, + "referenceStatus": oracle["compile"]["status"], + "referenceGap": oracle["compile"]["status"] != native_status, + "evidence": expectation["evidence"], + "owner": expectation["owner"], + "comparison": contract, + "note": expectation["note"], + "reason": inconclusive_reason, + "regressionStages": [ + item["name"] for item in stages if item["outcome"] == "regression" + ], + "inconclusiveStages": [ + item["name"] for item in stages if item["outcome"] == "inconclusive" + ], + "stages": stages, + } + + +def build_report( + results: list[dict[str, Any]], + comparison_stages: list[str] | None = None, +) -> dict[str, Any]: by_stage: dict[str, dict[str, int]] = {} by_category: dict[str, dict[str, int]] = {} for result in results: @@ -357,7 +691,8 @@ def build_report(results: list[dict[str, Any]]) -> dict[str, Any]: "comparison": { "oracle": "fixture oracle snapshots", "producer": "opy-rs result producer contract", - "stages": [ + "stages": comparison_stages + or [ "compile-status", "diagnostics", "exact-output", @@ -375,6 +710,19 @@ def build_report(results: list[dict[str, Any]]) -> dict[str, Any]: } +def build_compiler_report(results: list[dict[str, Any]]) -> dict[str, Any]: + return build_report( + results, + [ + "compile-status", + "normalized-output", + "semantic-wir", + "diagnostic-code", + "compiler-contract", + ], + ) + + def run( fixtures_root: Path, report_path: Path, @@ -429,6 +777,59 @@ def run( return 0 +def run_compiler( + fixtures_root: Path, + report_path: Path, + results_root: Path, + expectations_path: Path = DEFAULT_COMPILER_EXPECTATIONS, + selected_ids: set[str] | None = None, + allow_inconclusive: bool = False, +) -> int: + all_ids = fixture_ids(fixtures_root) + expectations = load_compiler_expectations(expectations_path) + missing = sorted(set(all_ids) - set(expectations)) + extra = sorted(set(expectations) - set(all_ids)) + if missing or extra: + detail = [] + if missing: + detail.append(f"missing compiler expectations: {', '.join(missing)}") + if extra: + detail.append(f"compiler expectations for unknown fixtures: {', '.join(extra)}") + raise DiffError("; ".join(detail)) + ids = [ + fixture_id + for fixture_id in all_ids + if not selected_ids or fixture_id in selected_ids + ] + unknown = sorted((selected_ids or set()) - set(all_ids)) + if unknown: + raise DiffError(f"fixture not found: {', '.join(unknown)}") + + results = [ + compare_compiler_fixture(fixtures_root, fixture_id, results_root, expectations) + for fixture_id in ids + ] + report = build_compiler_report(results) + write_json(report_path, report) + print(json.dumps(report["summary"], indent=2, sort_keys=True)) + blocking = [ + result + for result in results + if result["status"] in {"regression", "unexpected-divergence"} + ] + if blocking: + for result in blocking: + stages = ", ".join(result.get("regressionStages", [])) or "native outcome" + print(f"REGRESSION {result['fixture']}: {stages}", file=sys.stderr) + return 1 + inconclusive = [result for result in results if result["status"] == "inconclusive"] + if inconclusive and not allow_inconclusive: + for result in inconclusive: + print(f"INCONCLUSIVE {result['fixture']}: {result['reason']}", file=sys.stderr) + return 2 + return 0 + + def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--fixtures", type=Path, default=DEFAULT_FIXTURES) diff --git a/compatibility/differential-expectations.json b/compatibility/differential-expectations.json index a1bf800..0ddb0a1 100644 --- a/compatibility/differential-expectations.json +++ b/compatibility/differential-expectations.json @@ -48,14 +48,14 @@ {"fixture": "real-world/6v6-adjustments", "nativeStatus": "failure", "classification": "match", "ruleNames": false, "evidence": ["oracle:real-world/6v6-adjustments/oracle.json", "provenance:real-world/6v6-adjustments/fixture.json"], "note": "The full project remains preserved as a failure corpus case with recorded provenance."}, {"fixture": "synthetic/issue-35-integration", "nativeStatus": "success", "classification": "match", "ruleNames": true, "evidence": ["oracle:synthetic/issue-35-integration/oracle.json", "implementation-invariant:opy-compiler-vertical-slice"], "note": "The OPY source implementation resolves the source fixture; the dedicated opy-compiler test independently lowers it through canonical WIR validation and deterministic workshop-rs emission."}, {"fixture": "synthetic/issue-40-structural", "nativeStatus": "success", "classification": "match", "ruleNames": false, "evidence": ["oracle:synthetic/issue-40-structural/oracle.json", "implementation-invariant:opy-compiler-structural-lowering"], "note": "The pinned oracle records subroutine source identity, deterministic explicit/implicit variable allocation, and player event filters; the dedicated opy-compiler test independently asserts those structures in canonical WIR."}, - {"fixture": "synthetic/issue-46-primitives", "nativeStatus": "success", "classification": "match", "ruleNames": false, "evidence": ["oracle:synthetic/issue-46-primitives/oracle.json", "implementation-invariant:opy-compiler-primitive-lowering", "test:opy-compiler-issue-46-oracle-equivalence"], "note": "The pinned oracle records the evidenced assignment and modification lowering (including **= and single-level indexed forms), value expressions, array indexing (including the firstOf index-0 read normalization), not-comparison negation, implicit default variables at fixed Workshop slots, and non-null variable initializers; null-default initializer semantics and deeper indexed writes remain explicit follow-ups. The opy-compiler test suite reparses both the native output and the oracle Workshop text through the canonical workshop-rs parser and requires structural equivalence."}, + {"fixture": "synthetic/issue-46-primitives", "nativeStatus": "success", "classification": "match", "ruleNames": false, "evidence": ["oracle:synthetic/issue-46-primitives/oracle.json", "implementation-invariant:opy-compiler-primitive-lowering", "test:opy-compiler-issue-46-native-wir-gap"], "note": "The pinned oracle records the evidenced assignment and modification lowering (including **= and single-level indexed forms), value expressions, array indexing (including the firstOf index-0 read normalization), not-comparison negation, implicit default variables at fixed Workshop slots, and non-null variable initializers; null-default initializer semantics and deeper indexed writes remain explicit follow-ups. The dedicated opy-compiler test compares native lowered WIR directly with the parsed oracle WIR and keeps the residual difference explicit."}, {"fixture": "synthetic/issue-46-unsupported", "nativeStatus": "success", "classification": "match", "ruleNames": false, "evidence": ["oracle:synthetic/issue-46-unsupported/oracle.json", "implementation-invariant:opy-compiler-primitive-lowering-limits"], "note": "Negative #46 probe: the source implementation resolves and the pinned oracle compiles the dict-indexed assignment, while the native compiler rejects the dict primitive with the stable source-attributed unsupported-integration-surface diagnostic."}, - {"fixture": "synthetic/issue-47-control-flow", "nativeStatus": "success", "classification": "match", "ruleNames": false, "evidence": ["oracle:synthetic/issue-47-control-flow/oracle.json", "test:opy-compiler-issue-47-oracle-equivalence"], "note": "The #47 control-flow slice resolves in the source implementation and lowers to canonical WIR with structural equivalence to the pinned oracle; direct loop/switch break and the nested switch shape are independently covered by opy-compiler tests."}, + {"fixture": "synthetic/issue-47-control-flow", "nativeStatus": "success", "classification": "match", "ruleNames": false, "evidence": ["oracle:synthetic/issue-47-control-flow/oracle.json", "implementation-invariant:issue-47-control-flow-hir"], "note": "The #47 control-flow slice resolves in the source implementation; compiler-level canonical-WIR parity for its residual switch shapes is tracked separately in the compiler expectation contract."}, {"fixture": "synthetic/issue-47-unsupported", "nativeStatus": "success", "classification": "match", "ruleNames": false, "evidence": ["oracle:synthetic/issue-47-unsupported/oracle.json", "test:opy-compiler-issue-47-nested-negative"], "note": "Negative #47 probe: the source implementation and pinned oracle accept the nested conditional switch-break HIR, while the compiler rejects the form because canonical WIR has no equivalent structured break carrier."}, - {"fixture": "synthetic/issue-47-switch-order", "nativeStatus": "success", "classification": "match", "ruleNames": true, "evidence": ["oracle:synthetic/issue-47-switch-order/oracle.json", "test:opy-compiler-issue-47-switch-order"], "note": "The #47 default-before-case probe preserves authored arm order and fallthrough in the source implementation; native and pinned Workshop both reparse to equivalent canonical WIR."}, - {"fixture": "synthetic/issue-47-switch-structured-target", "nativeStatus": "success", "classification": "match", "ruleNames": true, "evidence": ["oracle:synthetic/issue-47-switch-structured-target/oracle.json", "test:opy-compiler-issue-47-structured-switch-target"], "note": "The #47 structured switch probe places nested if/while actions in an earlier arm and verifies later case/default targets against canonical emitted action widths through native/oracle WIR equivalence."}, + {"fixture": "synthetic/issue-47-switch-order", "nativeStatus": "success", "classification": "match", "ruleNames": true, "evidence": ["oracle:synthetic/issue-47-switch-order/oracle.json", "implementation-invariant:issue-47-authored-switch-order"], "note": "The #47 default-before-case probe preserves authored arm order and fallthrough in the source implementation; compiler-level WIR equivalence is tracked separately because the native lowering still has an explicit residual gap."}, + {"fixture": "synthetic/issue-47-switch-structured-target", "nativeStatus": "success", "classification": "match", "ruleNames": true, "evidence": ["oracle:synthetic/issue-47-switch-structured-target/oracle.json", "implementation-invariant:issue-47-structured-switch-source-order"], "note": "The #47 structured switch probe preserves nested if/while actions and authored case/default target order in the source implementation; compiler-level WIR parity remains an explicit lowering gap."}, {"fixture": "synthetic/issue-47-switch-multiple-break", "nativeStatus": "success", "classification": "match", "ruleNames": true, "evidence": ["oracle:synthetic/issue-47-switch-multiple-break/oracle.json", "test:opy-compiler-issue-47-multiple-switch-break"], "note": "The source implementation preserves the multi-break source and the pinned oracle accepts it; the compiler rejects the later-reachable multi-target shape explicitly because workshop-rs v0.1.11 has one canonical else carrier."}, - {"fixture": "synthetic/issue-47-do-while-shapes", "nativeStatus": "success", "classification": "match", "ruleNames": true, "evidence": ["oracle:synthetic/issue-47-do-while-shapes/oracle.json", "test:opy-compiler-issue-47-do-while-break-shapes"], "note": "Direct, conditional, and nested do-while break shapes resolve and match the pinned Workshop after native and oracle reparsing through workshop-rs."}, + {"fixture": "synthetic/issue-47-do-while-shapes", "nativeStatus": "success", "classification": "match", "ruleNames": true, "evidence": ["oracle:synthetic/issue-47-do-while-shapes/oracle.json", "test:opy-compiler-issue-47-do-while-break-shapes"], "note": "Direct, conditional, and nested do-while break shapes resolve and match the pinned Workshop through direct native-WIR comparison with the parsed oracle."}, {"fixture": "synthetic/issue-47-do-while-invalid-placement", "nativeStatus": "failure", "classification": "match", "ruleNames": false, "evidence": ["oracle:synthetic/issue-47-do-while-invalid-placement/oracle.json", "test:opy-compiler-issue-47-invalid-do-while-placement"], "note": "The source implementation reports the stable source-attributed do-while-placement diagnostic for a non-prefix do-while."}, {"fixture": "census/workshop-feature-census", "nativeStatus": "success", "classification": "match", "ruleNames": true, "evidence": ["oracle:census/workshop-feature-census/oracle.json", "contract:workshop-rs#10-feature-census"], "note": "OPy source records opaque Workshop feature identities for the future workshop-rs lowering boundary."} ] diff --git a/compatibility/fixtures/census/workshop-feature-census/oracle.json b/compatibility/fixtures/census/workshop-feature-census/oracle.json index b67173f..9777a92 100644 --- a/compatibility/fixtures/census/workshop-feature-census/oracle.json +++ b/compatibility/fixtures/census/workshop-feature-census/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "census/workshop-feature-census", "input": { - "sha256": "0d05e6fe81bc4a4759f833b9dd22bfe76892bbb0a1ab24b23107c85528f09619", + "files": [ + { + "path": "workshop-feature-census.opy", + "sha256": "0d05e6fe81bc4a4759f833b9dd22bfe76892bbb0a1ab24b23107c85528f09619" + } + ], + "sha256": "eceffc7b71269344bd7918bcd396111beb9a61219c14542e2568d7b502e4411e", "source": "workshop-feature-census.opy" }, "oracle": { diff --git a/compatibility/fixtures/real-world/6v6-adjustments/oracle.json b/compatibility/fixtures/real-world/6v6-adjustments/oracle.json index 60800f3..882eeac 100644 --- a/compatibility/fixtures/real-world/6v6-adjustments/oracle.json +++ b/compatibility/fixtures/real-world/6v6-adjustments/oracle.json @@ -15,7 +15,477 @@ }, "fixture": "real-world/6v6-adjustments", "input": { - "sha256": "45a3c2a3ea23e092035c96c88b5a8b8c2c6545e75aa93dc085723dda5c6e30d1", + "files": [ + { + "path": "constants/adj_constants.opy", + "sha256": "b204295a573bce36a1dc8f3c88d08fe6d0dacff1c08bd6ebc114ba329a3d2573" + }, + { + "path": "constants/ow2_constants.opy", + "sha256": "0b7099b2844781bbdb16d2e59a1051e187e6c7e5fcfa2425662b22f721c363e9" + }, + { + "path": "heroes/ana/init.opy", + "sha256": "f04ef48e8b05d92171e52ad17312a9166c9b479cf22da63b42b2103797e3fa39" + }, + { + "path": "heroes/ana/nade.opy", + "sha256": "7183c3d4188fa34b092f56a79f0f4b25a443167ab422e8fcd772553ecb9dc820" + }, + { + "path": "heroes/ashe/init.opy", + "sha256": "06822e04f40ea7a1d87741e891e365f0eabb66ac668ebc23fffcb922e15f0e8d" + }, + { + "path": "heroes/baptiste/init.opy", + "sha256": "3e22f66b101507730c3c2e4f72ae6a907a22ba52b14dd07e1acf0d36cbb0fdf5" + }, + { + "path": "heroes/baptiste/regen_burst.opy", + "sha256": "25ada2bae59d896a4aafc38f3e3a3b98ebfe416396c633fec6d433a98e4b5301" + }, + { + "path": "heroes/bastion/init.opy", + "sha256": "3567e234c7e10a94cd720ce6fe39f52c86dd8ad39161acb1be6dbee0d0d42325" + }, + { + "path": "heroes/bastion/reconfigure.opy", + "sha256": "c797c657dd3e5e138f0174c6fc773bb53a9b75a71195ef207e6cd254c56debb3" + }, + { + "path": "heroes/brigitte/bash.opy", + "sha256": "b9e160416acbecaff18c9d55e6694f4ad18ff4a1b7c8d7150dc47014fdcb2e9e" + }, + { + "path": "heroes/brigitte/init.opy", + "sha256": "99ee256d00a5f38764b1c1e237720e92349d693476e57d42ea8457ba87f21a17" + }, + { + "path": "heroes/brigitte/inspire.opy", + "sha256": "5df1f1464b4035a6eb7f87584c3aea669e7e29579cc18f280eee893807a8506b" + }, + { + "path": "heroes/brigitte/repair_pack.opy", + "sha256": "904b181ba1215796b27d1a33056d90b22f2e1d78178622c1a7c7df461110f211" + }, + { + "path": "heroes/brigitte/whipshot.opy", + "sha256": "4446dc11009cef338b7a29a33bba96cd872f0ce966abcff2acf129010d4f2ea8" + }, + { + "path": "heroes/doomfist/init.opy", + "sha256": "51970d3b77b0c4400ce8f0c5ac94eb846c512d66337a99e9b2b5466e7cf4dd1d" + }, + { + "path": "heroes/doomfist/meteor_strike.opy", + "sha256": "7781707eae021507ae36c5247dec72709ec82d403c7f509a1b543eecb8758d92" + }, + { + "path": "heroes/doomfist/powerblock.opy", + "sha256": "8c27c6f3a247506655121f85a4c9ddb2123eaa0da31f54009724ce68f650e383" + }, + { + "path": "heroes/doomfist/punch.opy", + "sha256": "62ee636dfb13591711bc4fe21c986fa6388a939d8239ec276ba722cad90e7660" + }, + { + "path": "heroes/doomfist/slam.opy", + "sha256": "a556023a01b979aab60ccb5d917d5423a3c1e53ca44494a6012ef4896f185668" + }, + { + "path": "heroes/dva/boosters.opy", + "sha256": "109e7734b394e26d87017c40f08921c785090d70ac0ea740012a95d9f0616e8d" + }, + { + "path": "heroes/dva/defense_matrix.opy", + "sha256": "4a694a35522a2517371368b21f59740dd8f45d6b8a67404ea52c304aefb52362" + }, + { + "path": "heroes/dva/hp.opy", + "sha256": "4e0f5bb1490a15215d3298f2b4a8b1e3560bca7976b35158a7c851d918c01af3" + }, + { + "path": "heroes/dva/init.opy", + "sha256": "811b9efd68549c2aaca82a316ef85a98f24961635cf3610997df95091180758c" + }, + { + "path": "heroes/echo/duplicate.opy", + "sha256": "11224aa7188a1ad46472bb9f6d73cfd01b4bcad2b4eb210b7193c860e4c5312d" + }, + { + "path": "heroes/echo/init.opy", + "sha256": "8802cbd58d065261c2409153086cfa4d66eeffb3c6d91c3dc6fd63681acd8b6f" + }, + { + "path": "heroes/freja/init.opy", + "sha256": "9dd086735af89e39165c97a3764fbcb9846b532e6b10228f664f878f4a7ebbb1" + }, + { + "path": "heroes/genji/init.opy", + "sha256": "3562ccbd90111dbea676c018cd34a13e408e2a8c0b936617e2ecd0854a49694f" + }, + { + "path": "heroes/hanzo/init.opy", + "sha256": "e8a7e1ff7629f61167120dcdd88525c724a885d3281e03c29818906459b9929b" + }, + { + "path": "heroes/hazard/init.opy", + "sha256": "2b195e42eb6d8884424f5c95dce4858453a306ac7db46354bf4ec3a4d7930861" + }, + { + "path": "heroes/illari/init.opy", + "sha256": "18b6657d79f702b5ad680bcc5af2c9d9de884d3389230b9a7875b118560ffaa1" + }, + { + "path": "heroes/illari/solar_rifle.opy", + "sha256": "3363e8ee5a3d6fdc68160f013741a2e56ec15ffc3a28ed171b7293e747a73921" + }, + { + "path": "heroes/junkerqueen/carnage.opy", + "sha256": "cf8e192f9edfc1e7ab8e12180842d21a47c9a336cad474ae3d6c4b5793ecf212" + }, + { + "path": "heroes/junkerqueen/commanding_shout.opy", + "sha256": "d964de003661c41117b63a6486fd66fc5e459661e5b1c0ae667bc5983e65da74" + }, + { + "path": "heroes/junkerqueen/init.opy", + "sha256": "4e401d7aa542cf99477c7b4781c7e1596b54e7773b6c6a0559f6ed7069bf2b55" + }, + { + "path": "heroes/junkerqueen/jagged_blade.opy", + "sha256": "8868ddf544de8715dd4947ba95f965af4ed244d21aa7e46576d9b5a03d61907f" + }, + { + "path": "heroes/junkerqueen/rampage.opy", + "sha256": "a0a3d775e9ef8fa46899b9e9d8d5fbcfdd65780926c3b680ad1affd8c58df4a8" + }, + { + "path": "heroes/junkrat/init.opy", + "sha256": "7a0324386ba0d5507bce3664d0bd7c299a09676965d5f04e7a410aa7505696e3" + }, + { + "path": "heroes/junkrat/mayhem.opy", + "sha256": "8bc47d9493d3a1d707d382cdbe292b42090d636bd85b6bcba526a352abbd3ac6" + }, + { + "path": "heroes/juno/init.opy", + "sha256": "795ab8951a0f10c6730084405fa33ff889cade9d2cf9d07ba8825f7cf90a6070" + }, + { + "path": "heroes/juno/orbital_ray.opy", + "sha256": "d55f32378c4faa8a50426a81336ffc5b127064ad0ea25bfae07cfb51ea14e700" + }, + { + "path": "heroes/juno/torpedoes.opy", + "sha256": "ce16f4d9d731d92dc889af73e285762ed7b3d402f23fb722bde6d4d6a25d2225" + }, + { + "path": "heroes/kiriko/init.opy", + "sha256": "b017e259e821005a31bebd493201dab5eb3bd0096087b95ff16cce2ee9e487f9" + }, + { + "path": "heroes/kiriko/protection_suzu.opy", + "sha256": "3f50daf0a1694e0ea851b8a9e84f9cb498868234db426c6880641cff84e511b4" + }, + { + "path": "heroes/kiriko/swift_step.opy", + "sha256": "27c1c92d137db6ff44ccd99f279e28a60a4ce331689ce657f5a46acfbaa8f81e" + }, + { + "path": "heroes/lifeweaver/init.opy", + "sha256": "9437032822cc5fdecc46abfb9fb80ddbcd21fb571d0358b6a98d14dd0db84d3c" + }, + { + "path": "heroes/lucio/aura.opy", + "sha256": "0843fb8f9dc71dc7b730a61b30dedca7ea405f94ad6b86d4421e8c55a44d3dc1" + }, + { + "path": "heroes/lucio/init.opy", + "sha256": "7557d7caaa9eb7a1896f555f22470dd4d5a490b425539e986b49d997b7ca0886" + }, + { + "path": "heroes/mauga/cage_fight.opy", + "sha256": "cf1d04b3248c17180ac2b1f545d3beda4c5d70e5dacc207567dd495573c2845f" + }, + { + "path": "heroes/mauga/init.opy", + "sha256": "76c78838028e23e5790b18bfe249df316cc63bd25b3c468d6cdbb4029ec576f4" + }, + { + "path": "heroes/mccree/deadeye.opy", + "sha256": "24077e96eff9f4e65ac0b8e67b14585837462b275e7869c15c6471ee9a13c1fd" + }, + { + "path": "heroes/mccree/init.opy", + "sha256": "96adb0ae7b656f0941716de273d7e9f382f858487f42ee1f248b5140995b627b" + }, + { + "path": "heroes/mccree/peacekeeper.opy", + "sha256": "aab3df1d08f9960cec8b08194ecefbcef44b1c08140f51f0406bdd7da1950c4f" + }, + { + "path": "heroes/mei/cryo_freeze.opy", + "sha256": "0d19c28675a5b12f44bda5536e1719924b9241929677bd4e3abee251978c6bd2" + }, + { + "path": "heroes/mei/init.opy", + "sha256": "9279b206812bda7e09770e5a547383a69ae4bbe45da37e594f8c55af4a21f0a5" + }, + { + "path": "heroes/mercy/init.opy", + "sha256": "adeb0a0da805a8fb6561ac834deadb5e39d0462b761981b3952bdd75d4ffbad0" + }, + { + "path": "heroes/moira/coalescence.opy", + "sha256": "a7281154781a465614aa4518715532489ce0614268532df30573a1b9ab1a3763" + }, + { + "path": "heroes/moira/init.opy", + "sha256": "d22794d8e68238956a28d4ed69badd96b09c4c737259305f7485e7ff3947a030" + }, + { + "path": "heroes/orisa/fortify.opy", + "sha256": "9f3f92fee1b7df50e455009353aea594b2876ba059d3eabf827c9fcd87653467" + }, + { + "path": "heroes/orisa/init.opy", + "sha256": "763c89d4d47d8881034fd57289de2d238fc660adf92f66145ea50aa6f0362aa5" + }, + { + "path": "heroes/orisa/javelin.opy", + "sha256": "85f409c6e7639cb2b276ef99a03ddac2629b56529b87a14a5624b975d3652f02" + }, + { + "path": "heroes/orisa/terra_surge.opy", + "sha256": "987c74f4f1a7ac8d8d70c84522860a0c06a742bc8c6db7c468b90d6127ea7616" + }, + { + "path": "heroes/pharah/init.opy", + "sha256": "0068c17233451472968ef56449b9bd66446966d47722037eff0e234fbf387b93" + }, + { + "path": "heroes/ramattra/init.opy", + "sha256": "f944e6d5f8b0616125ac1857998a1f35b32779824918fc00a1e737613998ba25" + }, + { + "path": "heroes/ramattra/nemesis_form.opy", + "sha256": "ac199c07cd4a62a551fa60afc1036aca8208c48114578e11103c4f422e487a30" + }, + { + "path": "heroes/reaper/init.opy", + "sha256": "c5e4ea7c4008083ecedf281fc8da36308875426523a63516cd1ea50c7ea83145" + }, + { + "path": "heroes/reinhardt/charge.opy", + "sha256": "838e95a92e0adb2be435803c53f478cdeb9cf03b0dcd1af38a1359a22713f202" + }, + { + "path": "heroes/reinhardt/firestrike.opy", + "sha256": "cfa84ea46d8465d60e7bd0f3e42bd55dfa2e5c77a565cbf036c8469a59f14c27" + }, + { + "path": "heroes/reinhardt/init.opy", + "sha256": "aa5d6eea90116649ccc038f84e2b4c5c5d1d358c8ab78dbe52e8d5095e0da27e" + }, + { + "path": "heroes/reinhardt/shatter.opy", + "sha256": "f5af347762ab7d068c354ccf1a1cd8404505ba3936b6113423b41bcb922a5774" + }, + { + "path": "heroes/roadhog/breather.opy", + "sha256": "4fce21cb92a475462749a5c1df84fd30948c29fa6c80bb86dfce7c084b5f1076" + }, + { + "path": "heroes/roadhog/init.opy", + "sha256": "c0f759e25f8bf1c979dcb30bd2bed31e8563585d5de8c1b90ef85fa654957b14" + }, + { + "path": "heroes/roadhog/wholehog.opy", + "sha256": "d90db88f2976835a6acaa25b9c2aabe6c6f0645bd9980a1849f9235f77318e1c" + }, + { + "path": "heroes/sigma/accretion.opy", + "sha256": "86f0cdb215c902c61ea3e9b145f4e856cd3ccf0a3bd2e251fa2301c1c190c2a9" + }, + { + "path": "heroes/sigma/init.opy", + "sha256": "4a1bd2f4f6a3c60f80d2bd663215efefb8d41bb23f080ba24bdb5c43b2c6e581" + }, + { + "path": "heroes/sigma/kinetic_grasp.opy", + "sha256": "31c9ef9e37cfd7b5547d45e825e520183436ecf5160ea145bf86a8680e791e85" + }, + { + "path": "heroes/sojourn/init.opy", + "sha256": "aec06fc842fb78b96455b1e6f34d1bc2aa39503f6a611a797ce4572daee8119e" + }, + { + "path": "heroes/soldier/biotic_field.opy", + "sha256": "c6fc2d4e66076033606087a6dd702c24bb35dc16fae8e09a6ac559aa1de7cdf9" + }, + { + "path": "heroes/soldier/init.opy", + "sha256": "cca99af06dcb4cd48ca6d9f9e2dc6ddec1c307a390d380357abdfd15ed2632fe" + }, + { + "path": "heroes/sombra/init.opy", + "sha256": "c3f128e7b3730b6dbbb9e3fd7dbaaac4f3c25bb2159149776bbfb75fcfe452a5" + }, + { + "path": "heroes/sombra/translocator.opy", + "sha256": "3d2057cd1dd565816e723872b774bddef0dcac3e7ea1713c0621885e734a9c36" + }, + { + "path": "heroes/symmetra/init.opy", + "sha256": "31f37d6ac8b82312dbbeaa6d978abe570c11094a406b5763e7925a361ed35911" + }, + { + "path": "heroes/torbjorn/init.opy", + "sha256": "5895d6ea36646718105560bcd572247985c7b52d6c7661a44accd8fedd08ab42" + }, + { + "path": "heroes/tracer/init.opy", + "sha256": "b74fdfb187c2d71e8b9e3eb7f6a06d596e109b8bc287d9c57db2a11fb44afb70" + }, + { + "path": "heroes/vendetta/init.opy", + "sha256": "31a1cf0988dbf324674781890427e99e5d04bd0c4e669167c9b2a90ea7a6bdfc" + }, + { + "path": "heroes/venture/burrow.opy", + "sha256": "6a359b37c3fb111f4459f2e1182b99dfc0a133bd95a12d3d01b611a2b1a1c61d" + }, + { + "path": "heroes/venture/clobber.opy", + "sha256": "38bbd7798ac632a970a0c5db29033bc4b7165963d9ab258aafa12e229dea1154" + }, + { + "path": "heroes/venture/drill_dash.opy", + "sha256": "caf53f179567dafd7d8ab3dfec8dd9844d3ad2c0b54662d5443526d42cef4411" + }, + { + "path": "heroes/venture/excavator.opy", + "sha256": "b92a827ea646dd1b7b5771e2cf7b401fd318e0721f82632b19b33b885c48ee73" + }, + { + "path": "heroes/venture/init.opy", + "sha256": "87b839b7320a9c7fdf520123eecea9baf38c14048c85ed67b7bf5d27420283be" + }, + { + "path": "heroes/venture/tectonic_shock.opy", + "sha256": "941153326f7ab057fbf5096444cb7570714ce86284f0fe4f4a1e7cb7ef3f5c7a" + }, + { + "path": "heroes/widowmaker/init.opy", + "sha256": "4c623c225e1c988ecfcddeede5db63e556fcdc320fa56ba62f0103b5144738d4" + }, + { + "path": "heroes/winston/barrier.opy", + "sha256": "b4d328ba1481f5b52d2e447f60e6e0c34819f156007361c8bdb8ecfcf0707820" + }, + { + "path": "heroes/winston/init.opy", + "sha256": "3d718c50f8bb0013e7e83137d65e575461e2f1a772393f6aa6ebca8fb56a58d8" + }, + { + "path": "heroes/winston/primal.opy", + "sha256": "2f08b9592afd7da6dca1482efae832ce6e3326041db4874f4249f4a0735149dd" + }, + { + "path": "heroes/wreckingball/grapple.opy", + "sha256": "3ad509e40b72c5e4ebd500817f260b259580754695ed1b14b302a7260cbf4667" + }, + { + "path": "heroes/wreckingball/init.opy", + "sha256": "317229534679bb89dff7f1845ac5a08c72b80515b61174d1508bfa9755e0c321" + }, + { + "path": "heroes/wreckingball/piledriver.opy", + "sha256": "71928f3cd2fca422183ced10231140c630daf65d7d589d362b67b900901266ea" + }, + { + "path": "heroes/wreckingball/shields.opy", + "sha256": "f052c50ca26ddee150b0079b50e649491f1ec612c556edb4670fea54149869e8" + }, + { + "path": "heroes/wuyang/init.opy", + "sha256": "1c9c3d5b679c3391a9b6871b15bf5bb125b0351b8a1e577fc81f92eb3631b0a4" + }, + { + "path": "heroes/zarya/ally_bubble.opy", + "sha256": "98311612f10e8b2f05ab347cb17045d34733b85cf5cc4f1b59ae17f836cb6673" + }, + { + "path": "heroes/zarya/init.opy", + "sha256": "817d0193f2f0448f9583c526b81992ad65d010b979cb987df13820b8e697319b" + }, + { + "path": "heroes/zarya/self_bubble.opy", + "sha256": "d626ee3ed61e981042b56dbb4999c5accfceba395f27f9292703051bbf7864f5" + }, + { + "path": "heroes/zenyatta/init.opy", + "sha256": "7d233d3f5d990140a1acacbdb85672b7beb626ddef6ff7a0105b0bbca0e38ce8" + }, + { + "path": "heroes/zenyatta/transc.opy", + "sha256": "b632e797f8854cc2c371289f506195b8a604629292b01a57ed424882bbc02714" + }, + { + "path": "lobby/lobby.opy", + "sha256": "a321bb32f1e1f08f7c4d64d038591f7bacdd99fdd09f6177ab1e8c42da1ab86d" + }, + { + "path": "main.opy", + "sha256": "45a3c2a3ea23e092035c96c88b5a8b8c2c6545e75aa93dc085723dda5c6e30d1" + }, + { + "path": "passives/damage/damage_passive.opy", + "sha256": "a8c1d6f5147a7d66a1208df6531d6aa6964a79d0c7a6526af4fe5459e7ce772a" + }, + { + "path": "passives/support/self_heal.opy", + "sha256": "f66e19a2945ba3b71cc2029b813f99f402a0d38a91caae3a87eb528a4ed2d5cd" + }, + { + "path": "passives/tank/knockback.opy", + "sha256": "3d4653e4770c0a80ae97434b4d5f136f2816f97ca13e6acddaa1f8edc2955855" + }, + { + "path": "passives/tank/ult_charge.opy", + "sha256": "2abd6898561ff6c1c41164e79418dfd856314adf025c6efb3ca7dd3f55949dc1" + }, + { + "path": "utilities/anti_crash.opy", + "sha256": "1b1198ac7cc49d51b76a41c7781c8b0b288e59e5f63dba94379d729b1b2bb7bd" + }, + { + "path": "utilities/custom_hp.opy", + "sha256": "c34d4fe926d8488ce0e129da05caebfcfd02dad9c453374d378ad17904238786" + }, + { + "path": "utilities/hero_init.opy", + "sha256": "ba216e632946715b3af0b2c19546ba1204542636a32121af931b4d73793f7295" + }, + { + "path": "utilities/hero_reset.opy", + "sha256": "ad62ad2e229d414a2daf6bd5328b726c00154c06ab3888d52f7cbaa771a01241" + }, + { + "path": "utilities/hero_switch.opy", + "sha256": "d5b9a260539103bb243fe59f900fa999528f25b267264273a90aa73302d4dac2" + }, + { + "path": "utilities/macro_functions.opy", + "sha256": "dd1986db2f65df66f8d4883c60e3c403ca15f0f1e7d6cecf39cace2d1bc4f8b2" + }, + { + "path": "utilities/watermark.opy", + "sha256": "7db2d7bcdea9f4bed11f0bd01837acd4127a07cd80cc84a9b465278772fce889" + } + ], + "sha256": "ac6706a4aac869f6f6599215e4dbd8dd8bc78de1503bde93ff79ab62260c81e3", "source": "main.opy" }, "oracle": { diff --git a/compatibility/fixtures/real-world/overpy-broken-weapons/oracle.json b/compatibility/fixtures/real-world/overpy-broken-weapons/oracle.json index 5ce752c..06ada51 100644 --- a/compatibility/fixtures/real-world/overpy-broken-weapons/oracle.json +++ b/compatibility/fixtures/real-world/overpy-broken-weapons/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "real-world/overpy-broken-weapons", "input": { - "sha256": "4b966f2b755545143c7a9fafdbb7b5d5e85604910f14d2532d92eea0854ae427", + "files": [ + { + "path": "broken_weapons.opy", + "sha256": "4b966f2b755545143c7a9fafdbb7b5d5e85604910f14d2532d92eea0854ae427" + } + ], + "sha256": "08448dbca223b16df836d84b1a690b11dc5d93df7861739b7112401a2243c5dc", "source": "broken_weapons.opy" }, "oracle": { diff --git a/compatibility/fixtures/real-world/overpy-cake/oracle.json b/compatibility/fixtures/real-world/overpy-cake/oracle.json index 82ca87d..52329a3 100644 --- a/compatibility/fixtures/real-world/overpy-cake/oracle.json +++ b/compatibility/fixtures/real-world/overpy-cake/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "real-world/overpy-cake", "input": { - "sha256": "aa1baf73b47136fc5fd01f87458657fb4f4940e939ee6cafaa4eadc6ea742975", + "files": [ + { + "path": "source.opy", + "sha256": "aa1baf73b47136fc5fd01f87458657fb4f4940e939ee6cafaa4eadc6ea742975" + } + ], + "sha256": "2e51bf1c73d1b0e1d7654dba99870679c902976ebfe49574129c0a30a29a8bd4", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/real-world/overpy-client-to-server/oracle.json b/compatibility/fixtures/real-world/overpy-client-to-server/oracle.json index 5ccfb0e..bb5e36f 100644 --- a/compatibility/fixtures/real-world/overpy-client-to-server/oracle.json +++ b/compatibility/fixtures/real-world/overpy-client-to-server/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "real-world/overpy-client-to-server", "input": { - "sha256": "3ef442054f57c1a9f9905f11da4d5e27726564c5bab44c74ebfab56825b94781", + "files": [ + { + "path": "clientToServer.opy", + "sha256": "3ef442054f57c1a9f9905f11da4d5e27726564c5bab44c74ebfab56825b94781" + } + ], + "sha256": "e050e138bcae63eb09c9d13991d0c8ffa7da9ba0e50deeb50a2c88ea7b8934a3", "source": "clientToServer.opy" }, "oracle": { diff --git a/compatibility/fixtures/real-world/overpy-cronch/oracle.json b/compatibility/fixtures/real-world/overpy-cronch/oracle.json index 79282c8..9b12bee 100644 --- a/compatibility/fixtures/real-world/overpy-cronch/oracle.json +++ b/compatibility/fixtures/real-world/overpy-cronch/oracle.json @@ -15,7 +15,13 @@ }, "fixture": "real-world/overpy-cronch", "input": { - "sha256": "413d3d598ae08f3682acf925b7294316a68aba7c9a959e95d66b461485ba9169", + "files": [ + { + "path": "cronch.opy", + "sha256": "413d3d598ae08f3682acf925b7294316a68aba7c9a959e95d66b461485ba9169" + } + ], + "sha256": "2c9764fa13d484ae244a1a9907c208e2c5731024e9898f5ab27597faa633cc30", "source": "cronch.opy" }, "oracle": { diff --git a/compatibility/fixtures/real-world/overpy-crosshair/oracle.json b/compatibility/fixtures/real-world/overpy-crosshair/oracle.json index 9657454..2ec6c97 100644 --- a/compatibility/fixtures/real-world/overpy-crosshair/oracle.json +++ b/compatibility/fixtures/real-world/overpy-crosshair/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "real-world/overpy-crosshair", "input": { - "sha256": "d2c22b59c11fee75ae4febc96c94126059f4e5c5f64cc2e5a28e939968ed8f8f", + "files": [ + { + "path": "crosshair.opy", + "sha256": "d2c22b59c11fee75ae4febc96c94126059f4e5c5f64cc2e5a28e939968ed8f8f" + } + ], + "sha256": "b9f9e7c69754b5fd59389c7794d29cc0eb6dff90b8b5f6d6103a9293351b10fe", "source": "crosshair.opy" }, "oracle": { diff --git a/compatibility/fixtures/real-world/overpy-inputhud/oracle.json b/compatibility/fixtures/real-world/overpy-inputhud/oracle.json index c7edf2f..d7fe466 100644 --- a/compatibility/fixtures/real-world/overpy-inputhud/oracle.json +++ b/compatibility/fixtures/real-world/overpy-inputhud/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "real-world/overpy-inputhud", "input": { - "sha256": "ae7a0e004cc80e61d256d0ff2509f51bdc96b27d60a93c9215b327436e7eefc1", + "files": [ + { + "path": "inputhud.opy", + "sha256": "ae7a0e004cc80e61d256d0ff2509f51bdc96b27d60a93c9215b327436e7eefc1" + } + ], + "sha256": "4aaaad4f0395dfa9410eb1b535dc05d542068b6138d080b9bb1813004fc7ae46", "source": "inputhud.opy" }, "oracle": { diff --git a/compatibility/fixtures/real-world/overpy-meipocalypse/oracle.json b/compatibility/fixtures/real-world/overpy-meipocalypse/oracle.json index c04609f..8fba7ab 100644 --- a/compatibility/fixtures/real-world/overpy-meipocalypse/oracle.json +++ b/compatibility/fixtures/real-world/overpy-meipocalypse/oracle.json @@ -15,7 +15,53 @@ }, "fixture": "real-world/overpy-meipocalypse", "input": { - "sha256": "f982574116462c2796b8800a18f35ed8a55c599f2785c1ef7ca5ee6b21e5b604", + "files": [ + { + "path": "barricades.opy", + "sha256": "98c6af05fdf0459efc371666e60b736adbe3d845aec6a0f05b9fec4f0c16100f" + }, + { + "path": "debug.opy", + "sha256": "b5f78f0049bf965fbb1ebf1ae55b4a524523c8770094455e5780a31bf5e152f3" + }, + { + "path": "debug_settings.opy", + "sha256": "96ec104b525c7a24b64049354334b53afa9cd6ee7eaed0afc2709dc7d1c6e459" + }, + { + "path": "fightforyourlife.opy", + "sha256": "7b6c69554922c81cea80283c95b0dc3a8641fca5edbb00019fc2456337ad25fd" + }, + { + "path": "heroUnlock.opy", + "sha256": "ba49692b62356fa821e20b925b097fde8b5ce5ff1d9bb30d4dfe563ceeff8283" + }, + { + "path": "mei_types.opy", + "sha256": "39b1b890831a674c299930f7ceb9dc77e335147a914fb544767dfdbf2fbb4381" + }, + { + "path": "meipocalypse.opy", + "sha256": "f982574116462c2796b8800a18f35ed8a55c599f2785c1ef7ca5ee6b21e5b604" + }, + { + "path": "settings.opy", + "sha256": "429e51da582c24bb52112bd6db9f2ef759e70621e3f5ddb543533aa53ed19634" + }, + { + "path": "shop.opy", + "sha256": "f84b1cb88cb6c935ecbaf9dced48cbdd9327616a698a4d50b984a844a1c13b1c" + }, + { + "path": "waves.opy", + "sha256": "0c6fe7d2d5bdd83805413bbdb3d62a26055b6b01e17874759acd2d2a68338c09" + }, + { + "path": "zones.opy", + "sha256": "a4f3d898c31cdcdb427ab02d986d786fa312acd1a00e2cc8dcc2c2f6aac0aba7" + } + ], + "sha256": "6d248490a57cc1a57b0b52cfafe464eb2b633b2b0226c2850d315e8a7654416c", "source": "meipocalypse.opy" }, "oracle": { diff --git a/compatibility/fixtures/real-world/overpy-parabola/oracle.json b/compatibility/fixtures/real-world/overpy-parabola/oracle.json index d88cd83..8f28a0e 100644 --- a/compatibility/fixtures/real-world/overpy-parabola/oracle.json +++ b/compatibility/fixtures/real-world/overpy-parabola/oracle.json @@ -15,7 +15,13 @@ }, "fixture": "real-world/overpy-parabola", "input": { - "sha256": "2dce50c374a00e3d3e664f4753f736f788cfb2d0df3a2149b139e6f8cb484940", + "files": [ + { + "path": "parabola.opy", + "sha256": "2dce50c374a00e3d3e664f4753f736f788cfb2d0df3a2149b139e6f8cb484940" + } + ], + "sha256": "6b7921247aa2e70147223c0b401b232f3820bf7a5d2be369142e5a0840d18b0e", "source": "parabola.opy" }, "oracle": { diff --git a/compatibility/fixtures/real-world/overpy-pixelart/oracle.json b/compatibility/fixtures/real-world/overpy-pixelart/oracle.json index 10fa098..cada75e 100644 --- a/compatibility/fixtures/real-world/overpy-pixelart/oracle.json +++ b/compatibility/fixtures/real-world/overpy-pixelart/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "real-world/overpy-pixelart", "input": { - "sha256": "b9b7c9b0dea9ebd4734a88a10eb8639f32eec8119aefee44333a73cee7ac6502", + "files": [ + { + "path": "pixelart.opy", + "sha256": "b9b7c9b0dea9ebd4734a88a10eb8639f32eec8119aefee44333a73cee7ac6502" + } + ], + "sha256": "84118801f779dbc0729ee8c6d8d4ebe232f271d9bb6c7fa5069db2a79dd76789", "source": "pixelart.opy" }, "oracle": { diff --git a/compatibility/fixtures/real-world/overpy-santa/oracle.json b/compatibility/fixtures/real-world/overpy-santa/oracle.json index c7838b6..ce92c9c 100644 --- a/compatibility/fixtures/real-world/overpy-santa/oracle.json +++ b/compatibility/fixtures/real-world/overpy-santa/oracle.json @@ -15,7 +15,13 @@ }, "fixture": "real-world/overpy-santa", "input": { - "sha256": "63d9a92ba2c59423857322c4b6d651a2e86863ed040e8af585599a364f11fe0e", + "files": [ + { + "path": "santa.opy", + "sha256": "63d9a92ba2c59423857322c4b6d651a2e86863ed040e8af585599a364f11fe0e" + } + ], + "sha256": "47ed906272f1be049283a5e8d16b87d64caceccbc945a12d18464b6587f13793", "source": "santa.opy" }, "oracle": { diff --git a/compatibility/fixtures/real-world/overpy-zencopter/oracle.json b/compatibility/fixtures/real-world/overpy-zencopter/oracle.json index 7e898e8..a284249 100644 --- a/compatibility/fixtures/real-world/overpy-zencopter/oracle.json +++ b/compatibility/fixtures/real-world/overpy-zencopter/oracle.json @@ -15,7 +15,13 @@ }, "fixture": "real-world/overpy-zencopter", "input": { - "sha256": "246f74a946b097c299853dc3d46d1a722cbdc6f97baf7e095986cda6963cfe96", + "files": [ + { + "path": "heli.opy", + "sha256": "246f74a946b097c299853dc3d46d1a722cbdc6f97baf7e095986cda6963cfe96" + } + ], + "sha256": "de71cf04bd24a6f2483af1a66fdae26129cff5b877b69ccd4ea53dcea0b1850c", "source": "heli.opy" }, "oracle": { diff --git a/compatibility/fixtures/real-world/ow1-emulator/oracle.json b/compatibility/fixtures/real-world/ow1-emulator/oracle.json index ab16a1c..d5c3c2b 100644 --- a/compatibility/fixtures/real-world/ow1-emulator/oracle.json +++ b/compatibility/fixtures/real-world/ow1-emulator/oracle.json @@ -15,7 +15,585 @@ }, "fixture": "real-world/ow1-emulator", "input": { - "sha256": "612035e575745d38a59efc8a55a3670e2e706e04c28df668652baea92ea1088f", + "files": [ + { + "path": "1v1_main.opy", + "sha256": "612035e575745d38a59efc8a55a3670e2e706e04c28df668652baea92ea1088f" + }, + { + "path": "common/constants/ow1_constants.opy", + "sha256": "98e12b80222b54bcc308a5819ee3f8ba732f2299ffc679bb13cc6fe2d73b8b25" + }, + { + "path": "common/constants/ow2_constants.opy", + "sha256": "bb5077e2973ba9dcb28db8088b7eb814679520074b87f8173ce67614fe227871" + }, + { + "path": "common/env.opy", + "sha256": "8ea0be608a2d98129aed782ae6436ed714d00850f0a4932539bca913378226d2" + }, + { + "path": "common/macro_functions.opy", + "sha256": "e9d8ce1047fc11cf769e485f5082624cda8d65e0a29d4c5ea03400883541f8f3" + }, + { + "path": "heroes/ana/init.opy", + "sha256": "2a3be36d72d4cab43a019b9c9ffb138ac2b01fda7efbe7f553d9dfd1ad2c464d" + }, + { + "path": "heroes/ana/nade.opy", + "sha256": "ef26cb62bfc35db5348d8290a44fefd3e1ff079dd3c3de48168e404a67989291" + }, + { + "path": "heroes/ashe/bob.opy", + "sha256": "7e9af3163a703a9411e66ca071dc8994a2a65394d82c683127d684cfe4486bca" + }, + { + "path": "heroes/ashe/init.opy", + "sha256": "7733accf871a0fea38114d1f82d858f51fb5a2b3aeedbb82e35af5f9e89d57a7" + }, + { + "path": "heroes/ashe/viper.opy", + "sha256": "ca808ed6e7c3702960e4d87f07fa69d56145c50777fbd927ba91160cc89a3fdb" + }, + { + "path": "heroes/baptiste/init.opy", + "sha256": "3301b928192c055f0be6099fcb2d4ab646dfcb51e1fc7b89ad4a42474f3f1f07" + }, + { + "path": "heroes/baptiste/regen_burst.opy", + "sha256": "c25b618f2832637b2a8d7a8d7f774e79cb2cfb721631675ca045a3bd7b744e4e" + }, + { + "path": "heroes/bastion/configuration.opy", + "sha256": "5ca765efee20b463cf2298e0abf676f8e9f10020ebec0a64116730fd76522698" + }, + { + "path": "heroes/bastion/init.opy", + "sha256": "2b9bb31905c88b207034f9c71d8911cf5a35cf5e94382b4363749302a830423f" + }, + { + "path": "heroes/bastion/machine_gun.opy", + "sha256": "3be05d3ebf637279d8f873ad8c25948fccb37ddc15a81ad2512c3edab5a6815e" + }, + { + "path": "heroes/bastion/recon.opy", + "sha256": "7fb7c1534dae75cf3eac146b816cc39d387c613224a15c161925ce92114c5dde" + }, + { + "path": "heroes/bastion/repair.opy", + "sha256": "4fc44a4f3036d644bf3cb2c4be0f04d07b7c172b498fa82f9a34aeaddfa5054d" + }, + { + "path": "heroes/bastion/sentry.opy", + "sha256": "7e473bdcf18925f07034fceb73aea9089ca0e77a1cb4f82374d0627ed8991082" + }, + { + "path": "heroes/bastion/tank.opy", + "sha256": "3df2d3200925be255e2e27d2843aa415a74a9f196db9c8ca86601eed8348f0d1" + }, + { + "path": "heroes/brigitte/bash.opy", + "sha256": "c9bbd0ba0ec4055ea78b500e358efd7b82914711a9ff55d7894a81fa3e80230f" + }, + { + "path": "heroes/brigitte/flail.opy", + "sha256": "b071482a2b1b4ecf4bfa1dbe0b90920e064b7dcdc0477ac92d118fed1be94e59" + }, + { + "path": "heroes/brigitte/init.opy", + "sha256": "9891e7bf3ed5639579d2cdcddd26a4f12fc328a299ad40ae7d8faebc7dd3d9c5" + }, + { + "path": "heroes/brigitte/inspire.opy", + "sha256": "c2b6d735d8e7ccbe13bb551d3f964946a196571618c6a07c47f55592f26119e5" + }, + { + "path": "heroes/brigitte/rally.opy", + "sha256": "49e5511e57f0809863fee3f89309881c3dc9d43831ceaaab13fb5fe28f4109d8" + }, + { + "path": "heroes/brigitte/repair_pack.opy", + "sha256": "3ad14e7703b5f6aad2e736bee490d295940f15f131de5c49e7273a3b70d25b0d" + }, + { + "path": "heroes/cassidy/deadeye.opy", + "sha256": "1b268dd1501ad1611f2f3907b96533de2035ab528de0f10dfcc6ecd59317a8e1" + }, + { + "path": "heroes/cassidy/flashbang.opy", + "sha256": "ec69d12d30731a088eee76b1bd94b99ac5433b92b33e57daafdcc15dcc4fe125" + }, + { + "path": "heroes/cassidy/init.opy", + "sha256": "f10c872aa44ee4ee9d8b9fc586ef16876e5003e1bb6dc37eaeb467ba3654f4fd" + }, + { + "path": "heroes/cassidy/peacekeeper.opy", + "sha256": "2745a8bb0ef6d727883eeec1e6d51c9a8706fe1a1ee413fa95f2eab3512a3a69" + }, + { + "path": "heroes/cassidy/roll.opy", + "sha256": "8f170580db46e1b127bcd08da88294d3a89713ec94cf5e527274bd1dd1e0ee4f" + }, + { + "path": "heroes/doomfist/cannon.opy", + "sha256": "9e7f73c1f595130329258121f3548caafefa13d1cd3711e853bf2c6fd75ee3f6" + }, + { + "path": "heroes/doomfist/defense.opy", + "sha256": "4e8857e41c2dd60acd2c75fc3af10358cd676684a187252dc09d0c95dac5a67e" + }, + { + "path": "heroes/doomfist/init.opy", + "sha256": "c86015d0c552ba93e87e163aca080ec994fe357ae5cbd37cfea1ad05f404577a" + }, + { + "path": "heroes/doomfist/meteor.opy", + "sha256": "e3b426133f2800dfffa4a59aeaedead27aab6cc54f4e0286e02910c03f1ebcd1" + }, + { + "path": "heroes/doomfist/punch.opy", + "sha256": "13f6b09fa3c64cb73d7ce66198577e1396ecafa08a8d714168485f556647507e" + }, + { + "path": "heroes/doomfist/slam.opy", + "sha256": "34d71292127720ee12329b2d0b1e5be463f4fe9ff29e439b1b89f616897766a2" + }, + { + "path": "heroes/doomfist/uppercut.opy", + "sha256": "cde0eff1f8b3daafd1202a23c34de11fb3e3a52b367b80abc4d97f6128992b87" + }, + { + "path": "heroes/dva/bomb.opy", + "sha256": "ff5f3b47d658812505e7c9a9c58ae9333d224431fe645bf9d33e84e38bc7ba7f" + }, + { + "path": "heroes/dva/booster.opy", + "sha256": "540eff4ebe9652b8ef0686c8e7a02aeab9709ad246f0bfa79937f3b358772dcd" + }, + { + "path": "heroes/dva/gun.opy", + "sha256": "25a253d7080d86cee60f16898412f8e52ded7db80d71d207385bae084a0b769e" + }, + { + "path": "heroes/dva/hp.opy", + "sha256": "94b652c0433f2aa7e5ed6e82ebb9d3e1ca75821db2cc095c985cf1b5b25cdf1f" + }, + { + "path": "heroes/dva/init.opy", + "sha256": "0e0a7c3f7516df52ce58a77a9b1e4f945fc5d5a9863db5c1803c4e11f04e8b1f" + }, + { + "path": "heroes/dva/micro_missiles.opy", + "sha256": "75bbf2858c16672c540c2e018316dee6e6ca0139a3f88f2180090023a9d6a40b" + }, + { + "path": "heroes/echo/duplicate.opy", + "sha256": "f63fb56cf65df95aa9fd515ab756b7645400a568fb0d1b305faebb79ec57b4eb" + }, + { + "path": "heroes/echo/focusing_beam.opy", + "sha256": "acce661f0ff841938518898f26e17638acfeb7e28ed9d353d357f9bb28106b8d" + }, + { + "path": "heroes/echo/init.opy", + "sha256": "e44a56264d6121992856c0f69419d221fc4fc12ef91ed4114590c2420a3b7a61" + }, + { + "path": "heroes/genji/blade.opy", + "sha256": "a8bc792a9d5eabd5e0871391e7b7348902d654bb0b1bbd6b6cdf3ebc7941bc26" + }, + { + "path": "heroes/genji/init.opy", + "sha256": "a6cbde5d5c8bf2497bfe29e9eedd11e367ffd32c01703b4c26668d41b9dba12e" + }, + { + "path": "heroes/hanzo/init.opy", + "sha256": "aec1a3aea7fa6f63bd0184c3d47eb6b79203dad61b5e238f77736df2a4457715" + }, + { + "path": "heroes/hanzo/storm.opy", + "sha256": "8fc8775a50246629eff98f7552d2a323bf54f3f895de8caaf4cb5c99401a3eac" + }, + { + "path": "heroes/illari/init.opy", + "sha256": "a57ac0ed708d22ba865dfcc64ffdd4f7d50b1b7b3ed863e424bc148f3134783d" + }, + { + "path": "heroes/illari/solar_rifle.opy", + "sha256": "29a9a8a3db241b326bce732cd21b2e0e9a79044a3f9a65fda70be66d62d8c4e3" + }, + { + "path": "heroes/illari/sun.opy", + "sha256": "05dd9e2b4bed02cd903235ddf3defb05bf99a1c453c42d529b6dd63ec8f3fb17" + }, + { + "path": "heroes/init.opy", + "sha256": "c28276033d1691af42ee4e5786ce754fa3e1d9183169a59b2e0300a6369d64f3" + }, + { + "path": "heroes/junkerqueen/carnage.opy", + "sha256": "6746d78e054b25113a1b22d5e2ea496d230fe759ca9e7ec83088a18aff4217f7" + }, + { + "path": "heroes/junkerqueen/init.opy", + "sha256": "03a48b3572ced11d6fd51cb4e4fb5d6ed6db8f0e5233584df5a234ccf0a3145d" + }, + { + "path": "heroes/junkrat/init.opy", + "sha256": "e89e983d68f98e5186d32e281d623d4324e73e0eabbd14ae873879ac292161bb" + }, + { + "path": "heroes/junkrat/trap.opy", + "sha256": "96c4e2f6e3d0982ea9cf31a411bb817ca94812420c1bfc24af425b1c68f6cc51" + }, + { + "path": "heroes/kiriko/init.opy", + "sha256": "9daba9ed51b0cbdd30a0441b8e6be4492bebb3b9a4f1f3bb233acc1f7c557c18" + }, + { + "path": "heroes/kiriko/kunai.opy", + "sha256": "cb127a7cbb717f4d7a454f8dce7a286c4ed077509f90146b4d39a3f1577b3b8a" + }, + { + "path": "heroes/kiriko/swift_step.opy", + "sha256": "628f94cec88f639b6e1431c121e660b3204c67b67999c36ef03ea12923463203" + }, + { + "path": "heroes/lifeweaver/init.opy", + "sha256": "a2d708edc8761283f3000f06f6059e940e4f0a5403ffaa5252341b62f368b76e" + }, + { + "path": "heroes/lucio/crossfade.opy", + "sha256": "84da980f4b5de16eb0ae7dd04db045531def10772f19d54b79766f7e9c41a158" + }, + { + "path": "heroes/lucio/init.opy", + "sha256": "4314cf4fc5456c78d65de395a3e6b1cb9494dfa480da5977d2e88ca391f4db58" + }, + { + "path": "heroes/mauga/init.opy", + "sha256": "f12b55cac7fda72deb2806af392ca7eb570a7ffe0ad9f1ee3adea21fa7c1aca7" + }, + { + "path": "heroes/mei/blaster.opy", + "sha256": "5baf1be6f1f33d55d58c7f649325446740eb5041f75d81dee28b1f3b0e45caf3" + }, + { + "path": "heroes/mei/cryo_freeze.opy", + "sha256": "969a7f76ecb42186810124aa59b5ce5e3a8ea0474339b6961421287eb5e4465a" + }, + { + "path": "heroes/mei/init.opy", + "sha256": "8c30ed8ecd9c574fb3087b3b3281facd4b1dd9f27bf018173c4d47c865bd2222" + }, + { + "path": "heroes/mercy/guardian.opy", + "sha256": "2e8142958e22bd59cc68a93c16982b164c092b9856e9f1ccde8263b7e7889929" + }, + { + "path": "heroes/mercy/init.opy", + "sha256": "27c35d5e5fb5314223df983d81aaee77f386a554bb3ff69dcade6ef984e4850d" + }, + { + "path": "heroes/moira/biotic_grasp.opy", + "sha256": "9483f30663ae84584405961f86cc2495e25f9619382857025723570c8c3cb8d7" + }, + { + "path": "heroes/moira/coalescence.opy", + "sha256": "75736064c0322954fd8390146948ced9ad88ad30b6d639e10256efd1e7246e89" + }, + { + "path": "heroes/moira/init.opy", + "sha256": "cc2f3c937369c5c4cb79cad29f666396833d6cf545ec6c710782d2c3ba79f116" + }, + { + "path": "heroes/orisa/barrier.opy", + "sha256": "0eda0c46b1f5013fb293090b576c0bd51b70d306da6c2e17f8ea0322f4dea167" + }, + { + "path": "heroes/orisa/fortify.opy", + "sha256": "e140453c9d0a1ae6a08787331cb346042129c5ea1bac617f04b906f914d5aea6" + }, + { + "path": "heroes/orisa/fusion_driver.opy", + "sha256": "6a8a8d5c83b3a88ac4b5c36f5dc3dfdd7092566519debc2377b8fa606e66d8d2" + }, + { + "path": "heroes/orisa/halt.opy", + "sha256": "29a9e8324bf09de80e806a7a53789d1e58d6ccf7c94e9efab01d708efe3410bb" + }, + { + "path": "heroes/orisa/init.opy", + "sha256": "aa3ce0dc59217cbf267f4148cf699695ebea89ca95596704cce7422704880de6" + }, + { + "path": "heroes/orisa/supercharger.opy", + "sha256": "2169e5f5edd0c02dbcd3bdd4bb6fedd233f1abf083e6f93dfe00ad729e3481b2" + }, + { + "path": "heroes/pharah/barrage.opy", + "sha256": "4b80910a0c5c3372830ff383c1841db7c5d02f4b327418a0b9d98f33f6c127b6" + }, + { + "path": "heroes/pharah/concussive.opy", + "sha256": "2559036805e7d9185e2a7fe8ac31ee822fc16e182efbb61eea2ba7814f1989fd" + }, + { + "path": "heroes/pharah/hover_jets.opy", + "sha256": "725293ab33d737a60a96fafb1439652971f01a8e5b1f171eb4c504f74f049f6f" + }, + { + "path": "heroes/pharah/init.opy", + "sha256": "6b2039d3e2c484c72dbd061bfe41eadf1d7680212c6484a6b008bcace36b6af8" + }, + { + "path": "heroes/pharah/rocket_launcher.opy", + "sha256": "e9cc8e29d25c1b0383c0b1bb6581040aef5175723eba2840213004aff29654e1" + }, + { + "path": "heroes/ramattra/init.opy", + "sha256": "3c3573a95527ea136b6eb60d5d915e99df6a81883440515a0ca924356535e7e2" + }, + { + "path": "heroes/ramattra/nemesis.opy", + "sha256": "534795ceeb8e6f1261bd066bcd6e1a91937c25bb9e92db5a6c732e9064cfd116" + }, + { + "path": "heroes/ramattra/vortex.opy", + "sha256": "2adc063888563e3c2f2a195bd4b15e21001cf06104a42c3705744b164c80e1f6" + }, + { + "path": "heroes/reaper/blossom.opy", + "sha256": "f684f2a10ce62352c938298a241f7aedc846f39dd89945cc006bdb5b4856d5a5" + }, + { + "path": "heroes/reaper/init.opy", + "sha256": "f6e3e7b6c5acf11390bade41e20a2725e129c2ec016f779dd01174e958d83d7d" + }, + { + "path": "heroes/reaper/shadowstep.opy", + "sha256": "7b68a115516c3a44d6030883cbcc18f21a52750f52aaccc2d45ea8770cea1efe" + }, + { + "path": "heroes/reinhardt/charge.opy", + "sha256": "cfbd42d990aceb79628f5ec04d1d7f97e9693020773e3d272f4714f7ceeb0131" + }, + { + "path": "heroes/reinhardt/firestrike.opy", + "sha256": "4e02e5a9e8a0abd33f8f197a37ec82f26c52f0e56afab908919c3ead44e8258c" + }, + { + "path": "heroes/reinhardt/hammer.opy", + "sha256": "0cc18dde6b06528627d92bf247e7263c550e923c5b4c8f797735b7d072596e68" + }, + { + "path": "heroes/reinhardt/init.opy", + "sha256": "28f0d0a748532f10a59fe4ebe5c31911adacd7c7758b6b4bcab0a5a1594e3684" + }, + { + "path": "heroes/reinhardt/shatter.opy", + "sha256": "062b3709d2b3c16b7ab83b1c66d56f593db209ebb134654a3b8906fdcafe985d" + }, + { + "path": "heroes/roadhog/breather.opy", + "sha256": "be117a458a995aee55e0d8307b730780629b43dd811f3e9138e4162aad4f959c" + }, + { + "path": "heroes/roadhog/hook.opy", + "sha256": "d0da64a2f7042c6e3f3ba4639e80ae0fcc8c1b1e3d1c0234c4d9452932a03807" + }, + { + "path": "heroes/roadhog/init.opy", + "sha256": "d6474ee76e4b4e7e8460259db8206ff1c7d74a5ed34314f93fea89cba0f3ae02" + }, + { + "path": "heroes/roadhog/wholehog.opy", + "sha256": "b125965bf869b1a9149ccac9cf627f75a75afe7f2076b4e6ef9dc0d089886dc2" + }, + { + "path": "heroes/sigma/init.opy", + "sha256": "70c862f515d9d9d9c119d3918e31251f61a0147f1eae20bf2f997e7177f836f0" + }, + { + "path": "heroes/sojourn/init.opy", + "sha256": "0eb63dfab3c40194d9ff2a9270eed3938de1c653a78d5c70f8aac07acd6dfedf" + }, + { + "path": "heroes/sojourn/railgun.opy", + "sha256": "60aa5150d9285fb007d0038eca2ca94eab77a34973b6af5b25e3a84bcddbefd1" + }, + { + "path": "heroes/soldier/init.opy", + "sha256": "399d997794b656ed39a237e6e2852ac77ffa4b04d293ea9b329ab1fe3f30e3aa" + }, + { + "path": "heroes/soldier/pulse_rifle.opy", + "sha256": "20119e4da3a922bfa76525ec413cbd62971ca9128e0f0fa1f92ad4176b88da43" + }, + { + "path": "heroes/soldier/sprint.opy", + "sha256": "84aeedfd52cd6f83af8557e8e86489dd0a2c3a25c64afbee2a53e51ce4ac9a5f" + }, + { + "path": "heroes/sombra/emp.opy", + "sha256": "be343e1a9f23d1b8d9a9ea0a62dc4787fff06267fb6c2a979ae246a3f8ed087b" + }, + { + "path": "heroes/sombra/hack.opy", + "sha256": "37ddc37c8e2249c3dea1a1c55ea3f6c71ab4931196961e40a02db5825786b726" + }, + { + "path": "heroes/sombra/init.opy", + "sha256": "2f7157c70fd62fcf023fe8a34e5baf557df94aeb64880a9524f2f6901dcac08c" + }, + { + "path": "heroes/sombra/stealth.opy", + "sha256": "f03a7a7bf56ba4986285c2f27992d2d8b435820c5b85944929886712080acbff" + }, + { + "path": "heroes/sombra/translocator.opy", + "sha256": "7a33d571b54865d1966a14c3ea99e490814d9e23d7ba905380b4fd0a73ac68e9" + }, + { + "path": "heroes/symmetra/init.opy", + "sha256": "156dff452ccbe623526b1c85ad79075909261d6eedf366d4bbf3db19fe92fb4f" + }, + { + "path": "heroes/symmetra/orb.opy", + "sha256": "aca8a29ff88124bd5bf705aeee76c6b5179dbc64749e1211b3ae02509ff3ef4b" + }, + { + "path": "heroes/symmetra/projector.opy", + "sha256": "f36e2e09e093cd65b51b216ed41c749ae0f59ac50dcc6099954e02144e08ad8d" + }, + { + "path": "heroes/symmetra/teleporter.opy", + "sha256": "87ef724136983e525123227f4a5b2819e2e48690093eeb4373e4ecc28b8a59a3" + }, + { + "path": "heroes/torbjorn/init.opy", + "sha256": "9bcf6303c6ac6dd0b5eaba0f9c3f91dd2a7435aefef5fcbca0194b2ca1377756" + }, + { + "path": "heroes/torbjorn/overload.opy", + "sha256": "fa2c8b9c584df5f7df5fff427a92a46cccef97aabf0ea6c74c716fca5441478b" + }, + { + "path": "heroes/tracer/init.opy", + "sha256": "60b3f7df437b104889e826b81caca7b5585c96297971d80a1feeb199d2001f1c" + }, + { + "path": "heroes/tracer/pulse_pistol.opy", + "sha256": "e26725fe5f18a3a9f5f6466074c7489c212d798966cd8a274852b2b11a541b4e" + }, + { + "path": "heroes/venture/init.opy", + "sha256": "d26a84ff1ea40653e748059b5e19d1aefddacaf023dc1cecf9045b36d4832734" + }, + { + "path": "heroes/widowmaker/init.opy", + "sha256": "e83d69c624bece4a9d36f3e004bdb16ac06274acdf9e55fcbb45db3f3dae01db" + }, + { + "path": "heroes/winston/barrier.opy", + "sha256": "492b6edc3a58aefe019be9d9ff41de63843d74392b9433c02d6a02bfacfd8064" + }, + { + "path": "heroes/winston/init.opy", + "sha256": "4b165fee0bfd0c05b4f24c99cbcaec462e87cf4e9c8d3df29722e8f30c67f1c5" + }, + { + "path": "heroes/winston/leap.opy", + "sha256": "f2960739521418978d06639d9af6a9ec38f50647000438d5bd5b4b77b0187f14" + }, + { + "path": "heroes/wreckingball/adaptive_shield.opy", + "sha256": "d2f4a01a2843529dca67f8d991ee412795047c3ed4795fec462e11856cc4bf22" + }, + { + "path": "heroes/wreckingball/grapple.opy", + "sha256": "66928b73fcfe35215f612733e1d8c0fb1606a4b23207ea0859db87bfc9eb1f18" + }, + { + "path": "heroes/wreckingball/init.opy", + "sha256": "e60df4657cb19f10c901144ee9bab835d82916ee31f8b0dd8c1d82b5a9c65aba" + }, + { + "path": "heroes/zarya/ally_bubble.opy", + "sha256": "e6b51efd1364b1e874c0a9fe7f7e858f7e5b0b306e55a4050aba778c40cf292f" + }, + { + "path": "heroes/zarya/init.opy", + "sha256": "305cadd757fbfbaa75468452f0fdbc9c7530768e43bc6e408f8a7dbd9494f491" + }, + { + "path": "heroes/zarya/self_bubble.opy", + "sha256": "82b53510d1ae844c5f4af2770e0206a7334647a872ccf9de2ebe94169084815f" + }, + { + "path": "heroes/zenyatta/init.opy", + "sha256": "bbe54cddb8aec0ff0c029472954cd5e111883a559af71c49348f0616fffd3d18" + }, + { + "path": "misc/arena.opy", + "sha256": "af06aca8f77e2adac805b676dc6569534900a03d5be017bd8c792e62ef0ddff4" + }, + { + "path": "passives/global/global_passive.opy", + "sha256": "64a09fd27f415818eb123de7319e19c8cddf662fff25d8c9534db59c9ac91508" + }, + { + "path": "passives/support/self_heal.opy", + "sha256": "b3ec2e5a1cdaa7bbe84b5133ad18156b86f95f83c7f1f4995ed89639299add92" + }, + { + "path": "passives/tank/ult_charge.opy", + "sha256": "6e3dbc73ad167ab8df0b8777d49cb4bfe06fbb31ee63bc9d6607ac0633ee71c1" + }, + { + "path": "utilities/anti_crash.opy", + "sha256": "4057a4255c80f3cba014def5e410a1299dbf570b646119735068f5264f8da032" + }, + { + "path": "utilities/custom_hp.opy", + "sha256": "ec8cd66e25e36c5ea64be548214ef1c5b5f4b7a3f55ef35d36844e887b2dae7c" + }, + { + "path": "utilities/emu_helper.opy", + "sha256": "c60c775606e640085d0b26544f5e1d6849b1e14c2a144b4c06364e1829dcb7b4" + }, + { + "path": "utilities/hero_roster.opy", + "sha256": "80db1a9519370c87203c8e31d25636fc3229049fa011a12c8b10554f2857fe05" + }, + { + "path": "utilities/hero_switch.opy", + "sha256": "f3c53687bc86584be5fb39725ab1e8455e4f3867c93538e8c3870c9136ab3345" + }, + { + "path": "utilities/hit_detection.opy", + "sha256": "0b2119cef8136a8cff5402f4d601b2fc5c76635c4e05171a6b9e84317ffb9b8d" + }, + { + "path": "utilities/ready.opy", + "sha256": "9949eb83cde4b59fbb2fb8344b5a833f5b4b27f4951dabb8ad756ff13ea030d6" + }, + { + "path": "utilities/reset.opy", + "sha256": "212a6604bdd1f95bf899344b5ffcd1442ebe8f45e0e153b415cbe56d7cd5777e" + }, + { + "path": "utilities/role_lock.opy", + "sha256": "eb48c6767978333a611192da2f5b22cdc02dc4ecf0a65c772e5dfcdf28b3b187" + }, + { + "path": "utilities/watermark.opy", + "sha256": "fcb773484da735c66916280a2d9be7fe3a8dd8db47859ba217bf6135c49ad298" + } + ], + "sha256": "d1cbe2c8902843cde6f1360e1cfa48a7bf31374691d0217acc28834c28df2ca2", "source": "1v1_main.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/basic-rule/oracle.json b/compatibility/fixtures/synthetic/basic-rule/oracle.json index e3b8738..fa25c5b 100644 --- a/compatibility/fixtures/synthetic/basic-rule/oracle.json +++ b/compatibility/fixtures/synthetic/basic-rule/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "synthetic/basic-rule", "input": { - "sha256": "3f39a0286864f70ce5b5369cbaf663037b1def9984c4e4a0da3da2bb5553dcc4", + "files": [ + { + "path": "source.opy", + "sha256": "3f39a0286864f70ce5b5369cbaf663037b1def9984c4e4a0da3da2bb5553dcc4" + } + ], + "sha256": "901f8a6dfa185b1f5b5537695693036e083222653c703debeab4145635c01bdc", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/chase-condition-agentlab/oracle.json b/compatibility/fixtures/synthetic/chase-condition-agentlab/oracle.json index 48e13cc..6bf5246 100644 --- a/compatibility/fixtures/synthetic/chase-condition-agentlab/oracle.json +++ b/compatibility/fixtures/synthetic/chase-condition-agentlab/oracle.json @@ -15,7 +15,13 @@ }, "fixture": "synthetic/chase-condition-agentlab", "input": { - "sha256": "5aa7ad4f0cb028b57e750557ef6410c0c39dd1fd39572a875ef9cb0c746d7ae6", + "files": [ + { + "path": "source.opy", + "sha256": "5aa7ad4f0cb028b57e750557ef6410c0c39dd1fd39572a875ef9cb0c746d7ae6" + } + ], + "sha256": "a76b2a958f8d594ed4bcc3537442eb8282f67878ae3110806a81f85fca3f450a", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/chase-enums/oracle.json b/compatibility/fixtures/synthetic/chase-enums/oracle.json index 321e5b8..5bcd1a5 100644 --- a/compatibility/fixtures/synthetic/chase-enums/oracle.json +++ b/compatibility/fixtures/synthetic/chase-enums/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "synthetic/chase-enums", "input": { - "sha256": "45e545039ff2338659c5fbcb78bf9dd86ca0ecd56df19a63219a4971285e0365", + "files": [ + { + "path": "source.opy", + "sha256": "45e545039ff2338659c5fbcb78bf9dd86ca0ecd56df19a63219a4971285e0365" + } + ], + "sha256": "2ed5934d1779f94c68ab35874fea15e8b681ef07be4a5cd1494e5ba11d41037d", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/chase-keywords/oracle.json b/compatibility/fixtures/synthetic/chase-keywords/oracle.json index c93d064..fc20e30 100644 --- a/compatibility/fixtures/synthetic/chase-keywords/oracle.json +++ b/compatibility/fixtures/synthetic/chase-keywords/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "synthetic/chase-keywords", "input": { - "sha256": "cfe40674f580b176241ebe3270a68dcd1f0e3feb4a3abc8bcba24642691f8bba", + "files": [ + { + "path": "source.opy", + "sha256": "cfe40674f580b176241ebe3270a68dcd1f0e3feb4a3abc8bcba24642691f8bba" + } + ], + "sha256": "c3798d40c6c3d81cb1959592dd08234573dd65a7f37e442c5384f3fe2915329c", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/control-flow/oracle.json b/compatibility/fixtures/synthetic/control-flow/oracle.json index 876c2f9..fa34263 100644 --- a/compatibility/fixtures/synthetic/control-flow/oracle.json +++ b/compatibility/fixtures/synthetic/control-flow/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "synthetic/control-flow", "input": { - "sha256": "81abea756fbec67cb1502142e7b8dd0e7abd6b803e49562c01840bed6afc0150", + "files": [ + { + "path": "source.opy", + "sha256": "81abea756fbec67cb1502142e7b8dd0e7abd6b803e49562c01840bed6afc0150" + } + ], + "sha256": "220eb532ddb1b608f5255c37caf7c3e75813f4a699f8a33be33b1926ab153e48", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/declarations-numbers/oracle.json b/compatibility/fixtures/synthetic/declarations-numbers/oracle.json index 69f9ac1..77f89dc 100644 --- a/compatibility/fixtures/synthetic/declarations-numbers/oracle.json +++ b/compatibility/fixtures/synthetic/declarations-numbers/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "synthetic/declarations-numbers", "input": { - "sha256": "83d1851238535bdfcc419dbb7eada23a71ec5777ccf437f2767924c1d9be59b5", + "files": [ + { + "path": "source.opy", + "sha256": "83d1851238535bdfcc419dbb7eada23a71ec5777ccf437f2767924c1d9be59b5" + } + ], + "sha256": "72a36cb4b38639babb060a132a660bce5f3d0afa11199ab25828740c9df4e2a9", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/declarations-rules/oracle.json b/compatibility/fixtures/synthetic/declarations-rules/oracle.json index c4d7e67..ea4ba32 100644 --- a/compatibility/fixtures/synthetic/declarations-rules/oracle.json +++ b/compatibility/fixtures/synthetic/declarations-rules/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "synthetic/declarations-rules", "input": { - "sha256": "055b7b34908b35f1549c88bdcc8d3a8aea0edf767c722a1f0ad2b04d8d1dea48", + "files": [ + { + "path": "source.opy", + "sha256": "055b7b34908b35f1549c88bdcc8d3a8aea0edf767c722a1f0ad2b04d8d1dea48" + } + ], + "sha256": "4d4666efcc550290af42daddbdba3501b4c59f0f4b62b775123f572f61920926", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/diagnostics/oracle.json b/compatibility/fixtures/synthetic/diagnostics/oracle.json index a72c882..1e8fb04 100644 --- a/compatibility/fixtures/synthetic/diagnostics/oracle.json +++ b/compatibility/fixtures/synthetic/diagnostics/oracle.json @@ -15,7 +15,13 @@ }, "fixture": "synthetic/diagnostics", "input": { - "sha256": "3b482dfd7f5b28e89fecb86ffb629f864ea27f280a3638523763e8401a9d3158", + "files": [ + { + "path": "source.opy", + "sha256": "3b482dfd7f5b28e89fecb86ffb629f864ea27f280a3638523763e8401a9d3158" + } + ], + "sha256": "86f930b798ff365f4a75d8e818028afc6c7d19e082e8885b566fc678ca70c6bf", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/expressions-values/oracle.json b/compatibility/fixtures/synthetic/expressions-values/oracle.json index 5a7aa72..4179aa7 100644 --- a/compatibility/fixtures/synthetic/expressions-values/oracle.json +++ b/compatibility/fixtures/synthetic/expressions-values/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "synthetic/expressions-values", "input": { - "sha256": "d114352687e3c1b52bd284283e27b83b6079fffb23583127664471f0ab5b42d4", + "files": [ + { + "path": "source.opy", + "sha256": "d114352687e3c1b52bd284283e27b83b6079fffb23583127664471f0ab5b42d4" + } + ], + "sha256": "bdcbcd7f068858394c91eaee546aa6d775bfc71d8a8110463ad0abb8c5836b88", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/for-range-agentlab/oracle.json b/compatibility/fixtures/synthetic/for-range-agentlab/oracle.json index 229db53..d750156 100644 --- a/compatibility/fixtures/synthetic/for-range-agentlab/oracle.json +++ b/compatibility/fixtures/synthetic/for-range-agentlab/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "synthetic/for-range-agentlab", "input": { - "sha256": "8cfabbc9369bd2af0f39b2f15dcdc44edcc380891740e4b03edb3080c1400561", + "files": [ + { + "path": "source.opy", + "sha256": "8cfabbc9369bd2af0f39b2f15dcdc44edcc380891740e4b03edb3080c1400561" + } + ], + "sha256": "45ccb6d87a6cead7c35650c7f25b896eba83861eaede63b35a0045bfe462fc95", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/issue-28-invalid-syntax/oracle.json b/compatibility/fixtures/synthetic/issue-28-invalid-syntax/oracle.json index defc09c..cded013 100644 --- a/compatibility/fixtures/synthetic/issue-28-invalid-syntax/oracle.json +++ b/compatibility/fixtures/synthetic/issue-28-invalid-syntax/oracle.json @@ -15,7 +15,13 @@ }, "fixture": "synthetic/issue-28-invalid-syntax", "input": { - "sha256": "4c37a67406cb59376baabd986880ff71999f664456c3d1662cab47e47a371765", + "files": [ + { + "path": "source.opy", + "sha256": "4c37a67406cb59376baabd986880ff71999f664456c3d1662cab47e47a371765" + } + ], + "sha256": "32cc3774e3fbd6f0ad28d2e727b11614f14dfd82e6bf872b9cb9201425bec975", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/issue-28-string-modifiers/oracle.json b/compatibility/fixtures/synthetic/issue-28-string-modifiers/oracle.json index 9e3e424..99407ca 100644 --- a/compatibility/fixtures/synthetic/issue-28-string-modifiers/oracle.json +++ b/compatibility/fixtures/synthetic/issue-28-string-modifiers/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "synthetic/issue-28-string-modifiers", "input": { - "sha256": "a1067d0f682d3baf415c99b980d9b6a253acad47fff340a7b6e06e7af0843c68", + "files": [ + { + "path": "source.opy", + "sha256": "a1067d0f682d3baf415c99b980d9b6a253acad47fff340a7b6e06e7af0843c68" + } + ], + "sha256": "2cde6ae6699e81051e9890099279e1d7b26291549d0978a08312a0a752dcb6fa", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/issue-28-syntax/oracle.json b/compatibility/fixtures/synthetic/issue-28-syntax/oracle.json index 1f86ec3..f4f8663 100644 --- a/compatibility/fixtures/synthetic/issue-28-syntax/oracle.json +++ b/compatibility/fixtures/synthetic/issue-28-syntax/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "synthetic/issue-28-syntax", "input": { - "sha256": "edf9128e1aa5b6e2ad8d672e00dad15e58517daec7b1a5ae763d0030a57e5573", + "files": [ + { + "path": "source.opy", + "sha256": "edf9128e1aa5b6e2ad8d672e00dad15e58517daec7b1a5ae763d0030a57e5573" + } + ], + "sha256": "17d1328f76d60e0ee02325019468783e052e239b825f8cd504221518d2c278a0", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/issue-29-directives/oracle.json b/compatibility/fixtures/synthetic/issue-29-directives/oracle.json index 7f9f657..a96e5f2 100644 --- a/compatibility/fixtures/synthetic/issue-29-directives/oracle.json +++ b/compatibility/fixtures/synthetic/issue-29-directives/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "synthetic/issue-29-directives", "input": { - "sha256": "1d2f7a6fc4f2685dfbd928f1c558a7435d7df2d8e40424fe57041b8bef362c52", + "files": [ + { + "path": "source.opy", + "sha256": "1d2f7a6fc4f2685dfbd928f1c558a7435d7df2d8e40424fe57041b8bef362c52" + } + ], + "sha256": "1cc01729133baade27a0ab958b40a985bf4245bc50ee220751159a9165ad2bad", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/issue-29-invalid/oracle.json b/compatibility/fixtures/synthetic/issue-29-invalid/oracle.json index 24c0650..3af84f9 100644 --- a/compatibility/fixtures/synthetic/issue-29-invalid/oracle.json +++ b/compatibility/fixtures/synthetic/issue-29-invalid/oracle.json @@ -15,7 +15,13 @@ }, "fixture": "synthetic/issue-29-invalid", "input": { - "sha256": "2c3977deb6a40d8a4d38f5b4873ce89e388543e967f2643975d449490704d462", + "files": [ + { + "path": "source.opy", + "sha256": "2c3977deb6a40d8a4d38f5b4873ce89e388543e967f2643975d449490704d462" + } + ], + "sha256": "9c8eee6b2ce5f1d8df009b2d58a154aa8947d07d136b40d0d4eeabfefcea177c", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/issue-29-main-file/oracle.json b/compatibility/fixtures/synthetic/issue-29-main-file/oracle.json index c688167..3378c13 100644 --- a/compatibility/fixtures/synthetic/issue-29-main-file/oracle.json +++ b/compatibility/fixtures/synthetic/issue-29-main-file/oracle.json @@ -10,7 +10,21 @@ }, "fixture": "synthetic/issue-29-main-file", "input": { - "sha256": "0656503322a79c4a7eb9d7fd04bf797b5998ebbed82f0757b15e53d61a8eb2ce", + "files": [ + { + "path": "child.opy", + "sha256": "0640aaad875c72ebdd04d58df369045cd7a70575f37697a308f4850868354dff" + }, + { + "path": "entry.opy", + "sha256": "339950d8191b54ed0aea8cad0e01d544db92c2113be7af0019b15414325b513c" + }, + { + "path": "source.opy", + "sha256": "0656503322a79c4a7eb9d7fd04bf797b5998ebbed82f0757b15e53d61a8eb2ce" + } + ], + "sha256": "54c880a0c81287f4c710f2772f2dcb80b4a524f1b55d55f12a62ed06f46553a8", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/issue-31-negative/oracle.json b/compatibility/fixtures/synthetic/issue-31-negative/oracle.json index f98b651..e828617 100644 --- a/compatibility/fixtures/synthetic/issue-31-negative/oracle.json +++ b/compatibility/fixtures/synthetic/issue-31-negative/oracle.json @@ -15,7 +15,13 @@ }, "fixture": "synthetic/issue-31-negative", "input": { - "sha256": "613387a057b0ba8958db08b661435b5c53d69f0d4c6b5510214c89589d8fe2ad", + "files": [ + { + "path": "source.opy", + "sha256": "613387a057b0ba8958db08b661435b5c53d69f0d4c6b5510214c89589d8fe2ad" + } + ], + "sha256": "e90535d7af9c12f0570fcb36a7093bc78f9426444f7179c2705ddac215c8063b", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/issue-31-nested-scope/oracle.json b/compatibility/fixtures/synthetic/issue-31-nested-scope/oracle.json index b28c34c..a98ac21 100644 --- a/compatibility/fixtures/synthetic/issue-31-nested-scope/oracle.json +++ b/compatibility/fixtures/synthetic/issue-31-nested-scope/oracle.json @@ -10,7 +10,21 @@ }, "fixture": "synthetic/issue-31-nested-scope", "input": { - "sha256": "933baf034eb509b4bb09610645dbe20a24dbae9e903e91bbfb43409877f31991", + "files": [ + { + "path": "child.opy", + "sha256": "00776d92813be2a6f89b7bc4f4ed559c975aacb1e91f7017852ca3aad4382e01" + }, + { + "path": "grandchild.opy", + "sha256": "5cb1a52a4313fbe651a3f3075ac907ab884d6d5973a3fdd506c6002a3cdb0afc" + }, + { + "path": "source.opy", + "sha256": "933baf034eb509b4bb09610645dbe20a24dbae9e903e91bbfb43409877f31991" + } + ], + "sha256": "a20164167acdc69fd991aea76656788157d435d4099407cd1889462f2ed71eef", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/issue-31-positive/oracle.json b/compatibility/fixtures/synthetic/issue-31-positive/oracle.json index b0959a8..8bc1040 100644 --- a/compatibility/fixtures/synthetic/issue-31-positive/oracle.json +++ b/compatibility/fixtures/synthetic/issue-31-positive/oracle.json @@ -10,7 +10,17 @@ }, "fixture": "synthetic/issue-31-positive", "input": { - "sha256": "aa688034210c921381c0d0ddb7c876c05279bac306a6c3cd3103fc47e501a104", + "files": [ + { + "path": "child.opy", + "sha256": "fff006d57fb4436bfb6450eddc004bc337bf95f3ab8b6cd75c641f79e2d0d080" + }, + { + "path": "source.opy", + "sha256": "aa688034210c921381c0d0ddb7c876c05279bac306a6c3cd3103fc47e501a104" + } + ], + "sha256": "6bcf82bcfaa534e542e1f6b2057de38c65ae0084dac8cc11cf2f418b80e706bb", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/issue-33-f-string/oracle.json b/compatibility/fixtures/synthetic/issue-33-f-string/oracle.json index 84b296f..468fb3e 100644 --- a/compatibility/fixtures/synthetic/issue-33-f-string/oracle.json +++ b/compatibility/fixtures/synthetic/issue-33-f-string/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "synthetic/issue-33-f-string", "input": { - "sha256": "b314c0b598a5c5286b51b1ebaa5edebc8d192e65a6a091c75ec1dd4ba403a95d", + "files": [ + { + "path": "source.opy", + "sha256": "b314c0b598a5c5286b51b1ebaa5edebc8d192e65a6a091c75ec1dd4ba403a95d" + } + ], + "sha256": "70df6980a5ac4d30933bf83337325c2f835e277851300b90b2008f9d366bd614", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/issue-33-lambda-negative/oracle.json b/compatibility/fixtures/synthetic/issue-33-lambda-negative/oracle.json index 6cd5e1f..1133272 100644 --- a/compatibility/fixtures/synthetic/issue-33-lambda-negative/oracle.json +++ b/compatibility/fixtures/synthetic/issue-33-lambda-negative/oracle.json @@ -15,7 +15,13 @@ }, "fixture": "synthetic/issue-33-lambda-negative", "input": { - "sha256": "38ece46703ba1dd69dfc3140151f430dc5e195fd9abef134206f9dc602b80175", + "files": [ + { + "path": "source.opy", + "sha256": "38ece46703ba1dd69dfc3140151f430dc5e195fd9abef134206f9dc602b80175" + } + ], + "sha256": "8d1ab5aed754969f42e34f7d7ed74427b3d02a860b960f0fd61a3fa593a90a1d", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/issue-33-switch-break/oracle.json b/compatibility/fixtures/synthetic/issue-33-switch-break/oracle.json index d28c69f..d18856e 100644 --- a/compatibility/fixtures/synthetic/issue-33-switch-break/oracle.json +++ b/compatibility/fixtures/synthetic/issue-33-switch-break/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "synthetic/issue-33-switch-break", "input": { - "sha256": "fd00440c1b07806b8e2f8aa3757e37b9b4375a6f0b9717eda0f89a01c95167f1", + "files": [ + { + "path": "source.opy", + "sha256": "fd00440c1b07806b8e2f8aa3757e37b9b4375a6f0b9717eda0f89a01c95167f1" + } + ], + "sha256": "4e7bd1a2eb3fbf70418b2b5a1385dcfcce6d77f0b6601c97798e5a34f9810e1e", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/issue-35-integration/oracle.json b/compatibility/fixtures/synthetic/issue-35-integration/oracle.json index 5c299c0..6b342ee 100644 --- a/compatibility/fixtures/synthetic/issue-35-integration/oracle.json +++ b/compatibility/fixtures/synthetic/issue-35-integration/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "synthetic/issue-35-integration", "input": { - "sha256": "81bf609658e1e913773c534ff90271153cad741c131cf06c87785a1e9aef7824", + "files": [ + { + "path": "source.opy", + "sha256": "81bf609658e1e913773c534ff90271153cad741c131cf06c87785a1e9aef7824" + } + ], + "sha256": "462314b803f19552ebe5055988e7419321d9345400de93ab79a15307497103c0", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/issue-40-structural/oracle.json b/compatibility/fixtures/synthetic/issue-40-structural/oracle.json index 6b85aec..109f772 100644 --- a/compatibility/fixtures/synthetic/issue-40-structural/oracle.json +++ b/compatibility/fixtures/synthetic/issue-40-structural/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "synthetic/issue-40-structural", "input": { - "sha256": "c3a1daee735d48e670cf98fed74872b02480d9a7064a3340eadae3278d4eb7b8", + "files": [ + { + "path": "source.opy", + "sha256": "c3a1daee735d48e670cf98fed74872b02480d9a7064a3340eadae3278d4eb7b8" + } + ], + "sha256": "587fe2fb76b196f8eafb7817784710f6f5e03c789d1216f5e253f84d88bd8d40", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/issue-46-primitives/oracle.json b/compatibility/fixtures/synthetic/issue-46-primitives/oracle.json index e5be175..9774029 100644 --- a/compatibility/fixtures/synthetic/issue-46-primitives/oracle.json +++ b/compatibility/fixtures/synthetic/issue-46-primitives/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "synthetic/issue-46-primitives", "input": { - "sha256": "1fc8ac4a977c5b4eba20c8c45340b3ebacf78323e158bd121d723469a31394b4", + "files": [ + { + "path": "source.opy", + "sha256": "1fc8ac4a977c5b4eba20c8c45340b3ebacf78323e158bd121d723469a31394b4" + } + ], + "sha256": "7c743a96135e57867eea3e302c73643e3cf5070eea4ed1dd19a79f7a577438aa", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/issue-46-unsupported/oracle.json b/compatibility/fixtures/synthetic/issue-46-unsupported/oracle.json index bcd50a0..aba31d7 100644 --- a/compatibility/fixtures/synthetic/issue-46-unsupported/oracle.json +++ b/compatibility/fixtures/synthetic/issue-46-unsupported/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "synthetic/issue-46-unsupported", "input": { - "sha256": "caade82a453fa41ec5d2b19736b5b324a7a5d70773d9bff47e8634dd2f8dce8b", + "files": [ + { + "path": "source.opy", + "sha256": "caade82a453fa41ec5d2b19736b5b324a7a5d70773d9bff47e8634dd2f8dce8b" + } + ], + "sha256": "e1098bbb12f41d6ef22e12c4a285433950a34c91f59a6e1e5cd3c61978a7ac16", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/issue-47-control-flow/oracle.json b/compatibility/fixtures/synthetic/issue-47-control-flow/oracle.json index e6db9b6..f0c2ce1 100644 --- a/compatibility/fixtures/synthetic/issue-47-control-flow/oracle.json +++ b/compatibility/fixtures/synthetic/issue-47-control-flow/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "synthetic/issue-47-control-flow", "input": { - "sha256": "8ff446e90c2d5b1827c42d4d3480951b8059dad1c7f9ecb7f0a6e18a63f1ca77", + "files": [ + { + "path": "source.opy", + "sha256": "8ff446e90c2d5b1827c42d4d3480951b8059dad1c7f9ecb7f0a6e18a63f1ca77" + } + ], + "sha256": "09fb929572f39ae87a6cb4e75edf93f3eb84602c686864e85feaf05549680041", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/issue-47-do-while-invalid-placement/oracle.json b/compatibility/fixtures/synthetic/issue-47-do-while-invalid-placement/oracle.json index 9fb9951..4e53deb 100644 --- a/compatibility/fixtures/synthetic/issue-47-do-while-invalid-placement/oracle.json +++ b/compatibility/fixtures/synthetic/issue-47-do-while-invalid-placement/oracle.json @@ -15,7 +15,13 @@ }, "fixture": "synthetic/issue-47-do-while-invalid-placement", "input": { - "sha256": "b7df07df9799afec1c139cd0a23764804feeaccf7aae8dc7d9be575f37c1e5c7", + "files": [ + { + "path": "source.opy", + "sha256": "b7df07df9799afec1c139cd0a23764804feeaccf7aae8dc7d9be575f37c1e5c7" + } + ], + "sha256": "0aa0d20de8a47bc4eb1a8f0ce6b3766076e14f5f2bd30b7469b632f371ba9cb9", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/issue-47-do-while-shapes/oracle.json b/compatibility/fixtures/synthetic/issue-47-do-while-shapes/oracle.json index 57eff64..5810637 100644 --- a/compatibility/fixtures/synthetic/issue-47-do-while-shapes/oracle.json +++ b/compatibility/fixtures/synthetic/issue-47-do-while-shapes/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "synthetic/issue-47-do-while-shapes", "input": { - "sha256": "6e0baa620c74b51b98ecb2576a353bcae83f335bd50ba904c73347f8b87b9a9c", + "files": [ + { + "path": "source.opy", + "sha256": "6e0baa620c74b51b98ecb2576a353bcae83f335bd50ba904c73347f8b87b9a9c" + } + ], + "sha256": "52487c06f49d4649a34064d41c45d81a7bcbf0c5c4f3cd17af1a05d6b94c2f8a", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/issue-47-switch-multiple-break/oracle.json b/compatibility/fixtures/synthetic/issue-47-switch-multiple-break/oracle.json index bc6407c..52d520b 100644 --- a/compatibility/fixtures/synthetic/issue-47-switch-multiple-break/oracle.json +++ b/compatibility/fixtures/synthetic/issue-47-switch-multiple-break/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "synthetic/issue-47-switch-multiple-break", "input": { - "sha256": "e800e97e748ab8ed301e14064e88f115b74fd9953052f5b2108b510d084fedb3", + "files": [ + { + "path": "source.opy", + "sha256": "e800e97e748ab8ed301e14064e88f115b74fd9953052f5b2108b510d084fedb3" + } + ], + "sha256": "c51625efd490b713cb2a8d17e188458cf24c4c142a0e62562e7477a27bd33f33", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/issue-47-switch-order/oracle.json b/compatibility/fixtures/synthetic/issue-47-switch-order/oracle.json index 4a030e8..35b6bbd 100644 --- a/compatibility/fixtures/synthetic/issue-47-switch-order/oracle.json +++ b/compatibility/fixtures/synthetic/issue-47-switch-order/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "synthetic/issue-47-switch-order", "input": { - "sha256": "b45056da5e473dbfea1ba2de6731359872be6439d146ffbf6360781ead967f12", + "files": [ + { + "path": "source.opy", + "sha256": "b45056da5e473dbfea1ba2de6731359872be6439d146ffbf6360781ead967f12" + } + ], + "sha256": "bd2c7a39b913ee250476f556aa171d29f7b71e2aea03a1c54e0c0808cb983838", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/issue-47-switch-structured-target/oracle.json b/compatibility/fixtures/synthetic/issue-47-switch-structured-target/oracle.json index 2766680..ee0579e 100644 --- a/compatibility/fixtures/synthetic/issue-47-switch-structured-target/oracle.json +++ b/compatibility/fixtures/synthetic/issue-47-switch-structured-target/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "synthetic/issue-47-switch-structured-target", "input": { - "sha256": "80a1020848684a5a9703f47f9da0085050ceef9a856ad01bfae8018fc88cfb2b", + "files": [ + { + "path": "source.opy", + "sha256": "80a1020848684a5a9703f47f9da0085050ceef9a856ad01bfae8018fc88cfb2b" + } + ], + "sha256": "311c80e6637e36455ff625b3242fd92851e923401c38b24014cb8b03bd02be41", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/issue-47-unsupported/oracle.json b/compatibility/fixtures/synthetic/issue-47-unsupported/oracle.json index 5264c8f..0353beb 100644 --- a/compatibility/fixtures/synthetic/issue-47-unsupported/oracle.json +++ b/compatibility/fixtures/synthetic/issue-47-unsupported/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "synthetic/issue-47-unsupported", "input": { - "sha256": "e20ad681f3771fc07b49fe577778b595b8f3b847266e4805f0ef7b0cff81c9a6", + "files": [ + { + "path": "source.opy", + "sha256": "e20ad681f3771fc07b49fe577778b595b8f3b847266e4805f0ef7b0cff81c9a6" + } + ], + "sha256": "e586c87561514453611f98c11fe91f6aa64d684647505d9e59f6b45724487319", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/preprocessing/oracle.json b/compatibility/fixtures/synthetic/preprocessing/oracle.json index c77243c..13643cf 100644 --- a/compatibility/fixtures/synthetic/preprocessing/oracle.json +++ b/compatibility/fixtures/synthetic/preprocessing/oracle.json @@ -10,7 +10,17 @@ }, "fixture": "synthetic/preprocessing", "input": { - "sha256": "c0307032fc7f0cde66beb3824710480a2dd5a53832cc036b15acc1be2654ed0c", + "files": [ + { + "path": "shared.opy", + "sha256": "d4e08203bddd10e566d22363b7af590ac4ebb4879d90404e3df2fcb0f710093b" + }, + { + "path": "source.opy", + "sha256": "c0307032fc7f0cde66beb3824710480a2dd5a53832cc036b15acc1be2654ed0c" + } + ], + "sha256": "344dff011e32094787274531194b62da6e0e7c9e5df40383f00272b98f3c42a3", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/receiver-calls/oracle.json b/compatibility/fixtures/synthetic/receiver-calls/oracle.json index 4e3fc8d..86414d9 100644 --- a/compatibility/fixtures/synthetic/receiver-calls/oracle.json +++ b/compatibility/fixtures/synthetic/receiver-calls/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "synthetic/receiver-calls", "input": { - "sha256": "72ab2b077545fea887e26bd0544441e42bbaab569e5fdd77a9650452e08982e9", + "files": [ + { + "path": "source.opy", + "sha256": "72ab2b077545fea887e26bd0544441e42bbaab569e5fdd77a9650452e08982e9" + } + ], + "sha256": "9f1870c3f83735ac716d1c74cc08d87eb1bf61e147084ce6ef06f1a3f1d00382", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/receiver-playervar/oracle.json b/compatibility/fixtures/synthetic/receiver-playervar/oracle.json index f5f999a..630e5e5 100644 --- a/compatibility/fixtures/synthetic/receiver-playervar/oracle.json +++ b/compatibility/fixtures/synthetic/receiver-playervar/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "synthetic/receiver-playervar", "input": { - "sha256": "c8040ee474759c5b27541b2b9a514d350f605f471bc23246d1baeadc37a5eadc", + "files": [ + { + "path": "source.opy", + "sha256": "c8040ee474759c5b27541b2b9a514d350f605f471bc23246d1baeadc37a5eadc" + } + ], + "sha256": "62f7d04611b79a0765ee42514c166996141f36fea9c52970d7a808633116468f", "source": "source.opy" }, "oracle": { diff --git a/compatibility/fixtures/synthetic/settings/oracle.json b/compatibility/fixtures/synthetic/settings/oracle.json index a6e41da..a168ba3 100644 --- a/compatibility/fixtures/synthetic/settings/oracle.json +++ b/compatibility/fixtures/synthetic/settings/oracle.json @@ -10,7 +10,13 @@ }, "fixture": "synthetic/settings", "input": { - "sha256": "b7989b6c078370649e87407d697da6ca89719b4ec6fcfcee63ce7836d2008fa2", + "files": [ + { + "path": "source.opy", + "sha256": "b7989b6c078370649e87407d697da6ca89719b4ec6fcfcee63ce7836d2008fa2" + } + ], + "sha256": "f29d2d73660adbdbdfda2e441a24950ccf369e0334f54667b0498b02f92a33ed", "source": "source.opy" }, "oracle": { diff --git a/compatibility/input_identity.py b/compatibility/input_identity.py new file mode 100644 index 0000000..57ca6be --- /dev/null +++ b/compatibility/input_identity.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +"""Derive a stable identity for a fixture's resolved OPY source graph.""" + +from __future__ import annotations + +import hashlib +import json +from pathlib import Path +from typing import Any + + +class InputIdentityError(ValueError): + """A fixture source graph cannot be resolved into a stable identity.""" + + +def _sha256_bytes(value: bytes) -> str: + return hashlib.sha256(value).hexdigest() + + +def _fixture_relative_path(fixture_dir: Path, path: Path) -> str: + try: + return path.resolve().relative_to(fixture_dir.resolve()).as_posix() + except ValueError as error: + raise InputIdentityError( + f"source graph path escapes fixture directory: {path}" + ) from error + + +def _resolve(fixture_dir: Path, relative: str) -> tuple[Path, str]: + path = (fixture_dir / relative).resolve() + relative_path = _fixture_relative_path(fixture_dir, path) + if not path.is_file(): + raise InputIdentityError(f"source graph file does not exist: {relative_path}") + return path, relative_path + + +def project_input(fixture_dir: Path, metadata: dict[str, Any]) -> dict[str, Any]: + """Return the source project manifest and its canonical digest. + + A fixture's complete project input is every ``.opy`` source file under its + directory, excluding explicitly listed minimized regression snippets. The + manifest deliberately covers include closures and ``#!mainFile`` targets + without reimplementing the compiler's path resolution rules. + """ + + source_value = metadata.get("source") + if not isinstance(source_value, str) or not source_value: + raise InputIdentityError("fixture source must be a non-empty string") + + fixture_dir = fixture_dir.resolve() + _, source_relative = _resolve(fixture_dir, source_value) + regression_paths = set() + for regression in metadata.get("regressions", []): + if not isinstance(regression, dict): + raise InputIdentityError("fixture regression metadata must be objects") + regression_source = regression.get("source") + if not isinstance(regression_source, str) or not regression_source: + raise InputIdentityError("fixture regression source must be a non-empty string") + _, regression_relative = _resolve(fixture_dir, regression_source) + regression_paths.add(regression_relative) + + files: list[dict[str, str]] = [] + + source_paths = sorted(fixture_dir.rglob("*.opy")) + for path in source_paths: + relative = _fixture_relative_path(fixture_dir, path) + if relative in regression_paths: + continue + content = path.read_bytes() + try: + content.decode("utf-8") + except UnicodeDecodeError as error: + raise InputIdentityError(f"source project file is not UTF-8: {relative}") from error + files.append({"path": relative, "sha256": _sha256_bytes(content)}) + + if not any(item["path"] == source_relative for item in files): + raise InputIdentityError("fixture source is excluded from the source project") + manifest = {"source": source_relative, "files": files} + encoded = json.dumps( + manifest, + ensure_ascii=False, + sort_keys=True, + separators=(",", ":"), + ).encode("utf-8") + return {**manifest, "sha256": _sha256_bytes(encoded)} diff --git a/compatibility/run_native.py b/compatibility/run_native.py new file mode 100644 index 0000000..c424484 --- /dev/null +++ b/compatibility/run_native.py @@ -0,0 +1,220 @@ +#!/usr/bin/env python3 +"""Compile the compatibility corpus and run the shared differential contract.""" + +from __future__ import annotations + +import argparse +import json +import subprocess +import sys +from pathlib import Path +from typing import Any + +import diff +import input_identity + + +ROOT = Path(__file__).resolve().parents[1] +FIXTURES = ROOT / "compatibility" / "fixtures" +COMPILER_EXPECTATIONS = ROOT / "compatibility" / "compiler-expectations.json" +DEFAULT_RESULTS = ROOT / "target" / "opy-compiler-results" +DEFAULT_REPORT = ROOT / "target" / "opy-compiler-report.json" + + +class NativeError(RuntimeError): + """A corpus execution or result-contract error.""" + + +def read_json(path: Path) -> dict[str, Any]: + try: + value = json.loads(path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as error: + raise NativeError(f"cannot read JSON {path}: {error}") from error + if not isinstance(value, dict): + raise NativeError(f"JSON root must be an object: {path}") + return value + + +def fixtures() -> list[tuple[Path, dict[str, Any]]]: + found = [] + for metadata_path in sorted(FIXTURES.glob("**/fixture.json")): + metadata = read_json(metadata_path) + source = metadata_path.parent / metadata["source"] + if not source.is_file(): + raise NativeError(f"source does not exist: {source}") + found.append((metadata_path.parent, metadata)) + if not found: + raise NativeError(f"no fixtures found under {FIXTURES}") + return found + + +def run_semantic_evidence( + binary: Path, + directory: Path, + metadata: dict[str, Any], + project_sha256: str, +) -> dict[str, Any]: + source = directory / metadata["source"] + completed = subprocess.run( + [ + str(binary), + "--source", + source.name, + "--root", + ".", + "--oracle", + "oracle.json", + "--input-sha256", + project_sha256, + ], + cwd=directory, + capture_output=True, + text=True, + encoding="utf-8", + check=False, + ) + if completed.returncode != 0: + raise NativeError( + f"{metadata['id']}: compatibility evidence failed " + f"(exit {completed.returncode}): {completed.stderr.strip()}" + ) + try: + result = json.loads(completed.stdout) + except json.JSONDecodeError as error: + raise NativeError( + f"{metadata['id']}: compatibility evidence did not produce JSON: " + f"{completed.stderr.strip()}" + ) from error + if not isinstance(result, dict) or result.get("schemaVersion") != 1: + raise NativeError(f"{metadata['id']}: invalid compatibility evidence schema") + if not isinstance(result.get("semanticWIR"), dict): + raise NativeError(f"{metadata['id']}: semantic-WIR evidence is missing") + return result + + +def run_fixture( + binary: Path, + semantic_binary: Path, + directory: Path, + metadata: dict[str, Any], + expectation: dict[str, Any], +) -> dict[str, Any]: + source = directory / metadata["source"] + try: + project = input_identity.project_input(directory, metadata) + except input_identity.InputIdentityError as error: + raise NativeError(f"{metadata['id']}: {error}") from error + completed = subprocess.run( + [ + str(binary), + "compile", + "--format", + "json", + "--language", + "en-US", + str(source.name), + ], + cwd=directory, + capture_output=True, + text=True, + encoding="utf-8", + check=False, + ) + try: + result = json.loads(completed.stdout) + except json.JSONDecodeError as error: + raise NativeError( + f"{metadata['id']}: compile did not produce JSON " + f"(exit {completed.returncode}): {completed.stderr.strip()}" + ) from error + if not isinstance(result, dict) or result.get("schemaVersion") != 1: + raise NativeError(f"{metadata['id']}: invalid compile report schema") + if result.get("compile", {}).get("status") not in {"success", "failure"}: + raise NativeError(f"{metadata['id']}: invalid compile status") + result["fixture"] = metadata["id"] + result["input"] = project + result["compile"]["processExitCode"] = completed.returncode + if completed.stderr: + result["compile"]["stderr"] = completed.stderr + if ( + expectation["comparison"] == "semantic-wir" + and result["compile"]["status"] == "success" + ): + result["compatibility"] = run_semantic_evidence( + semantic_binary, + directory, + metadata, + project["sha256"], + ) + return result + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--binary", + type=Path, + default=ROOT / "target" / "debug" / "opy-cli", + help="path to a built opy-cli binary", + ) + parser.add_argument( + "--semantic-binary", + type=Path, + help="path to the internal compatibility evidence binary", + ) + parser.add_argument("--results", type=Path, default=DEFAULT_RESULTS) + parser.add_argument("--report", type=Path, default=DEFAULT_REPORT) + parser.add_argument( + "--allow-inconclusive", + action="store_true", + help="return success for inconclusive comparisons after shared checks pass", + ) + args = parser.parse_args(argv) + binary = args.binary.resolve() + if not binary.is_file(): + raise NativeError(f"opy-cli binary does not exist: {binary}") + semantic_binary = ( + args.semantic_binary.resolve() + if args.semantic_binary + else binary.with_name("opy-compat") + ) + if not semantic_binary.is_file(): + raise NativeError(f"compatibility evidence binary does not exist: {semantic_binary}") + results_root = args.results.resolve() + report_path = args.report.resolve() + results_root.mkdir(parents=True, exist_ok=True) + try: + expectations = diff.load_compiler_expectations(COMPILER_EXPECTATIONS) + except diff.DiffError as error: + raise NativeError(str(error)) from error + + for directory, metadata in fixtures(): + try: + expectation = expectations[metadata["id"]] + except KeyError as error: + raise NativeError(f"missing compiler expectation: {metadata['id']}") from error + native = run_fixture(binary, semantic_binary, directory, metadata, expectation) + result_path = results_root / metadata["id"] / "result.json" + result_path.parent.mkdir(parents=True, exist_ok=True) + result_path.write_text(json.dumps(native, indent=2, sort_keys=True) + "\n") + + # Keep expectation loading, project-input validation, stage comparison, and + # blocking status classification in the existing independent comparator. + try: + return diff.run_compiler( + FIXTURES, + report_path, + results_root, + COMPILER_EXPECTATIONS, + allow_inconclusive=args.allow_inconclusive, + ) + except diff.DiffError as error: + raise NativeError(str(error)) from error + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except NativeError as error: + print(f"ERROR: {error}", file=sys.stderr) + raise SystemExit(2) diff --git a/compatibility/run_oracle.py b/compatibility/run_oracle.py index 4525e20..b774f8c 100644 --- a/compatibility/run_oracle.py +++ b/compatibility/run_oracle.py @@ -14,6 +14,8 @@ from pathlib import Path from typing import Any +import input_identity + ROOT = Path(__file__).resolve().parents[1] DEFAULT_FIXTURES = ROOT / "compatibility" / "fixtures" @@ -78,10 +80,6 @@ def sha256_bytes(value: bytes) -> str: return hashlib.sha256(value).hexdigest() -def sha256_file(path: Path) -> str: - return sha256_bytes(path.read_bytes()) - - def sha256_text(value: str) -> str: return sha256_bytes(value.encode("utf-8")) @@ -220,14 +218,15 @@ def run_fixture( workshop = normalize_text(workshop_exact) status = "success" if completed.returncode == 0 else "failure" + try: + project = input_identity.project_input(fixture_dir, fixture) + except input_identity.InputIdentityError as error: + raise RunnerError(f"invalid source project identity: {error}") from error return { "schemaVersion": 1, "fixture": fixture["id"], "oracle": oracle, - "input": { - "source": fixture["source"], - "sha256": sha256_file(source), - }, + "input": project, "compile": { "status": status, "exitCode": completed.returncode, diff --git a/compatibility/support-matrix.json b/compatibility/support-matrix.json index 27929cf..d883d1a 100644 --- a/compatibility/support-matrix.json +++ b/compatibility/support-matrix.json @@ -10,7 +10,7 @@ }, "snapshot": { "date": "2026-08-29", - "note": "Readiness baseline through the bounded #37 compiler slice. Settings, catalog-declared locale emission, and post-compile hooks have explicit OPY -> HIR -> WIR -> validated Workshop emission evidence. Optimizer controls remain non-blocking presentation differences; semantic replacement directives remain explicit gaps and fail at the compiler boundary until canonical lowering exists. Canonical Workshop catalog data, settings tables, locale spellings, WIR, validation, and emission remain owned by workshop-rs.", + "note": "Readiness baseline through the #38 versioned compile API and CLI. Supported settings, catalog-declared locale emission, and post-compile hooks have explicit OPY -> HIR -> WIR -> validated Workshop emission evidence. Compiler and CLI failures remain structured and source-attributed; optimizer controls remain non-blocking presentation differences and semantic replacement directives remain explicit gaps. Canonical Workshop catalog data, settings tables, locale spellings, WIR, validation, and emission remain owned by workshop-rs.", "asOfCommit": "9f7c62b1958a3804780a2de7d1bd55e2fb298533" }, "states": { @@ -642,7 +642,7 @@ "evidence": [ "fixtures:synthetic/issue-46-primitives", "test:opy-compiler-primitive-lowering", - "test:opy-compiler-issue-46-oracle-equivalence", + "test:opy-compiler-issue-46-native-wir-gap", "contract:workshop-rs-v0.1.11" ], "notes": "Issue #46. Global/player assignments and augmented assignments (+=, -=, *=, /=, %=, **=), single-level indexed variable assignments and modifications (setGlobalVariableAtIndex, modifyGlobalVariableAtIndex, setPlayerVariableAtIndex, modifyPlayerVariableAtIndex), and pass no-op statements lower into canonical workshop-rs WIR. Declaration initializer evidence covers non-null initializers; OverPy null-default initializer semantics remain tracked by #58. Deeper indexed writes remain tracked by #60. Direct global/player assignments and modifications carry full statement and target-variable span provenance; the single-level indexed forms lower to canonical Call actions, which carry only the statement-level source span (the canonical WIR Call shape has no separate target-span field)." @@ -651,7 +651,7 @@ "id": "compilation/opy-implicit-default-variables", "name": "OverPy implicit default global and player variables (A-DX) at fixed Workshop slots", "category": "compilation", - "state": "end-to-end-supported", + "state": "lowering-dependent", "evidence": [ "fixtures:synthetic/issue-46-primitives", "test:opy-compiler-implicit-default-variables", @@ -686,7 +686,7 @@ "id": "compilation/opy-control-flow-lowering", "name": "OPY control-flow HIR -> canonical WIR lowering", "category": "compilation", - "state": "end-to-end-supported", + "state": "lowering-dependent", "evidence": [ "fixtures:synthetic/issue-47-control-flow", "fixtures:synthetic/issue-47-unsupported", @@ -696,10 +696,10 @@ "fixtures:synthetic/issue-47-do-while-shapes", "fixtures:synthetic/issue-47-do-while-invalid-placement", "fixtures:synthetic/issue-33-switch-break", - "test:opy-compiler-issue-47-oracle-equivalence", + "test:opy-compiler-issue-47-residual-native-wir-gaps", "contract:workshop-rs-v0.1.11" ], - "notes": "Issue #47. If/elif/else, while, global-binder range-for, do-while expansion, source-ordered switch fallthrough/default, and direct loop/switch/do-while break lower into canonical workshop-rs WIR. Switch offsets and do-while distances use workshop-rs v0.1.11 canonical emitted action widths; direct, conditional, nested, and structured-tail do-while shapes plus structured switch targets are pinned and oracle-equivalent. A nested conditional switch break and a multi-break switch with later reachable actions remain stable source-attributed unsupported-integration-surface diagnostics because workshop-rs v0.1.11 has no lossless multi-target switch carrier." + "notes": "Issue #47. If/elif/else, while, global-binder range-for, do-while expansion, source-ordered switch fallthrough/default, and direct loop/switch/do-while break lower into canonical workshop-rs WIR. Switch offsets and do-while distances use workshop-rs v0.1.11 canonical emitted action widths; direct, conditional, and nested do-while shapes are pinned and oracle-equivalent. Residual native-WIR differences in switch-break and switch-target shapes remain explicit compiler gaps, while nested conditional switch break and multi-break switch cases remain stable source-attributed unsupported-integration-surface diagnostics because workshop-rs v0.1.11 has no lossless multi-target switch carrier." }, { "id": "compilation/workshop-lowering", @@ -710,7 +710,7 @@ "fixtures:synthetic", "fixtures:real-world" ], - "notes": "Issue #8 remains the broader lowering track. The released workshop-rs v0.1.11 public contract now supports the bounded #35 adapter, the #40 structural skeleton, and the separately evidenced #47 control-flow slice; full OPY statement/expression lowering, catalog/member/enum breadth, settings/content, locales, optimizer effects, and hooks remain lowering-dependent follow-up work. Oracle snapshots (fixtures/**/oracle.json) remain preserved reference evidence; no temporary Workshop IR is introduced here." + "notes": "Issue #38 exposes the bounded compiler/library/CLI surface over the released workshop-rs v0.1.11 contract. Full OPY statement/expression lowering, catalog/member/enum breadth, optimizer effects, and remaining real-project gaps stay explicit in compiler-expectations.json and the native corpus report; no temporary Workshop IR is introduced here." }, { "id": "compilation/end-to-end", @@ -722,7 +722,7 @@ "fixtures:real-world", "fixtures:census/workshop-feature-census" ], - "notes": "Full OPY-to-Workshop parity remains lowering-dependent after the bounded #35 first slice; the OPY-side feature census records opaque workshop-rs#10 identities without duplicating catalog definitions. Differential harness contract is ready (compatibility/diff.py, issue #25)." + "notes": "Full OPY-to-Workshop parity remains broader than the declared #38 baseline. compatibility/run_native.py compiles every fixture through the versioned CLI contract and compares it with the separate compiler-expectations.json baseline. Source/frontend expectations remain independent; entries are separately classified and no unverified case is counted as compiler parity." }, { "id": "decompilation/workshop-to-opy", @@ -751,8 +751,8 @@ "planned": 0, "source-supported": 23, "semantic-supported": 13, - "lowering-dependent": 10, - "end-to-end-supported": 10 + "lowering-dependent": 12, + "end-to-end-supported": 8 }, "byCategory": { "syntax": 14, diff --git a/compatibility/tests/test_diff.py b/compatibility/tests/test_diff.py index dc27461..ad21f7a 100644 --- a/compatibility/tests/test_diff.py +++ b/compatibility/tests/test_diff.py @@ -33,6 +33,18 @@ def test_expectations_cover_every_fixture_with_evidence(self): self.assertTrue(expectation["evidence"], fixture) self.assertTrue(expectation["note"], fixture) + def test_compiler_expectations_are_separate_and_cover_every_fixture(self): + source = diff.load_expectations() + compiler = diff.load_compiler_expectations() + fixtures = set(diff.fixture_ids(COMPATIBILITY_DIR / "fixtures")) + self.assertEqual(set(compiler), fixtures) + self.assertNotIn("comparison", source["synthetic/basic-rule"]) + for fixture, expectation in compiler.items(): + self.assertIn(expectation["comparison"], diff.EXPECTED_COMPILER_COMPARISONS) + self.assertTrue(expectation["evidence"], fixture) + self.assertTrue(expectation["owner"], fixture) + self.assertTrue(expectation["note"], fixture) + def write_result(self, root: Path, result: dict): path = root / result["fixture"] / "result.json" path.parent.mkdir(parents=True) @@ -165,6 +177,122 @@ def test_input_hash_mismatch_is_rejected(self): None, ) + def test_compiler_status_mismatch_is_blocking(self): + result = copy.deepcopy(self.oracle) + result["compile"]["status"] = "failure" + result["compile"]["exitCode"] = 1 + result["compile"]["workshopExact"] = "" + result["compile"]["workshop"] = "" + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + self.write_result(root, result) + report_result = diff.compare_compiler_fixture( + COMPATIBILITY_DIR / "fixtures", + "synthetic/basic-rule", + root, + diff.load_compiler_expectations(), + ) + self.assertEqual(report_result["status"], "unexpected-divergence") + self.assertIn("compile-status", report_result["regressionStages"]) + + def test_compiler_input_hash_mismatch_is_rejected(self): + result = copy.deepcopy(self.oracle) + result["input"]["sha256"] = "different" + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + self.write_result(root, result) + with self.assertRaises(diff.DiffError): + diff.compare_compiler_fixture( + COMPATIBILITY_DIR / "fixtures", + "synthetic/basic-rule", + root, + diff.load_compiler_expectations(), + ) + + def test_compile_result_rejects_public_semantic_wir_evidence(self): + result = copy.deepcopy(self.oracle) + result["compile"]["semanticWIR"] = {} + with self.assertRaises(diff.DiffError): + diff.require_result_shape(result, "compile result") + + def test_compiler_semantic_wir_consumes_direct_evidence(self): + fixture = "synthetic/issue-47-control-flow" + oracle = json.loads( + ( + COMPATIBILITY_DIR + / "fixtures" + / fixture + / "oracle.json" + ).read_text(encoding="utf-8") + ) + result = copy.deepcopy(oracle) + result["compatibility"] = { + "schemaVersion": 1, + "semanticWIR": { + "schemaVersion": 1, + "algorithm": "workshop-rs::roundtrip::equivalent", + "inputSha256": oracle["input"]["sha256"], + "referenceInputSha256": oracle["input"]["sha256"], + "equivalent": False, + }, + } + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + self.write_result(root, result) + report_result = diff.compare_compiler_fixture( + COMPATIBILITY_DIR / "fixtures", + fixture, + root, + diff.load_compiler_expectations(), + ) + self.assertEqual(report_result["status"], "known-gap") + self.assertEqual( + report_result["stages"][1]["outcome"], + "accepted-gap", + ) + + result["compatibility"]["semanticWIR"]["referenceError"] = "invalid pinned Workshop" + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + self.write_result(root, result) + report_result = diff.compare_compiler_fixture( + COMPATIBILITY_DIR / "fixtures", + fixture, + root, + diff.load_compiler_expectations(), + ) + self.assertEqual(report_result["status"], "inconclusive") + self.assertEqual( + report_result["stages"][1]["outcome"], + "inconclusive", + ) + + del result["compatibility"]["semanticWIR"]["referenceError"] + result["compatibility"]["semanticWIR"]["equivalent"] = True + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + self.write_result(root, result) + report_result = diff.compare_compiler_fixture( + COMPATIBILITY_DIR / "fixtures", + fixture, + root, + diff.load_compiler_expectations(), + ) + self.assertEqual(report_result["status"], "regression") + + def test_compiler_report_declares_compiler_stages(self): + report = diff.build_compiler_report([]) + self.assertEqual( + report["comparison"]["stages"], + [ + "compile-status", + "normalized-output", + "semantic-wir", + "diagnostic-code", + "compiler-contract", + ], + ) + def test_missing_producer_is_inconclusive(self): report_result = diff.compare_fixture( COMPATIBILITY_DIR / "fixtures", diff --git a/compatibility/tests/test_runner.py b/compatibility/tests/test_runner.py index bc71828..e0238c1 100644 --- a/compatibility/tests/test_runner.py +++ b/compatibility/tests/test_runner.py @@ -1,5 +1,6 @@ import json import sys +import tempfile import unittest from pathlib import Path @@ -8,9 +9,32 @@ sys.path.insert(0, str(COMPATIBILITY_DIR)) import run_oracle # noqa: E402 +import input_identity # noqa: E402 class RunnerTests(unittest.TestCase): + def test_project_identity_covers_all_sources_and_excludes_regressions(self): + with tempfile.TemporaryDirectory() as temporary: + fixture_dir = Path(temporary) + (fixture_dir / "source.opy").write_text("#!mainFile\n", encoding="utf-8") + (fixture_dir / "shared.opy").write_text("rule \"shared\":\n", encoding="utf-8") + (fixture_dir / "regression.opy").write_text("broken", encoding="utf-8") + metadata = { + "source": "source.opy", + "regressions": [{"source": "regression.opy"}], + } + + first = input_identity.project_input(fixture_dir, metadata) + self.assertEqual( + [item["path"] for item in first["files"]], + ["shared.opy", "source.opy"], + ) + (fixture_dir / "shared.opy").write_text( + "rule \"changed\":\n", encoding="utf-8" + ) + second = input_identity.project_input(fixture_dir, metadata) + self.assertNotEqual(first["sha256"], second["sha256"]) + def test_normalize_text_keeps_content_and_removes_presentation_noise(self): self.assertEqual( run_oracle.normalize_text("one \r\ntwo\r\n\r\n"), @@ -41,6 +65,11 @@ def test_repository_fixture_metadata_and_snapshots_are_valid(self): self.assertTrue(snapshot.is_file(), fixture["id"]) snapshot_data = json.loads(snapshot.read_text(encoding="utf-8")) self.assertEqual(snapshot_data["fixture"], fixture["id"]) + self.assertEqual( + snapshot_data["input"], + input_identity.project_input(fixture_path.parent, fixture), + fixture["id"], + ) real_world = [ fixture for _, fixture in fixtures if fixture["category"] == "real-world" diff --git a/crates/opy-cli/Cargo.toml b/crates/opy-cli/Cargo.toml index 01661b6..110d81e 100644 --- a/crates/opy-cli/Cargo.toml +++ b/crates/opy-cli/Cargo.toml @@ -4,7 +4,15 @@ version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true -description = "Standalone OPY CLI: check, inspect, support-matrix, version (Workshop-independent, no Node or Workshop backend required)." +description = "Standalone OPY CLI: check, compile, inspect, support-matrix, version." + +[features] +compatibility = [] + +[[bin]] +name = "opy-compat" +path = "src/bin/opy-compat.rs" +required-features = ["compatibility"] [lints] workspace = true @@ -13,5 +21,7 @@ workspace = true clap = { version = "4.5", features = ["derive"] } clap_complete = "4.5" opy-rs.workspace = true +opy-compiler = { path = "../opy-compiler", version = "0.1.3" } serde = { workspace = true, features = ["derive"] } serde_json.workspace = true +workshop-rs.workspace = true diff --git a/crates/opy-cli/src/bin/opy-compat.rs b/crates/opy-cli/src/bin/opy-compat.rs new file mode 100644 index 0000000..d2a4d5d --- /dev/null +++ b/crates/opy-cli/src/bin/opy-compat.rs @@ -0,0 +1,128 @@ +//! Internal compatibility evidence producer. +//! +//! This target is built only by the compatibility gate. It is deliberately +//! separate from `opy-cli compile`: pinned oracle input must never become part +//! of the supported compiler API or report schema. + +use std::path::PathBuf; +use std::process::ExitCode; + +use opy_compiler::Compiler; +use serde::Serialize; +use serde_json::Value; +use workshop_rs::catalog::{Catalog, Locale}; + +const ALGORITHM: &str = "workshop-rs::roundtrip::equivalent"; + +#[derive(Debug)] +struct Args { + source: PathBuf, + root: PathBuf, + oracle: PathBuf, + input_sha256: String, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +struct CompatibilityResult { + schema_version: u32, + #[serde(rename = "semanticWIR")] + semantic_wir: SemanticWIRComparison, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +struct SemanticWIRComparison { + schema_version: u32, + algorithm: &'static str, + input_sha256: String, + reference_input_sha256: String, + equivalent: bool, +} + +fn main() -> ExitCode { + match run() { + Ok(result) => { + println!( + "{}", + serde_json::to_string(&result).expect("compatibility result serializes") + ); + ExitCode::SUCCESS + } + Err(error) => { + eprintln!("opy-compat: {error}"); + ExitCode::from(1) + } + } +} + +fn run() -> Result { + let args = parse_args(std::env::args().skip(1))?; + let source_text = std::fs::read_to_string(&args.source) + .map_err(|error| format!("cannot read source '{}': {error}", args.source.display()))?; + let oracle_text = std::fs::read_to_string(&args.oracle) + .map_err(|error| format!("cannot read oracle '{}': {error}", args.oracle.display()))?; + let oracle: Value = serde_json::from_str(&oracle_text) + .map_err(|error| format!("cannot parse oracle '{}': {error}", args.oracle.display()))?; + let reference_input_sha256 = oracle["input"]["sha256"] + .as_str() + .ok_or_else(|| "oracle input.sha256 is missing".to_string())?; + let reference_workshop = oracle["compile"]["workshop"] + .as_str() + .ok_or_else(|| "oracle compile.workshop is missing".to_string())?; + + let compiler = + Compiler::new().map_err(|error| format!("cannot initialize compiler: {error}"))?; + let artifact = compiler + .compile_source( + &source_text, + &args.source.to_string_lossy(), + &args.root, + &Locale::new("en-US"), + ) + .map_err(|error| error.to_string())?; + let catalog = Catalog::builtin().map_err(|error| error.to_string())?; + let reference_wir = + workshop_rs::parser::parse(reference_workshop, &catalog, &Locale::new("en-US")) + .map_err(|error| error.to_string())?; + + Ok(CompatibilityResult { + schema_version: 1, + semantic_wir: SemanticWIRComparison { + schema_version: 1, + algorithm: ALGORITHM, + input_sha256: args.input_sha256, + reference_input_sha256: reference_input_sha256.to_string(), + equivalent: workshop_rs::roundtrip::equivalent(&artifact.wir, &reference_wir), + }, + }) +} + +fn parse_args(mut args: I) -> Result +where + I: Iterator, +{ + let mut source = None; + let mut root = None; + let mut oracle = None; + let mut input_sha256 = None; + while let Some(argument) = args.next() { + let value = |name: &str, args: &mut I| { + args.next() + .ok_or_else(|| format!("missing value for {name}")) + }; + match argument.as_str() { + "--source" => source = Some(PathBuf::from(value("--source", &mut args)?)), + "--root" => root = Some(PathBuf::from(value("--root", &mut args)?)), + "--oracle" => oracle = Some(PathBuf::from(value("--oracle", &mut args)?)), + "--input-sha256" => input_sha256 = Some(value("--input-sha256", &mut args)?), + other => return Err(format!("unknown argument {other}")), + } + } + Ok(Args { + source: source.ok_or_else(|| "--source is required".to_string())?, + root: root.ok_or_else(|| "--root is required".to_string())?, + oracle: oracle.ok_or_else(|| "--oracle is required".to_string())?, + input_sha256: input_sha256.ok_or_else(|| "--input-sha256 is required".to_string())?, + }) +} diff --git a/crates/opy-cli/src/cli.rs b/crates/opy-cli/src/cli.rs index 0ce4a11..edc157d 100644 --- a/crates/opy-cli/src/cli.rs +++ b/crates/opy-cli/src/cli.rs @@ -40,6 +40,8 @@ pub(crate) const EXIT_CODES: &str = "EXIT CODES: pub(crate) enum Command { /// Parse, preprocess, and resolve; print diagnostics to stderr. Check(CheckArgs), + /// Compile OPY source into canonical Workshop text. + Compile(CompileArgs), /// Print the resolved program model as JSON. Inspect(FileArgs), /// Print the compatibility support matrix or a filtered slice as JSON. @@ -62,6 +64,20 @@ pub(crate) struct CheckArgs { pub(crate) format: OutputFormatArg, } +#[derive(Debug, Args)] +pub(crate) struct CompileArgs { + #[command(flatten)] + pub(crate) file: FileArgs, + + /// Output format; JSON contains the versioned compile report. + #[arg(long, value_enum, default_value_t = OutputFormatArg::Text)] + pub(crate) format: OutputFormatArg, + + /// Workshop locale declared by the canonical catalog. + #[arg(long, default_value = "en-US", value_name = "LOCALE")] + pub(crate) language: String, +} + #[derive(Debug, Args)] pub(crate) struct FileArgs { /// Main OPY source file. diff --git a/crates/opy-cli/src/main.rs b/crates/opy-cli/src/main.rs index 1b24ef6..69bf7ef 100644 --- a/crates/opy-cli/src/main.rs +++ b/crates/opy-cli/src/main.rs @@ -15,12 +15,14 @@ use std::process::ExitCode; use clap::{CommandFactory, Parser, error::ErrorKind}; use clap_complete::{generate, shells}; +use opy_compiler::{CompileDiagnostic, CompileStatus, Compiler}; use opy_rs::support::{self, SupportMatrixError}; use opy_rs::tooling::{CheckOutcome, Diagnostic as OpyDiagnostic, check}; use opy_rs::{LANGUAGE_NAME, LANGUAGE_VERSION}; use serde::Serialize; +use workshop_rs::catalog::Locale; -use crate::cli::{CheckArgs, Cli, Command, FileArgs, OutputFormatArg, SupportArgs}; +use crate::cli::{CheckArgs, Cli, Command, CompileArgs, FileArgs, OutputFormatArg, SupportArgs}; use crate::present::{ CheckView, DiagnosticSeverity, DiagnosticView, PositionView, Presentation, SpanView, }; @@ -53,6 +55,7 @@ fn main() -> ExitCode { ExitCode::from(2) } Some(Command::Check(args)) => cmd_check(&args, presentation), + Some(Command::Compile(args)) => cmd_compile(&args, presentation), Some(Command::Inspect(args)) => cmd_inspect(&args, presentation), Some(Command::Support(args)) => cmd_support(&args), Some(Command::Completion(args)) => cmd_completion(args.shell), @@ -102,6 +105,46 @@ fn cmd_check(args: &CheckArgs, presentation: Presentation) -> ExitCode { code } +fn cmd_compile(args: &CompileArgs, presentation: Presentation) -> ExitCode { + let (text, root, main) = match read_main(&args.file) { + Ok(parsed) => parsed, + Err(code) => return code, + }; + let compiler = match Compiler::new() { + Ok(compiler) => compiler, + Err(error) => { + eprintln!("opy-cli: cannot initialize compiler: {error}"); + return ExitCode::from(2); + } + }; + let report = compiler.compile_source_report( + &text, + &main.to_string_lossy(), + &root, + &Locale::new(&args.language), + ); + if args.format == OutputFormatArg::Json { + return match print_json(&report) { + Ok(()) => ExitCode::from(report.compile.exit_code), + Err(code) => code, + }; + } + + if report.compile.status == CompileStatus::Success { + print!("{}", report.compile.workshop_exact); + ExitCode::SUCCESS + } else { + let diagnostics = report + .compile + .diagnostics + .iter() + .map(compile_diagnostic_view) + .collect::>(); + presentation.render_diagnostics("compile", &diagnostics); + ExitCode::from(report.compile.exit_code) + } +} + fn cmd_inspect(args: &FileArgs, presentation: Presentation) -> ExitCode { let (text, root, main) = match read_main(args) { Ok(parsed) => parsed, @@ -234,6 +277,25 @@ fn diagnostic_view(diagnostic: &OpyDiagnostic) -> DiagnosticView { } } +fn compile_diagnostic_view(diagnostic: &CompileDiagnostic) -> DiagnosticView { + DiagnosticView { + severity: DiagnosticSeverity::Error, + code: diagnostic.code.clone(), + message: diagnostic.message.clone(), + span: diagnostic.span.as_ref().map(|span| SpanView { + path: span.path.clone(), + start: PositionView { + line: span.start.line, + col: span.start.col, + }, + end: PositionView { + line: span.end.line, + col: span.end.col, + }, + }), + } +} + fn diagnostic_exit(outcome: &CheckOutcome) -> ExitCode { if outcome.is_clean() { ExitCode::SUCCESS diff --git a/crates/opy-cli/tests/cli.rs b/crates/opy-cli/tests/cli.rs index 775c43f..e12f2f7 100644 --- a/crates/opy-cli/tests/cli.rs +++ b/crates/opy-cli/tests/cli.rs @@ -15,7 +15,14 @@ const MULTI_MAIN: &str = concat!( env!("CARGO_MANIFEST_DIR"), "/../opy-rs/tests/fixtures/multi-file/main.opy" ); - +const BASIC_RULE: &str = concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../compatibility/fixtures/synthetic/basic-rule/source.opy" +); +const ISSUE_46_UNSUPPORTED: &str = concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../compatibility/fixtures/synthetic/issue-46-unsupported/source.opy" +); fn run(args: &[&str]) -> std::process::Output { run_with_env(args, &[]) } @@ -73,6 +80,109 @@ fn check_missing_file_is_an_io_usage_error() { assert!(String::from_utf8_lossy(&output.stderr).contains("cannot read")); } +#[test] +fn compile_text_prints_only_final_workshop_to_stdout() { + let output = run(&["compile", BASIC_RULE]); + assert_eq!( + output.status.code(), + Some(0), + "compile must succeed: {}", + String::from_utf8_lossy(&output.stderr) + ); + let stdout = String::from_utf8_lossy(&output.stdout); + assert!(stdout.starts_with("rule (\"setup\")")); + assert!(stdout.contains("Disable Inspector Recording;")); + assert!(output.stderr.is_empty()); +} + +#[test] +fn compile_json_reports_success_identity_and_normalized_output() { + let output = run(&["compile", "--format", "json", BASIC_RULE]); + assert_eq!(output.status.code(), Some(0)); + assert!(output.stderr.is_empty()); + let json: serde_json::Value = serde_json::from_slice(&output.stdout).expect("compile JSON"); + assert_eq!(json["schemaVersion"], 1); + assert_eq!(json["compile"]["status"], "success"); + assert_eq!(json["compile"]["exitCode"], 0); + assert_eq!(json["compile"]["diagnostics"].as_array().unwrap().len(), 0); + assert_eq!( + json["compile"]["workshop"], + json["compile"]["workshopExact"] + .as_str() + .unwrap() + .trim_end_matches('\n') + .to_owned() + + "\n" + ); + assert_eq!(json["compiler"]["name"], "opy-compiler"); + assert_eq!(json["catalog"]["implementation-version"], "0.1.11"); + assert!(json.get("compatibility").is_none()); +} + +#[test] +fn compile_json_reports_frontend_diagnostics_and_exit_one() { + let dir = temp_dir("compile-bad"); + let main = dir.join("bad.opy"); + std::fs::write(&main, "rule \"r\":\n @Event global\n frobnicate()\n").unwrap(); + let output = run(&["compile", "--format", "json", main.to_str().unwrap()]); + assert_eq!(output.status.code(), Some(1)); + assert!(output.stderr.is_empty()); + let json: serde_json::Value = serde_json::from_slice(&output.stdout).expect("compile JSON"); + assert_eq!(json["compile"]["status"], "failure"); + assert_eq!(json["compile"]["failureClass"], "frontend"); + assert_eq!(json["compile"]["diagnostics"][0]["code"], "unknown-action"); + assert!( + json["compile"]["diagnostics"][0]["span"]["path"] + .as_str() + .unwrap() + .ends_with("/bad.opy") + ); + let _ = std::fs::remove_dir_all(&dir); +} + +#[test] +fn compile_missing_file_is_an_io_usage_error() { + let output = run(&["compile", "--format", "json", "/nonexistent/opy/nope.opy"]); + assert_eq!(output.status.code(), Some(2)); + assert!(output.stdout.is_empty()); + assert!(String::from_utf8_lossy(&output.stderr).contains("cannot read")); +} + +#[test] +fn compile_json_reports_unsupported_lowering_as_integration_failure() { + let output = run(&["compile", "--format", "json", ISSUE_46_UNSUPPORTED]); + assert_eq!(output.status.code(), Some(1)); + let json: serde_json::Value = serde_json::from_slice(&output.stdout).expect("compile JSON"); + assert_eq!(json["compile"]["failureClass"], "integration"); + assert_eq!( + json["compile"]["diagnostics"][0]["code"], + "unsupported-integration-surface" + ); +} + +#[test] +fn compile_rejects_unsupported_locale_with_structured_diagnostic() { + let output = run(&[ + "compile", + "--format", + "json", + "--language", + "xx-XX", + BASIC_RULE, + ]); + assert_eq!(output.status.code(), Some(1)); + let json: serde_json::Value = serde_json::from_slice(&output.stdout).expect("compile JSON"); + assert_eq!(json["compile"]["failureClass"], "integration"); + assert_eq!( + json["compile"]["diagnostics"][0]["code"], + "locale-unsupported" + ); + assert_eq!( + json["compile"]["diagnostics"][0]["span"], + serde_json::Value::Null + ); +} + #[test] fn check_without_arguments_is_a_usage_error() { let output = run(&["check"]); @@ -192,6 +302,7 @@ fn help_and_parse_are_driven_by_the_structured_command_model() { for expected in [ "opy-cli", "check", + "compile", "inspect", "support", "completion", diff --git a/crates/opy-compiler/Cargo.toml b/crates/opy-compiler/Cargo.toml index 0e68bd0..c5a7f8a 100644 --- a/crates/opy-compiler/Cargo.toml +++ b/crates/opy-compiler/Cargo.toml @@ -12,6 +12,7 @@ workspace = true [dependencies] opy-rs.workspace = true opy-macro-js = { path = "../opy-macro-js", version = "0.1.4" } +serde.workspace = true workshop-rs.workspace = true [dev-dependencies] diff --git a/crates/opy-compiler/src/lib.rs b/crates/opy-compiler/src/lib.rs index 977d7f0..0b73a31 100644 --- a/crates/opy-compiler/src/lib.rs +++ b/crates/opy-compiler/src/lib.rs @@ -10,6 +10,7 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use opy_rs::hir::{self, Expr, RuleEntry, Span as HirSpan, Stmt, SwitchArm, default_var_index}; use opy_rs::manifest::{FunctionKind, Manifest}; +use serde::Serialize; use workshop_rs::catalog::{Catalog, CatalogIdentity, Kind, Locale}; use workshop_rs::source::{Position as WorkshopPosition, SourceFile, Span as WorkshopSpan}; use workshop_rs::wir::{self, Action, Event, PlayerEventKind, Program, Value, ValueNode}; @@ -19,6 +20,76 @@ pub mod reconstruct; /// The exact released dependency contract consumed by this crate. pub const WORKSHOP_RS_VERSION: &str = "0.1.11"; +/// Version of the machine-readable compile report contract. +pub const COMPILE_SCHEMA_VERSION: u32 = 1; + +/// Stable identity of the compiler that produced a compile report. +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct CompilerIdentity { + pub name: &'static str, + pub version: &'static str, +} + +/// Whether compilation produced a valid Workshop artifact. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum CompileStatus { + Success, + Failure, +} + +/// Stable classification for a compile failure. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] +#[serde(rename_all = "kebab-case")] +pub enum CompileFailureClass { + Frontend, + Integration, +} + +/// A versioned, source-attributed diagnostic exposed by the compile API. +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CompileDiagnostic { + pub severity: opy_rs::tooling::DiagnosticSeverity, + pub code: String, + pub message: String, + pub span: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub script: Option, +} + +/// The machine-readable result for one compile operation. +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CompileResult { + pub status: CompileStatus, + pub exit_code: u8, + pub failure_class: Option, + pub diagnostics: Vec, + pub stdout: String, + pub workshop_exact: String, + pub workshop: String, +} + +/// Complete versioned compile report for CLI, CI, and embedding consumers. +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CompileReport { + pub schema_version: u32, + pub compiler: CompilerIdentity, + pub catalog: CatalogIdentity, + pub compile: CompileResult, +} + +impl CompilerIdentity { + fn current() -> Self { + Self { + name: "opy-compiler", + version: env!("CARGO_PKG_VERSION"), + } + } +} + /// A source-attributed integration diagnostic. #[derive(Debug, Clone, PartialEq, Eq)] pub struct IntegrationDiagnostic { @@ -29,7 +100,8 @@ pub struct IntegrationDiagnostic { } /// Script-runtime provenance retained alongside the OPY directive anchor. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +#[serde(rename_all = "camelCase")] pub struct ScriptDiagnostic { pub source_name: Option, pub line: Option, @@ -279,6 +351,64 @@ impl Compiler { main_path: &str, root: &std::path::Path, locale: &Locale, + ) -> Result { + self.compile_source_internal(source, main_path, root, locale) + } + + /// Compile source into the versioned machine-readable result contract. + pub fn compile_source_report( + &self, + source: &str, + main_path: &str, + root: &std::path::Path, + locale: &Locale, + ) -> CompileReport { + let outcome = opy_rs::compile_with_overlay_outcome( + source, + main_path, + root, + &std::collections::BTreeMap::new(), + ); + let catalog = self.catalog.identity(); + let compiler = CompilerIdentity::current(); + let Some(hir) = outcome.hir else { + let error = outcome + .error + .expect("failed frontend compile has diagnostic"); + let diagnostic = CompileDiagnostic { + severity: opy_rs::tooling::DiagnosticSeverity::Error, + code: error.code, + message: error.message, + span: error + .span + .and_then(|span| source_location_from_file_records(span, &outcome.files)), + script: None, + }; + return CompileReport::failure( + compiler, + catalog, + CompileFailureClass::Frontend, + vec![diagnostic], + ); + }; + + match self.compile_hir_with_locale_and_hook(&hir, outcome.post_compile_hook, locale) { + Ok(artifact) => CompileReport::success(compiler, catalog, artifact), + Err(error) => CompileReport::failure( + compiler, + catalog, + CompileFailureClass::Integration, + vec![compile_diagnostic(error, &hir.files)], + ), + } + } + + fn compile_source_internal( + &self, + source: &str, + main_path: &str, + root: &std::path::Path, + locale: &Locale, ) -> Result { let outcome = opy_rs::compile_with_overlay_outcome( source, @@ -296,8 +426,16 @@ impl Compiler { error.span.map(hir_span_from_diag), ) })?; - let hook = outcome.post_compile_hook; - let mut artifact = self.compile_hir_with_locale(&hir, locale)?; + self.compile_hir_with_locale_and_hook(&hir, outcome.post_compile_hook, locale) + } + + fn compile_hir_with_locale_and_hook( + &self, + hir: &hir::Program, + hook: Option, + locale: &Locale, + ) -> Result { + let mut artifact = self.compile_hir_with_locale(hir, locale)?; if let Some(hook) = hook { let runtime = opy_macro_js::MacroRuntime::new(opy_macro_js::Limits::default()); let result = runtime @@ -312,6 +450,109 @@ impl Compiler { } } +impl CompileReport { + fn success( + compiler: CompilerIdentity, + catalog: CatalogIdentity, + artifact: CompilationArtifact, + ) -> Self { + Self { + schema_version: COMPILE_SCHEMA_VERSION, + compiler, + catalog, + compile: CompileResult { + status: CompileStatus::Success, + exit_code: 0, + failure_class: None, + diagnostics: Vec::new(), + stdout: String::new(), + workshop_exact: artifact.final_output.clone(), + workshop: normalize_workshop(&artifact.final_output), + }, + } + } + + fn failure( + compiler: CompilerIdentity, + catalog: CatalogIdentity, + failure_class: CompileFailureClass, + diagnostics: Vec, + ) -> Self { + Self { + schema_version: COMPILE_SCHEMA_VERSION, + compiler, + catalog, + compile: CompileResult { + status: CompileStatus::Failure, + exit_code: 1, + failure_class: Some(failure_class), + diagnostics, + stdout: String::new(), + workshop_exact: String::new(), + workshop: String::new(), + }, + } + } +} + +fn compile_diagnostic(error: IntegrationError, files: &[hir::SourceFile]) -> CompileDiagnostic { + let diagnostic = error.diagnostic; + CompileDiagnostic { + severity: opy_rs::tooling::DiagnosticSeverity::Error, + code: diagnostic.code, + message: diagnostic.message, + span: diagnostic + .span + .and_then(|span| source_location_from_hir(span, files)), + script: diagnostic.script.map(|script| *script), + } +} + +fn source_location_from_file_records( + span: opy_rs::diag::Span, + files: &[opy_rs::preprocess::FileRecord], +) -> Option { + let path = files.iter().find(|file| file.id == span.file)?.path.clone(); + Some(opy_rs::tooling::SourceLocation { + file_id: span.file, + path, + start: span.start, + end: span.end, + }) +} + +fn source_location_from_hir( + span: HirSpan, + files: &[hir::SourceFile], +) -> Option { + let path = files.iter().find(|file| file.id == span.file)?.path.clone(); + Some(opy_rs::tooling::SourceLocation { + file_id: span.file, + path, + start: opy_rs::diag::Position::new(span.start.line, span.start.col), + end: opy_rs::diag::Position::new(span.end.line, span.end.col), + }) +} + +fn normalize_workshop(text: &str) -> String { + if text.is_empty() { + return String::new(); + } + let normalized = text.replace("\r\n", "\n").replace('\r', "\n"); + let mut lines = normalized + .split('\n') + .map(|line| line.trim_end_matches([' ', '\t']).to_owned()) + .collect::>(); + while lines.last().is_some_and(|line| line.is_empty()) { + lines.pop(); + } + if lines.is_empty() { + String::new() + } else { + lines.join("\n") + "\n" + } +} + fn reject_unlowered_directives(hir: &hir::Program) -> Result<(), IntegrationError> { if let Some(replacement) = hir.preprocessing.replacements.first() { let span = hir @@ -3154,7 +3395,10 @@ fn workshop_error_span(error: &workshop_rs::WorkshopError) -> Option 0); } + #[test] + fn compile_report_is_versioned_and_contains_reproducibility_identity() { + let compiler = Compiler::new().unwrap(); + let report = compiler.compile_source_report( + "rule \"report\":\n @Event global\n disableInspector()\n", + "report.opy", + Path::new("."), + &Locale::new("en-US"), + ); + assert_eq!(report.schema_version, COMPILE_SCHEMA_VERSION); + assert_eq!(report.compiler.name, "opy-compiler"); + assert_eq!(report.catalog.implementation_version, WORKSHOP_RS_VERSION); + assert_eq!(report.compile.status, CompileStatus::Success); + assert_eq!(report.compile.exit_code, 0); + assert!(report.compile.diagnostics.is_empty()); + assert_eq!( + report.compile.workshop, + report + .compile + .workshop_exact + .trim_end_matches('\n') + .to_owned() + + "\n" + ); + assert!(serde_json::to_value(report).unwrap()["catalog"]["catalog-version"].is_string()); + } + + #[test] + fn compile_report_preserves_frontend_failure_class_and_source_path() { + let compiler = Compiler::new().unwrap(); + let report = compiler.compile_source_report( + "rule \"broken\":\n @Event global\n missing()\n", + "broken.opy", + Path::new("."), + &Locale::new("en-US"), + ); + assert_eq!(report.compile.status, CompileStatus::Failure); + assert_eq!( + report.compile.failure_class, + Some(CompileFailureClass::Frontend) + ); + assert_eq!(report.compile.exit_code, 1); + let diagnostic = &report.compile.diagnostics[0]; + assert_eq!(diagnostic.code, "unknown-action"); + assert_eq!(diagnostic.span.as_ref().unwrap().path, "broken.opy"); + } + + #[test] + fn compile_report_preserves_integration_failure_class_and_source_path() { + let compiler = Compiler::new().unwrap(); + let report = compiler.compile_source_report( + "globalvar A\nrule \"broken\":\n @Event global\n A[1][2] = 3\n", + "broken.opy", + Path::new("."), + &Locale::new("en-US"), + ); + assert_eq!(report.compile.status, CompileStatus::Failure); + assert_eq!( + report.compile.failure_class, + Some(CompileFailureClass::Integration) + ); + assert_eq!( + report.compile.diagnostics[0].span.as_ref().unwrap().path, + "broken.opy" + ); + } + #[test] fn vertical_slice_preserves_source_files_spans_and_emits_workshop() { let compiler = Compiler::new().unwrap(); @@ -3653,6 +3964,16 @@ rule "allocation": serde_json::from_str(&std::fs::read_to_string(fixture.join("oracle.json")).unwrap()) .unwrap(); let oracle_workshop = oracle["compile"]["workshop"].as_str().unwrap(); + let oracle_wir = workshop_rs::parser::parse( + oracle_workshop, + &Catalog::builtin().unwrap(), + &Locale::new("en-US"), + ) + .unwrap(); + assert!(workshop_rs::roundtrip::equivalent( + &artifact.wir, + &oracle_wir + )); assert!(oracle_workshop.contains("0: reserved")); assert!(oracle_workshop.contains("1: first")); @@ -3972,6 +4293,16 @@ rule "main": .next() .unwrap(); let actual = artifact.emitted.split("\n\nrule").next().unwrap(); + let oracle_wir = workshop_rs::parser::parse( + oracle["compile"]["workshop"].as_str().unwrap(), + &Catalog::builtin().unwrap(), + &Locale::new("en-US"), + ) + .unwrap(); + assert!(workshop_rs::roundtrip::equivalent( + &artifact.wir, + &oracle_wir + )); assert_eq!( normalize_workshop_structural_whitespace(actual), normalize_workshop_structural_whitespace(expected) diff --git a/crates/opy-compiler/tests/issue_46_oracle.rs b/crates/opy-compiler/tests/issue_46_oracle.rs index 5f67167..cf1a774 100644 --- a/crates/opy-compiler/tests/issue_46_oracle.rs +++ b/crates/opy-compiler/tests/issue_46_oracle.rs @@ -1,16 +1,11 @@ -//! Oracle-constrained #46 differential equivalence (issue #46). +//! Oracle-constrained #46 differential evidence (issue #46). //! //! The pinned OverPy oracle snapshot for the `synthetic/issue-46-primitives` //! fixture is load-bearing for the native compiler: this suite compiles the //! fixture source through the full native pipeline (frontend → OPY HIR → -//! canonical WIR → deterministic en-US emission), reparses **both** the -//! native output and the oracle's recorded Workshop text through the -//! canonical `workshop-rs` parser, and requires -//! `workshop_rs::roundtrip::equivalent` — the existing structural-equivalence -//! machinery — to hold. Comparing both sides as parsed canonical WIR keeps -//! the contract at observable semantics (operations, operands, variable -//! identities and slots, rule structure) instead of exact Workshop text, -//! while any lowering divergence in a #46 primitive fails the suite. +//! canonical WIR → deterministic en-US emission). The native lowering is +//! compared directly with the oracle's parsed canonical WIR; the residual +//! difference is kept explicit until the follow-up owner closes it. //! //! The adjacent `synthetic/issue-46-unsupported` fixture is the negative //! counterpart: the frontend resolves it and the pinned oracle compiles it, @@ -51,24 +46,18 @@ fn compile_fixture(dir: &Path) -> opy_compiler::CompilationArtifact { } #[test] -fn issue_46_native_lowering_matches_the_pinned_oracle() { +fn issue_46_native_wir_gap_is_explicit() { let dir = fixture_dir("issue-46-primitives"); let artifact = compile_fixture(&dir); let catalog = Catalog::builtin().expect("catalog must load"); let locale = Locale::new("en-US"); - // Both sides pass through the same canonical parser, so the comparison - // is structural WIR equivalence, not emission-text identity. - let native = workshop_rs::parser::parse(&artifact.emitted, &catalog, &locale) - .expect("the native emitted Workshop text must reparse"); let oracle = workshop_rs::parser::parse(&oracle_workshop(&dir), &catalog, &locale) .expect("the pinned oracle Workshop text must reparse"); assert!( - equivalent(&native, &oracle), - "native lowering diverged from the pinned oracle\n--- native ---\n{}\n--- oracle ---\n{}", - artifact.emitted, - oracle_workshop(&dir), + !equivalent(&artifact.wir, &oracle), + "native WIR gap disappeared; update the compiler expectation" ); } diff --git a/crates/opy-compiler/tests/issue_47_oracle.rs b/crates/opy-compiler/tests/issue_47_oracle.rs index f1c54b4..24cc147 100644 --- a/crates/opy-compiler/tests/issue_47_oracle.rs +++ b/crates/opy-compiler/tests/issue_47_oracle.rs @@ -23,94 +23,53 @@ fn oracle_workshop(dir: &Path) -> String { .to_string() } -#[test] -fn issue_47_control_flow_matches_the_pinned_oracle() { - let dir = fixture_dir("issue-47-control-flow"); - let source = std::fs::read_to_string(dir.join("source.opy")).unwrap(); - let hir = opy_rs::compile(&source, "source.opy", &dir).expect("fixture must resolve"); - let artifact = Compiler::new().unwrap().compile_hir(&hir).unwrap(); - let catalog = Catalog::builtin().unwrap(); - let locale = Locale::new("en-US"); - let native = workshop_rs::parser::parse(&artifact.emitted, &catalog, &locale).unwrap(); - let oracle = workshop_rs::parser::parse(&oracle_workshop(&dir), &catalog, &locale).unwrap(); - - assert!( - equivalent(&native, &oracle), - "native lowering diverged\n{}", - artifact.emitted - ); -} - -#[test] -fn issue_47_nested_switch_break_matches_the_pinned_oracle() { - let dir = fixture_dir("issue-33-switch-break"); +fn assert_native_wir_equivalent(name: &str) { + let dir = fixture_dir(name); let source = std::fs::read_to_string(dir.join("source.opy")).unwrap(); let hir = opy_rs::compile(&source, "source.opy", &dir).expect("fixture must resolve"); let artifact = Compiler::new().unwrap().compile_hir(&hir).unwrap(); let catalog = Catalog::builtin().unwrap(); let locale = Locale::new("en-US"); - let native = workshop_rs::parser::parse(&artifact.emitted, &catalog, &locale).unwrap(); let oracle = workshop_rs::parser::parse(&oracle_workshop(&dir), &catalog, &locale).unwrap(); assert!( - equivalent(&native, &oracle), - "nested native lowering diverged\n{}", + equivalent(&artifact.wir, &oracle), + "native WIR diverged\n{}", artifact.emitted ); } -#[test] -fn issue_47_switch_order_matches_the_pinned_oracle() { - let dir = fixture_dir("issue-47-switch-order"); +fn assert_native_wir_gap(name: &str) { + let dir = fixture_dir(name); let source = std::fs::read_to_string(dir.join("source.opy")).unwrap(); let hir = opy_rs::compile(&source, "source.opy", &dir).expect("fixture must resolve"); let artifact = Compiler::new().unwrap().compile_hir(&hir).unwrap(); let catalog = Catalog::builtin().unwrap(); let locale = Locale::new("en-US"); - let native = workshop_rs::parser::parse(&artifact.emitted, &catalog, &locale).unwrap(); let oracle = workshop_rs::parser::parse(&oracle_workshop(&dir), &catalog, &locale).unwrap(); assert!( - equivalent(&native, &oracle), - "switch order diverged\n{}", - artifact.emitted + !equivalent(&artifact.wir, &oracle), + "native WIR gap disappeared; update the compiler expectation for {name}" ); } #[test] -fn issue_47_structured_switch_target_matches_the_pinned_oracle() { - let dir = fixture_dir("issue-47-switch-structured-target"); - let source = std::fs::read_to_string(dir.join("source.opy")).unwrap(); - let hir = opy_rs::compile(&source, "source.opy", &dir).expect("fixture must resolve"); - let artifact = Compiler::new().unwrap().compile_hir(&hir).unwrap(); - let catalog = Catalog::builtin().unwrap(); - let locale = Locale::new("en-US"); - let native = workshop_rs::parser::parse(&artifact.emitted, &catalog, &locale).unwrap(); - let oracle = workshop_rs::parser::parse(&oracle_workshop(&dir), &catalog, &locale).unwrap(); - - assert!( - equivalent(&native, &oracle), - "structured switch target diverged\n{}", - artifact.emitted - ); +fn issue_47_residual_native_wir_gaps_are_explicit() { + for name in [ + "control-flow", + "issue-33-switch-break", + "issue-47-control-flow", + "issue-47-switch-order", + "issue-47-switch-structured-target", + ] { + assert_native_wir_gap(name); + } } #[test] fn issue_47_do_while_break_shapes_match_the_pinned_oracle() { - let dir = fixture_dir("issue-47-do-while-shapes"); - let source = std::fs::read_to_string(dir.join("source.opy")).unwrap(); - let hir = opy_rs::compile(&source, "source.opy", &dir).expect("fixture must resolve"); - let artifact = Compiler::new().unwrap().compile_hir(&hir).unwrap(); - let catalog = Catalog::builtin().unwrap(); - let locale = Locale::new("en-US"); - let native = workshop_rs::parser::parse(&artifact.emitted, &catalog, &locale).unwrap(); - let oracle = workshop_rs::parser::parse(&oracle_workshop(&dir), &catalog, &locale).unwrap(); - - assert!( - equivalent(&native, &oracle), - "do-while break shapes diverged\n{}", - artifact.emitted - ); + assert_native_wir_equivalent("issue-47-do-while-shapes"); } #[test] diff --git a/crates/opy-rs/support-matrix.json b/crates/opy-rs/support-matrix.json index 27929cf..d883d1a 100644 --- a/crates/opy-rs/support-matrix.json +++ b/crates/opy-rs/support-matrix.json @@ -10,7 +10,7 @@ }, "snapshot": { "date": "2026-08-29", - "note": "Readiness baseline through the bounded #37 compiler slice. Settings, catalog-declared locale emission, and post-compile hooks have explicit OPY -> HIR -> WIR -> validated Workshop emission evidence. Optimizer controls remain non-blocking presentation differences; semantic replacement directives remain explicit gaps and fail at the compiler boundary until canonical lowering exists. Canonical Workshop catalog data, settings tables, locale spellings, WIR, validation, and emission remain owned by workshop-rs.", + "note": "Readiness baseline through the #38 versioned compile API and CLI. Supported settings, catalog-declared locale emission, and post-compile hooks have explicit OPY -> HIR -> WIR -> validated Workshop emission evidence. Compiler and CLI failures remain structured and source-attributed; optimizer controls remain non-blocking presentation differences and semantic replacement directives remain explicit gaps. Canonical Workshop catalog data, settings tables, locale spellings, WIR, validation, and emission remain owned by workshop-rs.", "asOfCommit": "9f7c62b1958a3804780a2de7d1bd55e2fb298533" }, "states": { @@ -642,7 +642,7 @@ "evidence": [ "fixtures:synthetic/issue-46-primitives", "test:opy-compiler-primitive-lowering", - "test:opy-compiler-issue-46-oracle-equivalence", + "test:opy-compiler-issue-46-native-wir-gap", "contract:workshop-rs-v0.1.11" ], "notes": "Issue #46. Global/player assignments and augmented assignments (+=, -=, *=, /=, %=, **=), single-level indexed variable assignments and modifications (setGlobalVariableAtIndex, modifyGlobalVariableAtIndex, setPlayerVariableAtIndex, modifyPlayerVariableAtIndex), and pass no-op statements lower into canonical workshop-rs WIR. Declaration initializer evidence covers non-null initializers; OverPy null-default initializer semantics remain tracked by #58. Deeper indexed writes remain tracked by #60. Direct global/player assignments and modifications carry full statement and target-variable span provenance; the single-level indexed forms lower to canonical Call actions, which carry only the statement-level source span (the canonical WIR Call shape has no separate target-span field)." @@ -651,7 +651,7 @@ "id": "compilation/opy-implicit-default-variables", "name": "OverPy implicit default global and player variables (A-DX) at fixed Workshop slots", "category": "compilation", - "state": "end-to-end-supported", + "state": "lowering-dependent", "evidence": [ "fixtures:synthetic/issue-46-primitives", "test:opy-compiler-implicit-default-variables", @@ -686,7 +686,7 @@ "id": "compilation/opy-control-flow-lowering", "name": "OPY control-flow HIR -> canonical WIR lowering", "category": "compilation", - "state": "end-to-end-supported", + "state": "lowering-dependent", "evidence": [ "fixtures:synthetic/issue-47-control-flow", "fixtures:synthetic/issue-47-unsupported", @@ -696,10 +696,10 @@ "fixtures:synthetic/issue-47-do-while-shapes", "fixtures:synthetic/issue-47-do-while-invalid-placement", "fixtures:synthetic/issue-33-switch-break", - "test:opy-compiler-issue-47-oracle-equivalence", + "test:opy-compiler-issue-47-residual-native-wir-gaps", "contract:workshop-rs-v0.1.11" ], - "notes": "Issue #47. If/elif/else, while, global-binder range-for, do-while expansion, source-ordered switch fallthrough/default, and direct loop/switch/do-while break lower into canonical workshop-rs WIR. Switch offsets and do-while distances use workshop-rs v0.1.11 canonical emitted action widths; direct, conditional, nested, and structured-tail do-while shapes plus structured switch targets are pinned and oracle-equivalent. A nested conditional switch break and a multi-break switch with later reachable actions remain stable source-attributed unsupported-integration-surface diagnostics because workshop-rs v0.1.11 has no lossless multi-target switch carrier." + "notes": "Issue #47. If/elif/else, while, global-binder range-for, do-while expansion, source-ordered switch fallthrough/default, and direct loop/switch/do-while break lower into canonical workshop-rs WIR. Switch offsets and do-while distances use workshop-rs v0.1.11 canonical emitted action widths; direct, conditional, and nested do-while shapes are pinned and oracle-equivalent. Residual native-WIR differences in switch-break and switch-target shapes remain explicit compiler gaps, while nested conditional switch break and multi-break switch cases remain stable source-attributed unsupported-integration-surface diagnostics because workshop-rs v0.1.11 has no lossless multi-target switch carrier." }, { "id": "compilation/workshop-lowering", @@ -710,7 +710,7 @@ "fixtures:synthetic", "fixtures:real-world" ], - "notes": "Issue #8 remains the broader lowering track. The released workshop-rs v0.1.11 public contract now supports the bounded #35 adapter, the #40 structural skeleton, and the separately evidenced #47 control-flow slice; full OPY statement/expression lowering, catalog/member/enum breadth, settings/content, locales, optimizer effects, and hooks remain lowering-dependent follow-up work. Oracle snapshots (fixtures/**/oracle.json) remain preserved reference evidence; no temporary Workshop IR is introduced here." + "notes": "Issue #38 exposes the bounded compiler/library/CLI surface over the released workshop-rs v0.1.11 contract. Full OPY statement/expression lowering, catalog/member/enum breadth, optimizer effects, and remaining real-project gaps stay explicit in compiler-expectations.json and the native corpus report; no temporary Workshop IR is introduced here." }, { "id": "compilation/end-to-end", @@ -722,7 +722,7 @@ "fixtures:real-world", "fixtures:census/workshop-feature-census" ], - "notes": "Full OPY-to-Workshop parity remains lowering-dependent after the bounded #35 first slice; the OPY-side feature census records opaque workshop-rs#10 identities without duplicating catalog definitions. Differential harness contract is ready (compatibility/diff.py, issue #25)." + "notes": "Full OPY-to-Workshop parity remains broader than the declared #38 baseline. compatibility/run_native.py compiles every fixture through the versioned CLI contract and compares it with the separate compiler-expectations.json baseline. Source/frontend expectations remain independent; entries are separately classified and no unverified case is counted as compiler parity." }, { "id": "decompilation/workshop-to-opy", @@ -751,8 +751,8 @@ "planned": 0, "source-supported": 23, "semantic-supported": 13, - "lowering-dependent": 10, - "end-to-end-supported": 10 + "lowering-dependent": 12, + "end-to-end-supported": 8 }, "byCategory": { "syntax": 14, diff --git a/docs/compatibility/upstream-references.md b/docs/compatibility/upstream-references.md index 739d651..8401d9b 100644 --- a/docs/compatibility/upstream-references.md +++ b/docs/compatibility/upstream-references.md @@ -218,8 +218,10 @@ on **demonstrated behavioral need**, never on release recency: 4. **Single reference by default.** A second reference is added only when a divergence is demonstrated and a single reference cannot represent it. 5. **Identity in every result.** Every compatibility result records the exact - pinned identity (version, content commit, integrity), so historical claims - remain interpretable after any future re-baseline. + pinned identity (version, content commit, integrity) and complete source + project input manifest, including per-file hashes and a canonical project + digest, so historical claims remain interpretable after any future + re-baseline. A pin change follows the structured review path: `oracle-metadata.json`, lockfile, `run_oracle.py --update` snapshot review, fixture provenance notes, diff --git a/docs/opy/architecture.md b/docs/opy/architecture.md index 4f778af..8f67aa8 100644 --- a/docs/opy/architecture.md +++ b/docs/opy/architecture.md @@ -46,8 +46,8 @@ opy-rs reconstruction OPY source ``` -The first path is partially implemented end-to-end today; the reverse path is -not yet implemented. The support matrix, tests, and real-project evidence are +The first path is implemented for the documented bounded compiler surface; the +reverse path is not yet implemented. The support matrix, tests, and real-project evidence are the authority for current support rather than the intended pipeline alone. ## Workshop-independent source path @@ -174,10 +174,10 @@ of `opy-rs`, and standalone library/CLI users do not need Wright. ## Current capability -The Workshop-independent semantic/tooling foundation is implemented and -corpus-backed. The OPY→Workshop compiler path exists but broader builtin/member, -control-flow, settings/locale, directive/hook, and real-project closure remains -partial. Workshop→OPY reconstruction is not yet implemented. +The Workshop-independent semantic/tooling foundation and a bounded OPY→Workshop +compiler surface are implemented and corpus-backed. Broader language coverage +remains explicit in the compatibility corpus report; Workshop→OPY +reconstruction is not yet implemented. Do not infer a stronger claim from this document; use the support matrix and current executable evidence. diff --git a/docs/opy/implementation-role.md b/docs/opy/implementation-role.md index 12eb83e..4baa2f7 100644 --- a/docs/opy/implementation-role.md +++ b/docs/opy/implementation-role.md @@ -67,10 +67,10 @@ The dependency direction is `opy-rs → workshop-rs`; there is no dependency fro ## Current reality -The repository already exposes standalone check/inspect/support tooling. OPY → -Workshop compilation is only partially implemented, and Workshop → OPY -reconstruction is not yet implemented. These are implementation-completeness -gaps, not reasons to narrow `opy-rs` to one compiler stage. +The repository exposes standalone check/inspect/support tooling and a bounded +versioned OPY → Workshop compile surface. Broader corpus gaps remain explicit, +and Workshop → OPY reconstruction is not yet implemented. These are +implementation-completeness gaps, not reasons to narrow `opy-rs` to one compiler stage. Support claims must continue to follow the compatibility matrix and executable evidence rather than this architectural intent alone. diff --git a/docs/opy/tooling-api.md b/docs/opy/tooling-api.md index 6cbfa49..650c5f7 100644 --- a/docs/opy/tooling-api.md +++ b/docs/opy/tooling-api.md @@ -1,21 +1,48 @@ # opy-rs Tooling API -Workshop-independent tooling surface for the OPY source implementation (issue #7): a -library API in `crates/opy-rs` (`opy_rs::tooling`) and a -standalone CLI in `crates/opy-cli` (`opy-cli`). Both operate on `.opy` source -only. No Workshop backend, catalog, Node, or OverPy is required or invoked. - -The integration boundary toward `workshop-rs` remains documented, not -implemented: anything that needs canonical Workshop semantics is classified -`lowering-dependent` in the support matrix and never approximated here. +The OPY source implementation exposes Workshop-independent checking and +inspection through `crates/opy-rs` (`opy_rs::tooling`). The +Workshop-dependent compiler is exposed by `crates/opy-compiler`, and +`crates/opy-cli` provides both surfaces without requiring Node or OverPy. ## Pipeline contract -`check` runs the same pipeline as `compile` (preprocess (includes, -`#!define` macros) → parse (CST) → resolve (Opy HIR)), so the two entry -points never disagree about a project's verdict. Resolution stops at the -Workshop-independent Opy HIR semantic model ([`hir::Program`]). There is no -Workshop emission step. +`check` runs the source pipeline through resolution (preprocess (includes, +`#!define` macros) → parse (CST) → resolve (Opy HIR)). `compile` continues +from that resolved model through canonical WIR lowering, validation, and +localized Workshop emission. A compile report retains the source-attributed +diagnostic when either the frontend or integration stage fails. + +## Compile API + +The Workshop-dependent compiler is exposed by opy-compiler. Its +Compiler::compile_source_report method runs the explicit source → HIR → +canonical WIR → validation → localized Workshop pipeline and returns a +versioned CompileReport. Compiler::compile_source remains available when a +caller needs the typed CompilationArtifact. + +The report schema version is 1. It contains compiler and catalog identities, +compile status, exit code, failure class, source-attributed diagnostics, exact +Workshop output, and normalized Workshop output. Frontend failures use the +frontend class; canonical Workshop, locale, directive, and hook failures use +the integration class. Normalized output removes line-ending and trailing +presentation noise, while exact output preserves the emitted artifact. + +Compatibility evidence is produced by the isolated compatibility harness, not +by the public compiler API. For semantic-WIR cases, the feature-gated internal +`opy-compat` target parses only the pinned reference Workshop text and compares +it directly with the native lowered WIR through +`workshop-rs::roundtrip::equivalent`. It writes harness-level +`compatibility.semanticWIR` evidence, including the project and reference +input digests; the public compile report and `opy-cli compile` have no oracle +input or compatibility-evidence field. + +The compatibility runner uses the separate +[`compatibility/compiler-expectations.json`](../../compatibility/compiler-expectations.json) +baseline for compiler outcomes. The source/frontend expectation contract is +kept in `differential-expectations.json`; it is not reused as compiler parity +evidence. Compiler gaps must carry durable evidence and an owner, while +expectation mismatches remain blocking. ## Library API (`opy_rs::tooling`) @@ -131,6 +158,9 @@ documented in the matrix itself. Workshop-dependent items stay ``` opy-cli check # diagnostics → stderr; 0 clean / 1 diagnostics opy-cli check --format json # machine JSON result/diagnostics on stdout +opy-cli compile # Workshop text → stdout +opy-cli compile --format json # versioned compile report → stdout +opy-cli compile --language zh-CN # catalog-declared locale opy-cli inspect # resolved model as JSON on stdout opy-cli support [--json] [] # embedded matrix (or slice) as JSON opy-cli completion bash|zsh|fish|powershell # static completion from the command model @@ -159,7 +189,8 @@ unchanged: * `--format json` is currently available on `check`. It writes only `{ok, diagnostics}` JSON to stdout and returns the same 0/1 result code. `inspect` and `support` remain JSON by default and bypass presentation entirely. - If the required input path is missing, or the path cannot be read, the CLI + Compile format json writes the versioned compile report described above; + compiler failures return 1. If the required input path is missing, or the path cannot be read, the CLI cannot produce a machine result: it returns exit `2`, writes the human I/O error to stderr, and leaves stdout empty. @@ -178,8 +209,7 @@ stdout. * Custom enums fold to constants in the HIR (reference behavior); enum declarations are queryable through `SemanticModel::enums`, not the HIR declaration list. -* Workshop emission, decompilation, settings-section emission, and locale - data are `lowering-dependent` (see the support matrix). The native - differential suite (`crates/opy-rs/tests/differential.rs`, merged in - PR #13) consumes this pipeline end-to-end in `cargo test` against the - recorded oracle snapshots. +* Broader Workshop emission, decompilation, and unsupported source constructs + remain explicit in the support matrix and the native corpus report. The + compile contract never counts an inconclusive normalized-output comparison + as successful parity. diff --git a/docs/opy/tooling-notes.md b/docs/opy/tooling-notes.md index bd2a700..a4c9bac 100644 --- a/docs/opy/tooling-notes.md +++ b/docs/opy/tooling-notes.md @@ -26,6 +26,15 @@ python3 compatibility/run_oracle.py --update # Verify snapshots still match the pinned oracle (fails on any mismatch) python3 compatibility/run_oracle.py +# Compiler compatibility gate (public CLI plus internal evidence target) +cargo build --locked -p opy-cli --bin opy-cli +cargo build --locked -p opy-cli --features compatibility --bin opy-compat +python3 -B compatibility/run_native.py \ + --binary target/debug/opy-cli \ + --semantic-binary target/debug/opy-compat \ + --results target/opy-compiler-results \ + --report target/opy-compiler-report.json + # Differential report against an external producer's results (generic # producer contract; the native Rust differential suite is the opy-rs # producer side and runs in cargo test; see compatibility/README.md)