Skip to content

Proofs and tactics

Ligare uses the Curry–Howard view: a proposition is a constraint, and a proof is a term checked against that constraint. The current implementation has two root universes:

  • data contains values retained by the generated program.
  • prop contains propositions and evidence erased after checking.

There are no separate theorem or proof universes. theorem is a top-level command for naming a checked proposition inhabitant.

Proof blocks

The by form builds a term interactively:

ligare
theorem identity : int -> int := by
  intro value
  exact value

Proofs can also discharge a refinement at an expression:

ligare
def NonNegative : prop := int where (value => value >= 0)

#check 0 by
  exact auto : NonNegative

Core tactics

The current prover includes:

TacticRole
exact termClose the goal with a term.
introIntroduce Pi-type parameters or hypotheses.
apply termUse a function to reduce the current goal.
have name := termAdd a local lemma.
constructorBuild conjunctions and structs.
cases termSplit Boolean or enum cases.
induction termGenerate structural enum branches.
assumptionUse a matching local theorem.
rflClose a reflexive equality.
rw and simpRewrite with equality evidence.
solve, decide, norm_num, omega, linarithRequest bounded, kernel-checked automation.

Automation is fail-closed: an unknown proposition is not treated as proven. The kernel checks the generated evidence after tactic elaboration. Custom #[tactic] functions can extend the tactic surface, but their result still passes through normal checking.

Erasure

Evidence for a prop constraint is removed before C code generation. A refinement can therefore make an interface safer without adding a proof object or proof parameter to the runtime representation.