Expand description
Build ProofIR from a CodegenContext.
The lowering producer: types live in src/ir/proof_ir.rs, this
file fills them in from a typechecked + analysed codegen
context. Output lands in CodegenContext.proof_ir; both proof
backends read from the same field, so any classifier-side
decision flows consistently to Lean and Dafny without each
backend re-running shape detection.
Populates three IR sections: refined_types (refinement-via-
opaque records → Lean Subtype / Dafny subset type),
fn_contracts (per-pure-fn recursion shape: native /
sized-fuel / linear recurrence), and law_theorems (per-verify-
law strategy + quantifier decomposition + claim shape, with
Oracle-Lift’d impl-spec calls for effectful equivalence).
tests/proof_ir_diff.rs pins the producer’s output for each
canonical source pattern — divergence between the classifier and
the IR populator surfaces there.
§Epic #170 Phase 7 invariant — AST discovery + typed identity
This module is the last consumer of raw crate::ast::Expr
patterns in the codegen layer. That is intentional, not
migration debt.
§What’s AST-shaped (syntax-discovery-only)
Detector helpers in this file (detect_*, walk_for_*,
callee_matches_name, call_named_args, binary_call_var_const,
matches_ident_expr) walk ast::Expr directly. They are
pattern matchers over source shape — they look for things
like match n { 0 -> base; _ -> rec(n - 1) } or
Map.has(outer(m, k), k) to decide which ProofStrategy /
RecursionPlan variant lowers a given fn or law. The pattern
belongs in source-shape; rewriting them on ResolvedExpr would
be the same logic spelled in a different enum, no extra safety.
Every detector helper carries a syntax-discovery-only comment
at its definition.
§What’s identity-sensitive (typed IDs)
Decisions that depend on which fn / type / ctor a name
refers to (not just “does this name appear”) MUST go through
SymbolTable or ProofIR.refined_types (TypeId-keyed) /
ProofIR.fn_contracts (FnId-keyed). Examples:
- Refinement-carrier lookups go through
find_refined_type/resolve_refined_type_in_with_key, both of which canonicalise the name through the symbol table before reaching the IR map. - Fn-contract lookups go through
find_fn_contract_for_fn— pointer-eq scope on&FnDefresolves to the rightFnId. - The Lean native-guarded rewriter pins target by
FnIdviarewrite_native_guarded_calls_resolved_expr(PR 169).
§What stays raw-AST as a documented identity exception
Builtin matchers (callee_is X for X ∈ {"Bool.and", "Map.set", …}) compare against the canonical builtin namespace, which is
global by spec — no per-scope identity to leak. Verify-law
callsites all walk vb.fn_name (entry-only by parser grammar);
the EntryFnIndex newtype in verify_law.rs pins the
entry-only contract at the type level (PR 177).
Full ResolvedProofLowerView + semantic matcher API
(callee_is_builtin, callee_is_fn(FnId), ctor_is,
ident_name, int_lit) deferred per
project_phase_e_scope_b_deferred memory until a real trigger
lands (module-scoped verify, dotted law targets, LSP rename,
cross-scope inliner).
Structs§
- Proof
Lower Inputs - Backend-neutral view of the data
proof_lowerneeds. Built once per lowering call; lets the pipeline pass it through without requiring a fully-assembledCodegenContext(which only exists afterbuild_contextruns). Legacy callers still build the view from&CodegenContextviaProofLowerInputs::from_ctx.
Functions§
- carrier_
eligibility_ demotions - ETAP-2 carrier-
i64SLICE 2b FOLLOW-UP: the fail-closed eligibility tightening. The bare carrier interval (carrier_interval_table) proves only that the smart-constructor’s invariantfits_i64; it does NOT prove that every value of the carrier type actually went through that gate, nor that the carrier’s codegen is exercised only in positions the i64 erasure supports. This scan removes a carrier from the eligible set when either assumption is violated, so the carrier stays boxed ($AverInt) — the safe, pre-slice representation that the VM and the boxed wasm-gc path agree on. - carrier_
interval_ table - ETAP-2 SLICE 0+1: derive, per refinement-via-opaque type in scope, the
constant interval its smart-constructor invariant proves over the
carrier. The table is keyed by the opaque type’s bare Aver name (e.g.
"IntRange"). The bare-i64MIR pass matches a carrier parameter/local/return slot against this key — it extracts the bare name from the slot’sMirParam.ty(which the lowerer fills with the Debug formNamed { id: …, name: "IntRange" }, NOT the bare name; seebare_named_typeinbare_i64) and seeds the slot with the proven bound. - field_
carrier_ eligible_ intervals - ETAP-2 multi-field carrier-
i64: the per-(record, field)ELIGIBLE map —field_carrier_interval_tabletightened the same way the single-field path tightens its proven-bound set. An entry survives only when its bound is recognized ANDfits_i64AND the owning record is NOT demoted by any whole-program scan ([multi_field_record_demotions]): - field_
carrier_ interval_ table - ETAP-2 multi-field carrier-
i64: derive, per(record-type, Int-field)pair in scope, the constant interval a MULTI-ARG smart constructor’s guard proves over that field. This generalizescarrier_interval_tablefrom the single-value-field carrier TYPE to a multi-field record whose 2+-arg smart constructor bounds each field independently. - lower
- Run every proof-export lowering in one shot — convenience for
callers that want a fully-populated ProofIR. The pipeline calls
the three
populate_*fns directly so it can run them as independent stages and short-circuit on typecheck failure. - populate_
fn_ contracts - Walk
analyze_plans(inputs)and populateProofIR.fn_contracts. - populate_
law_ theorems - Walk every verify block, lift
VerifyKind::Lawentries intoProofIR.law_theorems. - populate_
refined_ types - Refinement-via-opaque lift. Walks every type definition (entry +
dep modules), classifies the records that pair a single carrier
field with a validating smart constructor, and emits
RefinedTypeDeclentries intoir.refined_types. Backends (Lean → Subtype, Dafny → subset type) render these directly.