feat: P-022 step 3 — port own-cfg (AST → CFG lowering) with exact CFG-JSON parity#203
Conversation
…-JSON parity
Third strangler-fig step of the P-022 Rust core migration. `own-cfg` lowers the
`own-syntax` AST to a control-flow graph and projects the canonical CFG-JSON
seam, mirroring `ownlang/{cfg,cfg_json,buffers}.py` line-for-line. Python stays
authoritative; this port introduces no behaviour change.
Crate (rust/crates/own-cfg, depends on own-syntax only, per the P-022 DAG — no
analysis/diagnostics edge):
* ir.rs — Kind/Symbol/Instr/Block/Cfg/Signature; symbol identity is a
newtype arena index (replacing Python's id(sym)), blocks are
threaded by BlockId so lowering never aliases a &mut.
* buffers.rs — buffers.py resolve/validate_policies/BufferInfo, value-for-value.
* builder.rs — the _Builder scope resolver + AST→CFG lowering + collect_*.
* json.rs — cfg_json.py projection plus a serializer that reproduces
json.dumps(indent=2, sort_keys=True, ensure_ascii=True)
byte-for-byte (the property the seam rests on).
Parity gate (mirrors the step-2 syntax fixtures):
* tests/test_cfg_fixtures.py (Python-authoritative) sweeps corpus/, examples/,
tests/fixtures/ plus curated cases, and freezes each input's canonical CFG
JSON + (line, code) diagnostics into tests/fixtures/cfg_parity.json; the
Python suite fails if it is stale.
* rust/crates/own-cfg/tests/parity.rs replays every case and asserts the
byte-identical CFG JSON, the identical diagnostics, and — for rejected
sources — the identical parser error text. 66 cases (60 corpus + 6 curated)
green; the Rust output also matches `python -m ownlang cfg --format json`
end-to-end.
Diagnostics carry code + line only at this step: the CFG-JSON seam does not
carry diagnostic message text (a verdict-layer contract pinned later by the
SARIF oracle), so the human message is deferred to the analysis/diagnostics
step, keeping this step's surface fully covered by its own oracle.
Closes #197
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cg6DVXahkyY68Ruu7f76rG
📝 WalkthroughWalkthroughThis PR adds a new Changesown-cfg crate: CFG lowering with Python parity
Estimated code review effort: 4 (Complex) | ~75 minutes Sequence Diagram(s)sequenceDiagram
participant Caller
participant build_module
participant Builder
participant resolve
participant canonical_json
Caller->>build_module: build_module(module)
build_module->>Builder: build_cfg(fn_decl, resources, signatures, policies, kinds)
Builder->>resolve: resolve buffer intent options
resolve-->>Builder: BufferInfo, diagnostics
Builder-->>build_module: Cfg, diagnostics
build_module-->>Caller: Vec<Cfg>, Vec<Diag>
Caller->>canonical_json: canonical_json(cfgs)
canonical_json-->>Caller: byte-canonical JSON string
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9334faa4ad
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // Python dict iterates in insertion order; a HashMap does not, so sort to | ||
| // keep the diagnostic stream stable.) | ||
| let mut names: Vec<&String> = policies.keys().collect(); | ||
| names.sort(); |
There was a problem hiding this comment.
Preserve policy diagnostic source order
When a caller validates multiple bad policy blocks whose names are not already sorted (for example policy Z before policy A), this sort emits the later A diagnostic before the earlier Z one. Python's authoritative validate_policies iterates policy declarations in insertion/source order, so the Rust port no longer matches the (line, code) diagnostic stream and users/fixtures see diagnostics out of source order; keep declaration order alongside the lookup map instead of sorting names.
Useful? React with 👍 / 👎.
…n order Python's `validate_policies` iterates `policies.values()` in dict insertion (source-declaration) order; the Rust port sorted policy names alphabetically for HashMap determinism, which reorders the (line, code) diagnostic stream when policy blocks aren't already alphabetized. Sort by declaration line instead — that IS the source order — so the port matches Python. Add a unit test with deliberately non-alphabetical policy names. Not observable on the CFG-JSON seam (validate_policies is a `check`-pipeline step, ported here for later use), but a divergence from Python all the same. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cg6DVXahkyY68Ruu7f76rG
Что и зачем
Третий шаг strangler-fig миграции P-022: новый крейт
rust/crates/own-cfgпонижает AST изown-syntaxв CFG и проектирует канонический CFG-JSON seam, зеркаляownlang/{cfg,cfg_json,buffers}.pyстрока-в-строку. Питон остаётся эталоном — поведение не меняется; расхождение считается багом Rust-стороны. Крейт зависит только отown-syntax(без рёбер на analysis/diagnostics, как требует DAG P-022).Состав:
ir.rs—Kind/Symbol/Instr/Block/Cfg/Signature; идентичность символа выражена newtype-индексом арены (вместоid(sym)), блоки протянуты черезBlockId, чтобы понижение никогда не алиасило&mut.buffers.rs—resolve/validate_policies/BufferInfoизbuffers.py, значение-в-значение.builder.rs— резолвер областей_Builder+ понижение AST→CFG +collect_*.json.rs— проекцияcfg_json.pyплюс сериализатор, воспроизводящийjson.dumps(indent=2, sort_keys=True, ensure_ascii=True)байт-в-байт (на этом и держится байтовая идентичность seam).Гейт паритета (по образцу syntax-фикстур шага 2):
tests/test_cfg_fixtures.py(питон — эталон) прогоняетcorpus/,examples/,tests/fixtures/+ курируемые кейсы и замораживает канонический CFG-JSON и диагностики(line, code)вtests/fixtures/cfg_parity.json;own-cfg/tests/parity.rsреплеит каждый кейс и требует байт-идентичный CFG-JSON, те же диагностики и — для отвергнутых источников — тот же текст ошибки парсера. 66 кейсов (60 корпус + 6 курируемых) зелёные; вывод Rust также сходится сpython -m ownlang cfg --format jsonend-to-end.Диагностики на этом шаге несут только
code + line: CFG-JSON seam не содержит текста сообщений (это контракт verdict-слоя, закрепляемый позже SARIF-оракулом), поэтому человекочитаемый текст отложен до шага analysis/diagnostics — при этом поверхность данного шага полностью покрыта собственным оракулом. PR не трогает схему OwnIR, так что отложенный OwnIR-rename сюда не подмешивается.Тип изменения
Как проверено
python tests/run_tests.py(включая новыеtest_cfg_fixtures.py, 66 кейсов в синхроне)ruff check .иmypy— чистоcd rust && cargo fmt --check && cargo clippy --all-targets && cargo test— 0 warnings, все тесты зелёныеpython scripts/oracle_exact.py --selftest— PASSСвязанные issue
Closes #197
Чеклист
feat:)🤖 Generated with Claude Code
https://claude.ai/code/session_01Cg6DVXahkyY68Ruu7f76rG
Generated by Claude Code
Summary by CodeRabbit
New Features
Tests