Zulip Chat Archive
Stream: new members
Topic: Termination checker problem for mutual block
Vadim Zaliva (Nov 06 2025 at 18:16):
I am hitting a problem with the termination checker on a relatively simple example: https://gist.github.com/vzaliva/df21596578211d99d7aa5d2699fec46b. The error "Cannot derive unfold equation beqExpr._mutual.eq_def
Tactic simp failed with a nested error: (deterministic) timeout at whnf, maximum number of heartbeats (200000) has been reached". I've tried increasing maxHeartbeats, but it didn't help. I manually provided the termination measure and proofs for all functions in the block, but it still did not help. Any suggestions on how to fix that?
Aaron Liu (Nov 06 2025 at 21:55):
seems to work fine if you do structural recursion
import Std.Data.HashMap
import Mathlib.Tactic
open Std
abbrev Address := Nat
abbrev Name := String
inductive Typ : Type where
| int : Typ
| bool : Typ
| unit : Typ
| ref : Typ → Typ
| arrow : Typ → Typ → Typ
| string : Typ
deriving Repr, DecidableEq
def Typ.beq : Typ → Typ → Bool
| int, int => true
| bool, bool => true
| unit, unit => true
| string, string => true
| ref t1, ref t2 => Typ.beq t1 t2
| arrow t1 t2, arrow t1' t2' => Typ.beq t1 t1' && Typ.beq t2 t2'
| _, _ => false
instance : BEq Typ where
beq := Typ.beq
inductive Op : Type where
| add : Op
| sub : Op
| mul : Op
| div : Op
| gte : Op
| lte : Op
| gt : Op
| lt : Op
| eq : Op
| and : Op
| bitand : Op
| or : Op
| neq : Op
deriving Repr, BEq, DecidableEq
inductive UnaryOp : Type where
| not : UnaryOp
deriving Repr, BEq, DecidableEq
abbrev Tag := Nat
inductive EffectTarget where
| namedVar : Name → EffectTarget
| returnValue : EffectTarget
deriving Repr, BEq
mutual
inductive SyntaxPredicate : Type where
| guardClosure: Tag → Name → Expr → SyntaxPredicate
| guardOnly : Tag → SyntaxPredicate
| noPred : SyntaxPredicate
structure Effect where
target : EffectTarget
poison : Bool
pred : SyntaxPredicate
inductive ExternReturn where
| simple : Typ → Bool → SyntaxPredicate → ExternReturn
| nestedFn : Name → Typ → ExternReturn → List Effect → ExternReturn
| nestedPfn : Name → Typ → ExternReturn → ExternReturn
inductive Expr : Type where
| unit : Expr
| num : Int → Expr
| bool : Bool → Expr
| str : String -> Expr
| var : Name → Expr
| op : Expr → Op → Expr → Expr
| unaryop : UnaryOp → Expr → Expr
| ite : Expr → Expr → Expr → Expr
| newRef : Expr → Expr
| assign : Expr → Expr → Expr
| deref : Expr → Expr
| seq : Expr → Expr → Expr
| kwhile : Expr → Expr → Expr
| vlet : Name → Expr → Expr → Expr
| fn : Name → Typ → Expr → Typ → Expr
| pfn : Name → Typ → Expr → Typ → Expr
| app : Expr → Expr → Expr
| rand : Expr -> Expr
| frand : Expr -> Expr
| printString : Expr -> Expr
| printInt : Expr -> Expr
| externVal : Typ → Bool → SyntaxPredicate → Expr
| externFn : Name → Typ → ExternReturn → List Effect → Expr
| externPfn : Name → Typ → ExternReturn → Expr
end
mutual
def beqExpr : Expr → Expr → Bool
| Expr.unit, Expr.unit => true
| Expr.num n1, Expr.num n2 => n1 == n2
| Expr.bool b1, Expr.bool b2 => b1 == b2
| Expr.str s1 , Expr.str s2 => s1 == s2
| Expr.var x1, Expr.var x2 => x1 == x2
| Expr.op e1 op1 e2, Expr.op e1' op2 e2' => beqExpr e1 e1' && op1 == op2 && beqExpr e2 e2'
| Expr.unaryop op1 e1, Expr.unaryop op2 e2 => op1 == op2 && beqExpr e1 e2
| Expr.ite e1 e2 e3, Expr.ite e1' e2' e3' => beqExpr e1 e1' && beqExpr e2 e2' && beqExpr e3 e3'
| Expr.newRef e1, Expr.newRef e2 => beqExpr e1 e2
| Expr.assign e1 e2, Expr.assign e1' e2' => beqExpr e1 e1' && beqExpr e2 e2'
| Expr.deref e1, Expr.deref e2 => beqExpr e1 e2
| Expr.seq e1 e2, Expr.seq e1' e2' => beqExpr e1 e1' && beqExpr e2 e2'
| Expr.kwhile e1 e2, Expr.kwhile e1' e2' => beqExpr e1 e1' && beqExpr e2 e2'
| Expr.vlet x1 e1 e2, Expr.vlet x2 e1' e2' => x1 == x2 && beqExpr e1 e1' && beqExpr e2 e2'
| Expr.fn x1 t1 e1 t1', Expr.fn x2 t2 e2 t2' => x1 == x2 && t1 == t2 && beqExpr e1 e2 && t1' == t2'
| Expr.pfn x1 t1 e1 t1', Expr.pfn x2 t2 e2 t2' => x1 == x2 && t1 == t2 && beqExpr e1 e2 && t1' == t2'
| Expr.app e1 e2, Expr.app e1' e2' => beqExpr e1 e1' && beqExpr e2 e2'
| Expr.rand e1, Expr.rand e2 => beqExpr e1 e2
| Expr.frand e1, Expr.frand e2 => beqExpr e1 e2
| Expr.printString e1, Expr.printString e2 => beqExpr e1 e2
| Expr.printInt e1, Expr.printInt e2 => beqExpr e1 e2
| Expr.externVal t1 p1 pred1, Expr.externVal t2 p2 pred2 =>
t1 == t2 && p1 == p2 && beqSyntaxPredicate pred1 pred2
| Expr.externFn x1 t1 ret1 eff1, Expr.externFn x2 t2 ret2 eff2 =>
x1 == x2 && t1 == t2 && beqExternReturn ret1 ret2 && beqEffectList eff1 eff2
| Expr.externPfn x1 t1 ret1, Expr.externPfn x2 t2 ret2 =>
x1 == x2 && t1 == t2 && beqExternReturn ret1 ret2
| _, _ => false
termination_by structural e1 _ => e1
def beqSyntaxPredicate : SyntaxPredicate → SyntaxPredicate → Bool
| SyntaxPredicate.guardClosure k1 arg1 body1, SyntaxPredicate.guardClosure k2 arg2 body2 =>
k1 == k2 && arg1 == arg2 && beqExpr body1 body2
| SyntaxPredicate.guardOnly k1, SyntaxPredicate.guardOnly k2 => k1 == k2
| SyntaxPredicate.noPred, SyntaxPredicate.noPred => true
| _, _ => false
termination_by structural p1 _ => p1
def beqEffect : Effect → Effect → Bool
| ⟨t1, p1, pred1⟩, ⟨t2, p2, pred2⟩ =>
t1 == t2 && p1 == p2 && beqSyntaxPredicate pred1 pred2
termination_by structural ef1 _ => ef1
def beqEffectList : List Effect → List Effect → Bool
| [], [] => true
| e1 :: es1, e2 :: es2 => beqEffect e1 e2 && beqEffectList es1 es2
| _, _ => false
termination_by structural efs1 _=> efs1
def beqExternReturn : ExternReturn → ExternReturn → Bool
| ExternReturn.simple t1 p1 pred1, ExternReturn.simple t2 p2 pred2 =>
t1 == t2 && p1 == p2 && beqSyntaxPredicate pred1 pred2
| ExternReturn.nestedFn x1 t1 ret1 eff1, ExternReturn.nestedFn x2 t2 ret2 eff2 =>
x1 == x2 && t1 == t2 && beqExternReturn ret1 ret2 && beqEffectList eff1 eff2
| ExternReturn.nestedPfn x1 t1 ret1, ExternReturn.nestedPfn x2 t2 ret2 =>
x1 == x2 && t1 == t2 && beqExternReturn ret1 ret2
| _, _ => false
termination_by structural r1 _ => r1
end
Vadim Zaliva (Nov 06 2025 at 22:01):
That worked! Thanks @Aaron Liu !
Last updated: Dec 20 2025 at 21:32 UTC