Skip to content

Add induction hypotheses to the generated principles for inductive types.#2

Closed
DIJamner wants to merge 2 commits into
triviajon:mainfrom
DIJamner:induction-principle-with-ih
Closed

Add induction hypotheses to the generated principles for inductive types.#2
DIJamner wants to merge 2 commits into
triviajon:mainfrom
DIJamner:induction-principle-with-ih

Conversation

@DIJamner

@DIJamner DIJamner commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Warning: the code in this PR was AI-generated and not human-validated. It should be treated with suspicion until reviewed line-by-line or rewritten.

Currently, generated principles like nat_ind are case-splitting principles, not induction principles, because they lack induction hypotheses. For example, currently the successor branch of nat_ind expects a proof of this shape: forall arg0, P (S arg0). With this PR, it should have this shape: forall arg0, P arg0 -> P (S arg0), which makes it possible to use it for induction. This PR also add support for parametric inductives like lists.

Given that we have fixpoints, it would seem like we could do the Rocq thing and generate a fixpoint definition rather than just the signature, but that seems blocked on dependent pattern matching, which is too much work to be worth it.

This branch also contains minimal examples of 3 (related) bugs, two of which cause segfaults and the third prevents some reducible terms from normalizing. It's probably worth fixing these if possible.

DIJamner and others added 2 commits June 27, 2026 21:07
The auto-generated <T>_ind was a dependent case-analysis principle: each
constructor case was `forall args, P (c args)`, with no hypothesis for
recursive arguments. That makes induction impossible (apply <T>_ind gives
no IH to work with).

Add an induction hypothesis for every first-order recursive constructor
argument, so e.g. nat_ind's step case becomes
`forall (n:nat), P n -> P (S n)` and tree_ind's becomes
`forall l r, P l -> P r -> P (node l r)`. Non-recursive arguments and
non-recursive types (bool) are unaffected.

Scope: non-parametric inductives only. Parametric `_ind` generation is
still skipped for a separate, pre-existing reason (constructor types use
fresh per-constructor parameter copies that don't match the builder's
parameter variables) and is left for a follow-up.

Adds a semantic regression test: a full abstract induction proof that
only type-checks when the IH is present.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GNvD2hCU3jo1wq6fCLv9GT
Previously the generator skipped the induction principle entirely for
parametric inductives (param_count > 0), because the principle was built
with the inductive's own param_vars while each constructor's stored type
used fresh per-constructor parameter copies in a divergent context branch.

Build the principle with its own parameter set, bound once after ind_var,
and derive each constructor case by *applying* the constructor to those
parameters (letting the kernel substitute them) rather than textually
stripping parameter binders. Argument variables are created fresh and the
remaining telescope is recomputed from the partial application after each
one, so argument and return types are rebased onto our variables and stay
valid even when a type mentions an earlier argument or a parameter (e.g.
cons's tail of type `list A`). This unifies the parametric and
non-parametric paths.

list_ind now matches Coq:
  forall A P, P (nil A) ->
    (forall a l, P l -> P (cons A a l)) -> forall l, P l

Adds a regression test that proves an abstract list induction (only
type-checks if list_ind has the IH-bearing shape).

Also adds bugs/ with two minimal reproducers (plus a clean-failure
contrast) for pre-existing crashes when inducting over goals stated with a
Fixpoint: `apply` with a fixpoint-mentioning motive, and type-checking an
explicit eliminator term whose step case must reduce `add (S n) O`. These
are documented, not fixed (Tier 2: symbolic iota/fix reduction).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GNvD2hCU3jo1wq6fCLv9GT
@DIJamner

Copy link
Copy Markdown
Contributor Author

A work-in-progress note: I'm triangulating the sources of the bugs (they all see fixpoint related (e.g. a stack overflow)) and will have more info on them soon.

@DIJamner

Copy link
Copy Markdown
Contributor Author

Here is a quick summary of my understanding of these bugs, which have some common roots:

  1. A fixpoint with a variable as its recursive argument incorrectly unfolds eagerly, which happens recursively and blows the stack.
  2. Something about the related case with conversion between two definitionally equal expressions that use a fixpoint can cause typechecking to incorrectly fail.

#3 also identifies a couple unrelated bugs and provides speculative (LLM-written, and so untrustworthy) fixes that might be useful either for careful human review or as a reference.

@DIJamner

Copy link
Copy Markdown
Contributor Author

#3 now also contains (un-reviewed, LLM-generated) fixes for these bugs, which may or may not be helpful.

@DIJamner DIJamner mentioned this pull request Jun 28, 2026
@DIJamner

Copy link
Copy Markdown
Contributor Author

This has been folded into #4 . It can be broken out separately if needed, but for now I am closing this issue.

@DIJamner DIJamner closed this Jun 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant