feat(lift): extend v0 whitelist to method calls + multi-arg ctors#55
Conversation
PR #38 self-lifted the canonicalizer crate and surfaced an honest gap: of 82 candidate atoms the rust-tests adapter saw, only 13 lifted; 69 skipped. 53 of those 69 (77%) traced to one cause: the v0 expression whitelist accepted only single-arg calls, no method calls, no array or tuple literals, no byte-string literals. Every JCS byte-faithful claim in the canonicalizer (sorted keys, no whitespace, escape sequences, integer formatting) is tested via assert_eq!(encode_jcs(&Value::object([...])), "<canonical>") which the v0 grammar could not reach. The most load-bearing claims in the substrate were operationally enforced but not lifted. This PR widens the operand whitelist to v0.5. Newly accepted shapes: - free-function call f(a, b, ...) -> Ctor("f", [args]) - method call recv.f(a, b) -> Ctor("f", [recv, a, b]) (UFCS-flatten one level) - reference &expr -> lift(expr) (transparent) - cast expr as T -> lift(expr) (transparent) - array literal [a, b] -> Ctor("array", [args]) - tuple literal (a, b) -> Ctor("tuple", [args]) - vec! macro vec![a, b] -> Ctor("vec", [args]) - byte-string literal b"..." -> Ctor("byte_str", [str]) - binary op a OP b in operand position -> Ctor("<op>", [a, b]) assert! body grammar (translate_bool_expr) is unchanged: still requires a top-level binary comparison. starts_with/matches!-style boolean predicates remain skipped with a clean reason. Self-lift on provekit-canonicalizer, before / after: rust-tests seen=82 lifted=13 skipped=69 (v0) rust-tests-l2 seen=11 lifted=0 skipped=11 (v0) rust-tests seen=57 lifted=41 skipped=16 (v0.5) rust-tests-l2 seen=11 lifted=8 skipped=3 (v0.5) Catalog members 13 -> 49. Newly-lifted contracts include the load- bearing JCS shape: encode_simple_object_sorts_keys, encode_nested_array_object, escape_quotes_and_backslash, unicode_in_object_key_and_value, integer_renders_without_decimal_point, control_characters_are_unicode_escaped, distinct_inputs_distinct_hashes, differently_ordered_inputs_produce_same_output, and 28 more. Catalog CID changes correspondingly: v0: blake3-512:79ef1067...8632de v0.5: blake3-512:5725809e...71ce01 The IR is structurally faithful, not reductive. encode_jcs of a nested Value::object produces a fully-nested Ctor("encode_jcs", [Ctor("Value::object", [Ctor("array", [Ctor("tuple", [str("a"), str("x")])])])]) LHS. The verifier may not yet have a semantic interpretation of encode_jcs as a primitive, but the lattice is clean and the predicate is content-addressable. Existing lifter tests updated to reflect the new whitelist: - skips_method_call_with_warning -> lifts_method_call_as_ufcs_ctor (with IR-shape inspection assertions) - round_trip integration: deliberately-skipped fixture moved from method-call shape to format!() shape (the new v0.5 negative shape) - layer2 pattern3_releases_claim_when_only_one_atom_lifts: same. New positive tests added for multi-arg calls, vec! macro, casts, byte-string literals, binary ops in operand position. New negative tests added for format!() macro and block expressions. Idempotent: two runs produce the same CID. make self-lift-canonicalizer regenerates the proof at blake3-512:5725809e... and replaces the v0 proof in place. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
✨ 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. Review rate limit: 0/1 reviews remaining, refill in 4 minutes and 15 seconds.Comment |
Summary
Closes the substrate gap surfaced by ProvekIt's first self-lift run (PR #38). v0 of the rust-tests adapter accepted only single-arg or zero-arg constructor calls and rejected method calls outright. Of the 82 candidate atoms in
provekit-canonicalizer, 13 lifted; 53 of the 69 skips traced to that one whitelist limitation.The canonicalizer's load-bearing claim shape is
assert_eq!(encode_jcs(&Value::object([("k", v), ...])), "<canonical>"). v0 could not reach it. Every spec-mandated byte-faithful JCS rule (sorted keys, no whitespace, escape sequences, integer formatting) was operationally enforced by tests but unreachable by the auto-lifter.This PR widens the operand whitelist to v0.5. New shapes accepted:
f(a, b, ...)->Ctor("f", [args])recv.f(a, b)->Ctor("f", [recv, a, b])(UFCS, one level)&expr->lift(expr)(transparent)expr as T->lift(expr)(transparent)[a, b]->Ctor("array", [args])(a, b)->Ctor("tuple", [args])vec![a, b]macro ->Ctor("vec", [args])b"..."->Ctor("byte_str", [str])a OP bin operand position ->Ctor("<op>", [a, b])Out of scope: every Rust expression, the
assert!-body predicate grammar (still requires a binary comparison),format!()and other operand-position macros besidesvec![], indexing/field-access/closures/blocks/ranges.assert!(h.starts_with(PREFIX))still skips with a clean reason.Skip-count delta
seenfor rust-tests dropped from 82 to 57 because Layer 2 now claims 8 tests as characterization conjunctions (3-atominteger_renders_without_decimal_point, 8-atomcontrol_characters_are_unicode_escaped, etc.); claimed tests don't re-appear in Layer 0. Combined coverage shows up indecls=49.Catalog CID:
blake3-512:79ef1067...8632deblake3-512:5725809e...71ce01Sample IR-JSON for newly-lifted contracts
encode_simple_object_sorts_keys::0Source:
IR:
{ "Atomic": { "name": "=", "args": [ {"Ctor": {"name": "encode_jcs", "args": [{"Var": {"name": "v"}}]}}, {"Const": {"value": {"String": "{\"a\":\"x\",\"b\":1}"}, "sort": "String"}} ] } }(
vlifts as a free Var because the let-binding flow is a Layer 3 future-work feature; the structural lift is faithful to the source line.)integer_renders_without_decimal_point(Layer 2 characterization, 3-atom)Source:
IR (and-conjunction of 3 atoms; one shown):
{ "Atomic": { "name": "=", "args": [ {"Ctor": {"name": "encode_jcs", "args": [ {"Ctor": {"name": "Value::integer", "args": [ {"Const": {"value": {"Int": 42}, "sort": "Int"}} ]}} ]}}, {"Const": {"value": {"String": "42"}, "sort": "String"}} ] } }distinct_inputs_distinct_hashes::0Source:
IR:
{ "Atomic": { "name": "≠", "args": [ {"Ctor": {"name": "blake3_512_of", "args": [ {"Ctor": {"name": "byte_str", "args": [ {"Const": {"value": {"String": "hello"}, "sort": "String"}} ]}} ]}}, {"Ctor": {"name": "blake3_512_of", "args": [ {"Ctor": {"name": "byte_str", "args": [ {"Const": {"value": {"String": "world"}, "sort": "String"}} ]}} ]}} ] } }This is the contract that v0 could not reach (b"" not whitelisted) and the spec rule "distinct inputs distinct hashes" is now lifted.
Lift-report.txt diff summary
assert!-body skips are out of scope for this PR (separate code path); they belong in a v0.6 predicate-grammar extension.Existing lifter tests pass
provekit-lift-rust-tests::lib: passprovekit-lift-rust-tests::layer2: pass (pattern3_releases_claim_when_only_one_atom_liftsupdated to useformat!()as the negative shape since"hello".len()now lifts)fixture_lifts_5_skips_1_and_round_trips_through_verifier: pass (deliberately-skipped fixture moved from method-call shape to format!() shape)provekit-lift: passNew positive tests added:
lifts_method_call_as_ufcs_ctor(with IR-shape inspection),lifts_multi_arg_call_with_array_and_tuple(recursive ctor verification),lifts_vec_macro_as_ctor,lifts_cast_transparently,lifts_byte_string_literal,lifts_binary_op_in_operand_position.New negative tests added:
skips_format_macro_in_operand_position,skips_block_expression_in_operand_position.Expression shapes still skipped (with reason)
assert!(h.starts_with(PREFIX))andassert!(matches!(c, ...))(14 hits) — the assert! body grammar requires a top-level binary comparison; method-call results in boolean position need a v0.6 predicate-grammar extension or an IR atomic-predicate addition. Out of scope here.format!(...)and other macro calls in operand position (1 hit in fixture; many in real code) — explicit skip with reason "macroformat!(...)in operand position not lifted in v0.5". Onlyvec![...]is recognized..await— never reached this PR; skip with the catch-all v0.5 reason.Verification
Two runs produce the same CID.
provekit verifyreports zero load errors.A small diagnostic
cargo run --example dump_self_lift -- <workspace>is included underprovekit-lift/examples/for inspecting per-adapter warnings and lifted-contract IR. Used during this PR to verify the structural fidelity of the lift; reusable for future workspace investigations.Test plan
cargo test --release -p provekit-lift-rust-tests(24 tests pass)cargo test --release -p provekit-lift(11 tests pass)make self-lift-canonicalizerproduces v0.5 CID5725809e...deterministically across runsprovekit verify .provekit/self-lifts/canonicalizerreturns zero load errors🤖 Generated with Claude Code