This implements the `termination_by structural` syntax proposed in #3909. I went with `termination_by structural` over, say, `termination_by (config := {method := .structural})` mainly because it was easier to get going (otherwise I’d have to look into how to define recursive parsers, as `Parser.config` depends on `term` and `termination_by` is part of term. But also because I find it more ergonomic and aesthetic as a user. But syntax can still change. The `termination_by?` syntax will no longer force well-founded recursion, and instead the inferred `termination_by structurally` annotation will be shown if structural termination is possible. While I was it, this fixes #4546 the easy way (log errors about but otherwise ignore incomplete `termination_by` sets for mutual recursion). Maybe we get multiple replacements (#4551), but even then this this good behavior. Involves a bit of shuffling around `TerimationHints` (now validated for a clique already by `PreDefinition.main`) and `TerminationArguments` (now lifted out of the `WF` namespace, and a bit simplified). Fixes #3909 --------- Co-authored-by: Richard Kiss <him@richardkiss.com>
85 lines
2.1 KiB
Text
85 lines
2.1 KiB
Text
|
|
def foo (n : Nat) : Nat := match n with
|
|
| 0 => 0
|
|
| n+1 => foo n
|
|
termination_by structural n
|
|
|
|
-- Test that this is indeed by structural recursion
|
|
example : foo (n + 3) = foo n := Eq.refl _
|
|
|
|
|
|
-- Check that we can still refer to a variable called `structural` in
|
|
-- the `termination_by` syntax
|
|
def bar (structural : Nat) : True := match structural with
|
|
| 0 => .intro
|
|
| structural+1 => bar structural
|
|
termination_by «structural»
|
|
|
|
namespace Errors
|
|
-- A few error conditions
|
|
|
|
/--
|
|
error: argument #1 cannot be used for structural recursion
|
|
it is unchanged in the recursive calls
|
|
-/
|
|
#guard_msgs in
|
|
def foo1 (n : Nat) : Nat := foo1 n
|
|
termination_by structural n
|
|
|
|
/--
|
|
error: argument #2 cannot be used for structural recursion
|
|
its type is an inductive family and indices are not variables
|
|
n.succ.le 100
|
|
-/
|
|
#guard_msgs in
|
|
def foo2 (n : Nat) (h : n < 100) : Nat := match n with
|
|
| 0 => 0
|
|
| n+1 => foo2 n (by omega)
|
|
termination_by structural h
|
|
|
|
/--
|
|
error: one parameter bound in `termination_by`, but the body of Errors.foo3 only binds 0 parameters.
|
|
-/
|
|
#guard_msgs in
|
|
def foo3 (n : Nat) : Nat → Nat := match n with
|
|
| 0 => id
|
|
| n+1 => foo3 n
|
|
termination_by structural m => m
|
|
|
|
/--
|
|
error: argument #1 cannot be used for structural recursion
|
|
failed to eliminate recursive application
|
|
ackermann (n + 1) m
|
|
-/
|
|
#guard_msgs in
|
|
def ackermann (n m : Nat) := match n, m with
|
|
| 0, m => m + 1
|
|
| .succ n, 0 => ackermann n 1
|
|
| .succ n, .succ m => ackermann n (ackermann (n + 1) m)
|
|
termination_by structural n
|
|
|
|
/--
|
|
error: argument #2 cannot be used for structural recursion
|
|
failed to eliminate recursive application
|
|
ackermann2 n 1
|
|
-/
|
|
#guard_msgs in
|
|
def ackermann2 (n m : Nat) := match n, m with
|
|
| 0, m => m + 1
|
|
| .succ n, 0 => ackermann2 n 1
|
|
| .succ n, .succ m => ackermann2 n (ackermann2 (n + 1) m)
|
|
termination_by structural m
|
|
|
|
/--
|
|
error: The termination argument of a structurally recursive function must be one of the parameters 'n', but
|
|
id n + 1
|
|
isn't one of these.
|
|
-/
|
|
#guard_msgs in
|
|
def foo4 (n : Nat) : Nat → Nat := match n with
|
|
| 0 => id
|
|
| n+1 => foo4 n
|
|
termination_by structural id n + 1
|
|
|
|
|
|
end Errors
|