Repository navigation
verify: State each Lean goal as named hypotheses, with a bridge the kernel checks - #59
Merged
Merged
Conversation
… and certify with the pinned toolchain.
…ut its function's identity.
…across regenerations.
…ment, never by position.
… in the architecture.
…n the tooling reference.
…el-checked bridge to the goal.
… by its named hypotheses.
…by its named hypotheses.
…y its named hypotheses.
…y its named hypotheses.
This was referenced Oct 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A Lean goal was one disjunction: the negation of every assumption before what it must show. A proof found its way to the conclusion by counting disjuncts, and nothing said which disjunct was the precondition, which a callee's postcondition, which the value of a variable:
Each goal now also has a readable form, with a bridge to the goal that the kernel checks:
A new proof file starts from it, with every name introduced, and a proof can use the hypotheses by name:
Design
Decision 6 of the Lean plan: the canonical goal stays the anchor, which certification checks and identity hashes, and the readable form is a checked consequence. Frama-C's WP labels each step of a sequent by kind (
Type:,Have:,When:) and with the property it comes from; Vermilion gives its hypotheses names from its VC generator, with an ordinal for each callee. This takes both, and adds the bridge neither has: Vermilion prints only the sequent, so a printer bug there is a wrong theorem.Where each assumption comes from. The passivizer records an origin with every assumption, from a current origin that each statement kind sets and its parts refine:
def(a definition),call:f(a callee's postcondition),inv,cond, andexit(a loop),keep(the value a path that skips an assignment keeps),exact(a machine operation's exact value),check,spec:f(a proved spec fact),reads:f, andchoose;prefor a written precondition,entryfor a generated one,null, andobject.makeGoalmarks eachOrit builds with the origin (LogicExpr::Premise). Like trigger patterns, the mark is outside the term's meaning, hash, and archive; the clones and the in-place simplifier keep it. A goal built elsewhere (spec checks) marks its assumptionsfact.The readable form peels the marked chain: each hypothesis is the operand its
Ornegates, or¬Bwhere simplification folded¬¬BtoB. It is namedh_<origin>_<n>, the ordinal counted within the goal so that a line shift renames nothing; a definition is named by what it defines (h_x_1). No name is ever a binder's (h_x_1_1beside a variableh_x_1). Itslets are the large subterms of the hypotheses and the conclusion. An unmarked disjunction, such as a postcondition!p || q, stays in the conclusion.The bridge
G.of_readable : G.readable → Gis a term with one prelude lemma per hypothesis:cppverify_imp_or : (a → b) → ¬a ∨ b, orcppverify_not_imp_or : (¬a → b) → a ∨ bfor a folded one. It builds withGenerated.lean, so certification checks every bridge, and a readable form that says less than its goal fails the build.Stubs apply the bridge and introduce every name,
lets included. A proof ofGitself, as earlier projects wrote, still checks, sinceCheck.leanstill checksG_proof : G.Tests
suite/lean_readable.cpp:lets, whose stub introduces them;Application type mismatch: The argument h_b_1 has type b_1 = __local_call_2_1 but is expected to have type b_1 = x_0 + BitVec.ofInt 32 2.lean_certify.cppproves itsTruegoals withtrivial, since after the stub'sapplythe goal is the readable form.Mutants of
LeanBackend.cpp,Obligation.cpp,ObligationSimplify.cpp,Passivize.cpp, andVerifier.cpp, each rebuilt and run against the Lean tests: 17 of 19 killed.the bridge's two lemmas swapped; a hypothesis always printed as the negated operand; no readable forms;
assumptions not marked; callee postconditions, statements, invariants, exact values, kept values, or spec postconditions left unnamed; written preconditions taken as generated;
a hypothesis allowed a variable's name; definitions not named by what they define;
the parts of a large goal, or the variables, not introduced; stubs without their names; stubs with names taken for proofs.
The two survivors drop the mark in the generic clones of a goal. No path from
makeGoalto the Lean exporter copies a markedOrtoday (the simplifier rewrites goals in place), so they are equivalent there. The copies stay, so the mark survives a copy as a source location does.Docs
--lean-projectrow.Checked
Builds on #58 (stable proof identity) and #56, whose commits come first here until they merge.
EncodingParityTest, which needs archives). The assertions build is left to CI.main, the new test fails from its first check of a readable form.-W;docs_coverage.testanddocs_examples.testpass, and the book's outputs are from this build. clang-format: clean.