@@ -341,6 +341,21 @@ partial def listToExpr (ctx : WalkerContext) : List Sexp → MetaM Expr
341341 let aE ← sexpToExpr ctx a
342342 let bE ← sexpToExpr ctx b
343343 return mkForall .anonymous .default aE bE
344+ -- Generic application fallback. Used by `cong` to translate
345+ -- `(f a1 … an)` into `f a1 … an` for UF symbols. The head is
346+ -- looked up in `ctx.vars` (UF symbols are local free vars in
347+ -- the home-system goal); arguments recurse through
348+ -- `sexpToExpr`. Falls through to the catch-all error if the
349+ -- head is not in scope, keeping unrecognized shapes as honest
350+ -- failures rather than silently building ill-typed apps.
351+ | (.atom name) :: args => do
352+ match ctx.vars.find? (Name.mkSimple name) with
353+ | some fE => do
354+ let argEs ← args.mapM (sexpToExpr ctx)
355+ Lean.Meta.mkAppM' fE argEs.toArray
356+ | none =>
357+ throwError m! "alethe walker: unsupported applied head '{ name} ' \
358+ (not a recognized operator, not in local scope)"
344359 | other =>
345360 throwError m! "alethe walker: unsupported Sexp shape: \
346361 ({ String.intercalate " " (other.map fun s => reprStr s)} )"
@@ -548,6 +563,105 @@ private def elabResolution (ctx : WalkerContext) (s : Step)
548563 throwError m! "alethe walker: 'resolution' needs at least one \
549564 premise, got { repr s.premises} "
550565
566+ /- ----------------------------------------------------------------
567+ Equality cluster: `refl` / `symm` / `trans` / `cong`.
568+
569+ These are the rules cvc5's `alethe-2024` emits for the UF /
570+ equality fragment. None of them touch a decision procedure
571+ (unlike `la_generic` / `la_mult_neg` which omega-discharge a
572+ tautological leaf): they reconstruct the kernel proof from the
573+ premises directly, so the resulting proof terms are axiom-free
574+ (no `propext` / `Classical.choice`, just `Eq.rec` underneath).
575+ ---------------------------------------------------------------- -/
576+
577+ /-- `refl`: a leaf rule with no premises, concluding `(cl (= t t))`
578+ for any term `t`. The walker requires LHS and RHS to be
579+ syntactically identical at the Sexp level (the form cvc5
580+ emits after preprocessing); the proof term is `Eq.refl t`. -/
581+ private def elabRefl (ctx : WalkerContext) (s : Step)
582+ : WalkerM (Expr × List Sexp) := do
583+ match s.clause with
584+ | [.list [.atom "=" , lhs, rhs]] => do
585+ unless lhs == rhs do
586+ throwError m! "alethe walker: 'refl' expects (= t t) with \
587+ identical sides, got (= { repr lhs} { repr rhs} )"
588+ let lhsE ← sexpToExpr ctx lhs
589+ let proof ← mkAppM ``Eq.refl #[lhsE]
590+ pure (proof, s.clause)
591+ | _ =>
592+ throwError m! "alethe walker: 'refl' expects clause \
593+ (cl (= t t)), got { repr s.clause} "
594+
595+ /-- `symm`: one premise proving `(= t u)`, conclusion `(= u t)`.
596+ The proof term is `Eq.symm` of the premise. -/
597+ private def elabSymm (s : Step) : WalkerM (Expr × List Sexp) := do
598+ match s.premises with
599+ | some [p] => do
600+ let (eP, _) ← lookupStep p
601+ let proof ← mkAppM ``Eq.symm #[eP]
602+ pure (proof, s.clause)
603+ | _ =>
604+ throwError m! "alethe walker: 'symm' expects exactly one \
605+ premise, got { repr s.premises} "
606+
607+ /-- `trans`: n premises proving `(= t1 t2)`, `(= t2 t3)`, …,
608+ `(= t_{n} t_{n+1})`, conclusion `(= t1 t_{n+1})`. The proof
609+ term is the left-fold of `Eq.trans` over the premise list.
610+ A single-premise `trans` is a no-op (passthrough). -/
611+ private def elabTrans (s : Step) : WalkerM (Expr × List Sexp) := do
612+ match s.premises with
613+ | some (p0 :: rest) => do
614+ let (e0, _) ← lookupStep p0
615+ let mut acc := e0
616+ for pi in rest do
617+ let (ei, _) ← lookupStep pi
618+ acc ← mkAppM ``Eq.trans #[acc, ei]
619+ pure (acc, s.clause)
620+ | _ =>
621+ throwError m! "alethe walker: 'trans' expects at least one \
622+ premise, got { repr s.premises} "
623+
624+ /-- `cong`: n premises proving `(= a1 b1)`, …, `(= an bn)`,
625+ conclusion `(= (f a1 … an) (f b1 … bn))`. The proof term is
626+ built by left-folding `Lean.Meta.mkCongr` over the premise list,
627+ starting from `Eq.refl f`. `mkCongr` collapses the
628+ `Eq.refl f` seed into `mkCongrArg` automatically, then chains
629+ through `mkCongr`'s general case for each subsequent
630+ argument — so the resulting term is a curried congruence
631+ cascade (`(f a1) a2 = (f b1) b2` etc.) matching Lean's own
632+ curried application convention.
633+
634+ The function head is required to be identical on both sides
635+ (Sexp `BEq`); typically `fA = .atom "f"` for a UF symbol the
636+ walker resolves through `sexpToExpr`'s context lookup, but a
637+ higher-order head (an applied list) is also accepted as long
638+ as both sides agree structurally. Arity mismatch or differing
639+ heads throw a clear error. -/
640+ private def elabCong (ctx : WalkerContext) (s : Step)
641+ : WalkerM (Expr × List Sexp) := do
642+ match s.clause, s.premises with
643+ | [.list [.atom "=" , .list (fA :: argsA), .list (fB :: argsB)]],
644+ some pids => do
645+ unless fA == fB do
646+ throwError m! "alethe walker: 'cong' function heads differ: \
647+ { repr fA} vs { repr fB} "
648+ unless argsA.length == argsB.length do
649+ throwError m! "alethe walker: 'cong' arity mismatch: LHS has \
650+ { argsA.length} args, RHS has { argsB.length} "
651+ unless argsA.length == pids.length do
652+ throwError m! "alethe walker: 'cong' has { pids.length} \
653+ premises but { argsA.length} argument pairs"
654+ let fExpr ← sexpToExpr ctx fA
655+ let mut acc ← mkAppM ``Eq.refl #[fExpr]
656+ for pid in pids do
657+ let (eqProof, _) ← lookupStep pid
658+ acc ← Lean.Meta.mkCongr acc eqProof
659+ pure (acc, s.clause)
660+ | _, _ =>
661+ throwError m! "alethe walker: 'cong' expects clause \
662+ (cl (= (f …) (f …))) with a premise list, got \
663+ clause { repr s.clause} , premises { repr s.premises} "
664+
551665/-- LIA-tautology leaf rules (`la_generic`, `la_mult_neg`). The
552666 step's clause is a linear-arithmetic tautology — its negation
553667 is LIA-unsatisfiable, with the Farkas multipliers carried in
@@ -591,16 +705,19 @@ def elabStep (ctx : WalkerContext) (s : Step) : WalkerM Unit := do
591705 | "false" => elabFalseStep ctx s
592706 | "la_generic" => elabLiaLeaf ctx s
593707 | "la_mult_neg" => elabLiaLeaf ctx s
708+ | "refl" => elabRefl ctx s
709+ | "symm" => elabSymm s
710+ | "trans" => elabTrans s
711+ | "cong" => elabCong ctx s
594712 | other =>
595713 throwError m! "alethe walker: rule '{ other} ' not yet \
596714 supported (current scope: resolution / or / \
597- false / la_generic / la_mult_neg, plus \
598- seeded assumes. Subsequent PRs add the \
599- equality (cong / refl / trans / symm) and \
600- boolean-cleanup (hole / rare_rewrite / \
601- equiv_* / implies / and_neg) clusters — the \
602- omega fallback handles full cvc5 traces in \
603- the meantime)."
715+ false / la_generic / la_mult_neg / refl / \
716+ symm / trans / cong, plus seeded assumes. \
717+ Subsequent PRs add the boolean-cleanup \
718+ cluster (hole / rare_rewrite / equiv_* / \
719+ implies / and_neg) — the omega fallback \
720+ handles full cvc5 traces in the meantime)."
604721 storeStep s.id proof clause
605722
606723/-- Walk an Alethe proof and return the `Expr` proving the final
0 commit comments