Commit graph

53 commits

Author SHA1 Message Date
Joachim Breitner
3d1d8fc1de
feat: unfolding functional induction principles (#8088)
This PR adds the “unfolding” variant of the functional induction and
functional cases principles, under the name `foo.induct_unfolding` resp.
`foo.fun_cases_unfolding`. These theorems combine induction over the
structure of a recursive function with the unfolding of that function,
and should be more reliable, easier to use and more efficient than just
case-splitting and then rewriting with equational theorems.

For example  instead of
```
ackermann.induct
  (motive : Nat → Nat → Prop)
  (case1 : ∀ (m : Nat), motive 0 m)
  (case2 : ∀ (n : Nat), motive n 1 → motive (Nat.succ n) 0)
  (case3 : ∀ (n m : Nat), motive (n + 1) m → motive n (ackermann (n + 1) m) → motive (Nat.succ n) (Nat.succ m))
  (x x : Nat) : motive x x
```
one gets
```
ackermann.fun_cases_unfolding
  (motive : Nat → Nat → Nat → Prop)
  (case1 : ∀ (m : Nat), motive 0 m (m + 1))
  (case2 : ∀ (n : Nat), motive n.succ 0 (ackermann n 1))
  (case3 : ∀ (n m : Nat), motive n.succ m.succ (ackermann n (ackermann (n + 1) m)))
  (x✝ x✝¹ : Nat) : motive x✝ x✝¹ (ackermann x✝ x✝¹)
```
2025-04-29 16:43:06 +00:00
Joachim Breitner
3fe195a4a9
fix: FunInd with nested well-founded recurison and late fixed parameters (#8094)
This PR fixes the generation of functional induction principles for
functions with nested nested well-founded recursion and late fixed
parameters. This is a follow-up for #7166. Fixes #8093.
2025-04-25 09:20:27 +00:00
Joachim Breitner
9fbdf847bd
fix: FunInd: properly split mutual structural recursion with extra parameters (#8086)
This PR makes sure that the functional induction priciples for mutually
recursive structural functions with extra parameters are split deeply,
as expected.
2025-04-24 13:32:53 +00:00
Joachim Breitner
d38d9400d8
fix: avoid panic in functional induction principle for structural recursion (#8083)
This PR fixes #8081.
2025-04-24 11:58:29 +00:00
Joachim Breitner
85f5a81f17
feat: FunInd: consume all type annotaions (#7997)
This PR removes all type annotations (optional paramters, auto
parameters, out params, semi-out params, not just optional parameters as
before) from the type of functional induction principles.
2025-04-17 07:52:17 +00:00
euprunin
2ea675369f
chore: fix spelling mistakes (#7328)
Co-authored-by: euprunin <euprunin@users.noreply.github.com>
2025-04-07 01:15:48 +00:00
Kim Morrison
7c41aad194 feat: deprecate Array.mkArray in favour of Array.replicate 2025-03-24 08:25:00 +01:00
Joachim Breitner
770af38c14
fix: fun_induction: correctly identify params and targets (#7622)
This PR fixes `fun_induction` when used on structurally recursive
functions where there are targets occurring before fixed parameters.

Fixes #7550
2025-03-21 12:12:15 +00:00
Sebastian Ullrich
24db5b598b
feat: use realizeConst for all equation, unfold, induction, and partial fixpoint theorems (#7261)
This PR ensures all equation, unfold, induction, and partial fixpoint
theorem generators in core are compatible with parallelism.

Stacked on #7247
2025-03-06 15:38:04 +00:00
Joachim Breitner
f45c19b428
feat: identify more fixed parameters (#7166)
This PR extends the notion of “fixed parameter” of a recursive function
also to parameters that come after varying function. The main benefit is
that we get nicer induction principles.


Before the definition

```lean
def app (as : List α) (bs : List α) : List α :=
  match as with
  | [] => bs
  | a::as => a :: app as bs
```

produced

```lean
app.induct.{u_1} {α : Type u_1} (motive : List α → List α → Prop) (case1 : ∀ (bs : List α), motive [] bs)
  (case2 : ∀ (bs : List α) (a : α) (as : List α), motive as bs → motive (a :: as) bs) (as bs : List α) : motive as bs
```
and now you get
```lean
app.induct.{u_1} {α : Type u_1} (motive : List α → Prop) (case1 : motive [])
  (case2 : ∀ (a : α) (as : List α), motive as → motive (a :: as)) (as : List α) : motive as
```
because `bs` is fixed throughout the recursion (and can completely be
dropped from the principle).

This is a breaking change when such an induction principle is used
explicitly. Using `fun_induction` makes proof tactics robust against
this change.

The rules for when a parameter is fixed are now:

1. A parameter is fixed if it is reducibly defq to the the corresponding
argument in each recursive call, so we have to look at each such call.
2. With mutual recursion, it is not clear a-priori which arguments of
another function correspond to the parameter. This requires an analysis
with some graph algorithms to determine.
3. A parameter can only be fixed if all parameters occurring in its type
are fixed as well.
This dependency graph on parameters can be different for the different
functions in a recursive group, even leading to cycles.
4. For structural recursion, we kinda want to know the fixed parameters
before investigating which argument to actually recurs on. But once we
have that we may find that we fixed an index of the recursive
parameter’s type, and these cannot be fixed. So we have to un-fix them
5. … and all other fixed parameters that have dependencies on them.

Lean tries to identify the largest set of parameters that satisfies
these criteria.

Note that in a definition like
```lean
def app : List α → List α → List α
  | [], bs => bs
  | a::as, bs => a :: app as bs
```
the `bs` is not considered fixes, as it goes through the matcher
machinery.


Fixes #7027
Fixes #2113
2025-03-04 22:26:20 +00:00
Joachim Breitner
36704e33bd feat: FunInd to split on bif as well
This PR treats `bif` (aka `cond`) like `if` in functional induction principles. It
introduces the `Bool.dcond` definition, with a docstring indicating that
this is for internal use.
2025-02-19 20:59:01 +01:00
Joachim Breitner
2a7b1162af fix: FunInd to clear match discriminants if possible
This PR tries to remove from functional induction principles hypotheses
that have been matched, as we expect the corresponding pattern to be
more useful. This avoids duplicate hypotheses due to the way `match`
refines hypotheses. Fixes #6281.
2025-02-17 18:21:10 +01:00
Joachim Breitner
96c6f9dc96
feat: fun_induction and fun_cases tactics (#7069)
This PR adds the `fun_induction` and `fun_cases` tactics, which add
convenience around using functional induction and functional cases
principles.

```
fun_induction foo  x y z
```
elaborates `foo x y z`, then looks up `foo.induct`, and then essentially
does
```
induction z using foo.induct y
```
including and in particular figuring out which arguments are parameters,
targets or dropped. This only works for non-mutual functions so far.

Likewise there is the `fun_cases` tactic using `foo.fun_cases`.
2025-02-16 10:59:56 +00:00
Joachim Breitner
a0776c33f0
refactor: FunInd: erase, not clear (#6923)
previously we did not include the “old” IH in the local context, so that
creating a MVar would not pick it up. But this always felt like a hack,
and prevented us from inferring types. So lets's try keeping them in the
context and using `withErasedFVars` only when creating metavariables.
2025-02-03 15:47:30 +00:00
Kim Morrison
21e8a99eff
feat: refactor of find functions on List/Array/Vector (#6833)
This PR makes the signatures of `find` functions across
`List`/`Array`/`Vector` consistent. Verification lemmas will follow in
subsequent PRs.

We were previously quite inconsistent about the signature of
`indexOf`/`findIdx` functions across `List` and `Array`. Moreover, there
are still quite large gaps in the verification lemma coverage for these
even at the `List` level.

My intention is to make the signatures consistent by providing:
`findIdx` / `findIdx?` / `findFinIdx?` (these all take a predicate, and
return respectively a `Nat`, `Option Nat`, `Option (Fin l.length)`) and
similarly `idxOf` / `idxOf?` / `finIdxOf?` (which look for an element)
for each of List/Array/Vector. I've seen enough examples by now where
each variant is genuinely the most convenient at the call-site, so I'm
going to accept the cost of having many closely related functions.
*Hopefully* for the verification lemmas we can simp all of these into
"projections" of the `Option (Fin l.length)` versions, and then only
have to specify that.

However, I will not plan on immediately either filling in the missing
verification lemmas (or even deciding what the simp normal forms
relating these operations are), and just reach parity amongst
List/Array/Vector for what is already there.
2025-01-30 01:14:21 +00:00
Joachim Breitner
3569797377
feat: functional cases theorem for non-recursive functions (#6261)
This PR adds `foo.fun_cases`, an automatically generated theorem that
splits the goal according to the branching structure of `foo`, much like
the Functional Induction Principle, but for all functions (not just
recursive ones), and without providing inductive hypotheses.

The design isn't quite final yet as to which function parameters should
become targets of the motive, and which parameters of the theorem, but
the current version is already proven to be useful, so start with this
and iterate later.
2025-01-21 10:16:42 +00:00
Joachim Breitner
7b813d4f5d
feat: partial_fixpoint: partial functions with equations (#6355)
This PR adds the ability to define possibly non-terminating functions
and still be able to reason about them equationally, as long as they are
tail-recursive or monadic.

Typical uses of this feature are
```lean4
def ack : (n m : Nat) → Option Nat
  | 0,   y   => some (y+1)
  | x+1, 0   => ack x 1
  | x+1, y+1 => do ack x (← ack (x+1) y)
partial_fixpiont

def whileSome (f : α → Option α) (x : α) : α :=
  match f x with
  | none => x
  | some x' => whileSome f x'
partial_fixpiont

def computeLfp {α : Type u} [DecidableEq α] (f : α → α) (x : α) : α :=
  let next := f x
  if x ≠ next then
    computeLfp f next
  else
    x
partial_fixpiont

noncomputable def geom : Distr Nat := do
  let head ← coin
  if head then
    return 0
  else
    let n ← geom
    return (n + 1)
partial_fixpiont
```

This PR contains

* The necessary fragment of domain theory, up to (a variant of)
Knaster–Tarski theorem (merged as
https://github.com/leanprover/lean4/pull/6477)
* A tactic to solve monotonicity goals compositionally (a bit like
mathlib’s `fun_prop`) (merged as
https://github.com/leanprover/lean4/pull/6506)
* An attribute to extend that tactic (merged as
https://github.com/leanprover/lean4/pull/6506)
* A “derecursifier” that uses that machinery to define recursive
function, including support for dependent functions and mutual
recursion.
* Fixed-point induction principles (technical, tedious to use)
* For `Option`-valued functions: Partial correctness induction theorems
that hide all the domain theory

This is heavily inspired by [Isabelle’s `partial_function`
command](https://isabelle.in.tum.de/doc/codegen.pdf).
2025-01-21 09:54:30 +00:00
Joachim Breitner
d27c5afa6e
refactor: ArgsPacker.unpack to return Option (#6359)
so that it can be used in pure code and that the error message can be
adjusted
2024-12-10 15:23:13 +00:00
Joachim Breitner
a9b6a9a975
refactor: WF.EqnInfo.hasInduct (#6357)
after #6355 not all functions with equation infos will support
functional induction, so prepare a flag to guide the name reservation.
2024-12-10 14:33:10 +00:00
Joachim Breitner
d2853ecbc4 feat: FunInd: omit unused parameters (#6330)
This PR removes unnecessary parameters from the funcion induction
principles. This is a breaking change; broken code can typically be adjusted
simply by passing fewer parameters.

Part 1, before stage0 update.

Closes #6320
2024-12-07 04:19:21 +01:00
Kim Morrison
ea221f3283
feat: Nat.(fold|foldRev|any|all)M? take a function which sees the upper bound (#6139)
This PR modifies the signature of the functions `Nat.fold`,
`Nat.foldRev`, `Nat.any`, `Nat.all`, so that the function is passed the
upper bound. This allows us to change runtime array bounds checks to
compile time checks in many places.
2024-11-22 03:05:51 +00:00
Joachim Breitner
93b4ec0351
refactor: use mkFreshUserName in ArgsPacker (#6093)
and other small refinements done while investigating an issue; not
actually user-visible.
2024-11-15 15:59:14 +00:00
Joachim Breitner
008537abbd
fix: FunInd: unfold aux definitions more carefully (#5904)
fixes #5903
2024-10-31 18:04:36 +00:00
Kim Morrison
218601009b
chore: rename Array.back to back! (#5897) 2024-10-31 09:18:18 +00:00
Joachim Breitner
5bea46deb0
fix: FunInd: withLetDecl and mkLetVar don’t mix (#5803)
Fixes: #5767
2024-10-22 10:15:14 +00:00
Kim Morrison
5d155d8b02
chore: simplify signature of Array.mapIdx (#5749)
This PR simplifies the signature of `Array.mapIdx`, to take a function
`f : Nat \to \a \to \b` rather than a function `f : Fin as.size \to \a
\to \b`.

Lean doesn't actually use the extra generality anywhere (so in fact this
change *simplifies* all the call sites of `Array.mapIdx`, since we no
longer need to throw away the proof).

This change would make the function signature equivalent to
`List.mapIdx`, hence making it easier to write verification lemmas.

We keep the original behaviour as `Array.mapFinIdx`.
2024-10-21 05:48:42 +00:00
Joachim Breitner
3e75d8f742
fix: FunInd: avoid over-eta-expanding in preprocessing step (#5619)
fixes #5602
2024-10-07 19:47:43 +00:00
euprunin
ba43ce18c3
chore: remove repeated words (#5438)
Co-authored-by: euprunin <euprunin@users.noreply.github.com>
Co-authored-by: Kim Morrison <scott.morrison@gmail.com>
2024-09-24 03:40:11 +00:00
euprunin
1b4ee185e8
chore: fix spelling mistakes in src/Lean/Meta/ (#5436)
---
This is the final set of fixes of this kind. Thanks for your patience!

Co-authored-by: euprunin <euprunin@users.noreply.github.com>
2024-09-23 23:09:14 +00:00
euprunin
8b61dda964
chore: fix spelling mistakes in error messages/exceptions (#5425)
Co-authored-by: euprunin <euprunin@users.noreply.github.com>
2024-09-23 13:19:05 +00:00
Joachim Breitner
445c8f2ee0
feat: FunInd: more equalities in context, more careful cleanup (#5364)
A round of clean-up for the context of the functional induction
principle cases.

* Already previously, with `match e with | p => …`, functional induction
would ensure that `h : e = p` is in scope, but it wouldn’t work in
dependent cases. Now it introduces heterogeneous equality where needed
(fixes #4146)
* These equalities are now added always (previously we omitted them when
the discriminant was a variable that occurred in the goal, on the
grounds that the goal gets refined through the match, but it’s more
consistent to introduce the equality in any case)
* We no longer use `MVarId.cleanup` to clean up the goal; it was
sometimes too aggressive (fixes #5347)
* Instead, we clean up more carefully and with a custom strategy:
* First, we substitute all variables without a user-accessible name, if
we can.
  * Then, we substitute all variable, if we can, outside in.
* As we do that, we look for `HEq`s that we can turn into `Eq`s to
substitute some more
  * We substitute unused `let`s.
  
**Breaking change**: In some cases leads to a different functional
induction principle (different names and order of assumptions, for
example).
2024-09-16 12:30:12 +00:00
Kim Morrison
8c6ac845b1 chore: cleanup after export Bool.and/or/not/xor 2024-09-16 12:45:51 +10:00
Kim Morrison
4e0f6b8b45 feat: export Bool.and/or/not/xor 2024-09-16 12:45:51 +10:00
Joachim Breitner
4c439c73a7
test: tracing and test case for #5347 (#5348)
not a fix, unfortunately, just recording the test.
2024-09-15 15:45:39 +00:00
Joachim Breitner
9f1eb479b0
feat: functional induction for mutual structural recursion (#4772) 2024-07-22 15:10:11 +00:00
Joachim Breitner
3a4d2cded3
refactor: Introduce PProdN module (#4807)
code to create nested `PProd`s, and project out, and related functions
were scattered in variuos places. This unifies them in
`Lean.Meta.PProdN`.

It also consistently avoids the terminal `True` or `PUnit`, for slightly
easier to read constructions.
2024-07-22 11:56:50 +00:00
Joachim Breitner
22ae04f3e7
refactor: FunInd overhaul (#4789)
This refactoring PR changes the structure of the `FunInd` module, with
the main purpose to make it easier to support mutual structural
recursion.

In particular the recursive calls are now longer recognized by their
terms (simple for well-founded recursion, `.app oldIH [arg, proof]`, but
tedious for structural recursion and even more so for mutual structural
recursion), but the type after replacing `oldIH` with `newIH`, where the
type will be simply and plainly `mkAppN motive args`).

We also no longer try to guess whether we deal with well-founded or
structural recursion but instead rely on the `EqnInfo` environment
extensions. The previous code tried to handle both variants, but they
differ too much, so having separate top-level functions is easier.

This also fuses the `foldCalls` and `collectIHs` traversals and
introduces a suitable monad for collecting the inductive hypotheses.
2024-07-21 14:46:52 +00:00
Joachim Breitner
1118978cbb
refactor: IndGroupInfo and IndGroupInst (#4738)
This adds the types
* `IndGroupInfo`, a variant of `InductiveVal` with information that
   applies to a whole group of mutual inductives and
* `IndGroupInst` which extends `IndGroupInfo` with levels and parameters
   to indicate a instantiation of the group.

One purpose of this abstraction is to make it clear when a fuction
operates on a group as a whole, rather than a specific inductive within
the group.

This is extracted from #4718 and #4733 to reduce PR size and improve
bisectability.
2024-07-13 08:30:09 +00:00
Joachim Breitner
2ad6d397f8
refactor: use indVal.numNested or indVal.numTypeFormers where applicable (#4734)
follow-up to #4684
2024-07-12 22:07:25 +00:00
Joachim Breitner
1311e36a98
feat: structural mutual recursion (#4575)
This adds support for mutual structural recursive functions.

For now this is opt-in: The functions must have a `termination_by
structural …` annotation (new since #4542) for this to work:

```lean
mutual
inductive A
  | self : A → A
  | other : B → A
  | empty
inductive B
  | self : B → B
  | other : A → B
  | empty
end

mutual
def A.size : A → Nat
  | .self a => a.size + 1
  | .other b => b.size + 1
  | .empty => 0
termination_by structural x => x
def B.size : B → Nat
  | .self b => b.size + 1
  | .other a => a.size + 1
  | .empty => 0
termination_by structural x => x
end
```

The recursive functions don’t have to be in a one-to-one relation to a
set of mutually recursive inductive data types. It is possible to ignore
some of the types:

```lean
def A.self_size : A → Nat
  | .self a => a.self_size + 1
  | .other _ => 0
  | .empty => 0
termination_by structural x => x
```

or have more than one function per argument type:

```lean
  def isEven : Nat → Prop
    | 0 => True
    | n+1 => ¬ isOdd n
  termination_by structural x => x

  def isOdd : Nat → Prop
    | 0 => False
    | n+1 => ¬ isEven n
  termination_by structural x => x
```


This does not include

 * Support for nested inductive data types or nested recursion
* Inferring mutual structural recursion in the absence of
`termination_by`.
 * Functional induction principles for these.
* Mutually recursive functions that live in different universes. This
may be possible,
maybe after beefing up the `.below` and `.brecOn` functions; we can look
into this some
   other time, maybe when there are concrete use cases.

---------

Co-authored-by: Richard Kiss <him@richardkiss.com>
Co-authored-by: Tobias Grosser <tobias@grosser.es>
2024-07-08 14:39:50 +00:00
Joachim Breitner
f0a11b8864
fix: FunInd: support structural recursion on reflexive types (#4327)
types like
```
inductive Many (α : Type u) where
  | none : Many α
  | more : α → (Unit → Many α) → Many α
```
have a `.brecOn` only supports motives producing `Type u`, but not `Sort
u`, but our induction principles produce `Prop`. So the previous
implementation of functional induction would fail for functions that
structurally recurse over such types.

We recognize this case now and, rather hazardously, replace `.brecOn`
with `.binductionOn` (and thus `.below ` with `.ibelow` and `PProd` with
`And`). This assumes that these definitions are highly analogous.

This also improves the error message when realizing a reserved name
fails with an exception, by prepending
```
Failed to realize constant {id}:
```
to the error message.

Fixes #4320
2024-06-05 07:54:48 +00:00
Joachim Breitner
c0fbcc76c4
feat: FunInd: reserve name .mutual_induct (#3898) 2024-04-16 11:59:40 +00:00
Joachim Breitner
c857d08be6
fix: remove derive_functional_induction (#3788)
this follows up on #3776 and the subsequent stage0 update, now relying
on the reserved name for the induction principles.
2024-03-27 10:08:13 +00:00
Joachim Breitner
ab318dda2d
feat: use reserved name infrastructure for functional induction (#3776)
no need to enter `derive_functional_induction` anymore.

(Will remove the support for `derive_functional_induction` after the
next stage0 update, since we are already using it in Init.)
2024-03-26 22:25:10 +00:00
Joachim Breitner
466ef74ccc
feat: functional induction for structural recursion (#3738)
This extends `derive_functional_induction` to work with structural
recursion as well.

It produces the less general, more concrete induction rule where the
induction hypothesis is
specialized for every argument of the recursive call, not just the the
one that the function
is recursing on.

Care is taken so that the induction principle and it's motive take the
arguments in the same
order as the original function.

While I was it, also makes sure that the order of the cases in the
induction principle matches
the order of recursive calls in the function better.

---------

Co-authored-by: David Thrane Christiansen <david@davidchristiansen.dk>
Co-authored-by: Leonardo de Moura <leomoura@amazon.com>
2024-03-26 13:36:24 +00:00
Joachim Breitner
e0c6c5d226
fix: functional induction: preseve order of cases better (#3762)
by passing an explicit array of metavariable around, instead of relying
on `getMVarsNoDelayed`, which may return them in unexpected order.
2024-03-25 11:59:29 +00:00
Joachim Breitner
80d2455b64
fix: prune universe params in functional induction (#3754)
fixes #3752
2024-03-24 10:15:50 +00:00
David Thrane Christiansen
966fa800f8
chore: remove the coercion from String to Name (#3589)
This coercion caused difficult-to-diagnose bugs sometimes. Because there
are some situations where converting a string to a name should be done
by parsing the string, and others where it should not, an explicit
choice seems better here.

---------

Co-authored-by: Mac Malone <tydeu@hatpress.net>
2024-03-21 23:46:03 +00:00
Mario Carneiro
49f66dc485
perf: rewrite UnusedVariables lint (#3186)
This is a rewrite of the `UnusedVariables` lint to inline and simplify
many of the dependent functions to try to improve the performance of
this lint, which quite often shows up in perf reports.

* The mvar assignment scanning is one of the most expensive parts of the
process, so we do two things to improve this:
  * Lazily perform the scan only if we need it
* Use an object-pointer hashmap to ensure that we don't have quadratic
behavior when there are many mvar assignments with slight differences.
* The dependency on `Lean.Server` is removed, meaning we don't need to
do the LSP conversion stuff anymore. The main logic of reference finding
is inlined.
* We take `fvarAliases` into account, and union together fvars which are
aliases of a base fvar. (It would be great if we had `UnionFind` here.)

More docs will be added once we confirm an actual perf improvement.

---------

Co-authored-by: Sebastian Ullrich <sebasti@nullri.ch>
2024-03-21 12:28:57 +00:00
Joachim Breitner
0b01ceb3bb
fix: substVars in functional inductions removed valuable information (#3695)
using the `substVars` tactic on the goal can remove too much
information, as it does not take into account that the `motive` may
depend on the fixed parameters.

This is fixed by etracting `substVar` from `subst` which expects the
`x`, not the `h : x = rhs`, and then using this tactic on the local
declarations _after_ the `motive` exclusively.
2024-03-16 14:55:31 +00:00