/- Copyright (c) 2021 Microsoft Corporation. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Dany Fabian -/ import Lean.Meta.Constructions import Lean.Meta.Transform import Lean.Meta.Tactic import Lean.Meta.Match.Match import Lean.Meta.Reduce namespace Lean.Meta.IndPredBelow open Match register_builtin_option maxBackwardChainingDepth : Nat := { defValue := 10 descr := "The maximum search depth used in the backwards chaining part of the proof of `brecOn` for inductive predicates." } /-- The context used in the creation of the `below` scheme for inductive predicates. -/ structure Context where motives : Array (Name × Expr) typeInfos : Array InductiveVal belowNames : Array Name headers : Array Expr numParams : Nat /-- Collection of variables used to keep track of the positions of binders in the construction of `below` motives and constructors. -/ structure Variables where target : Array Expr indVal : Array Expr params : Array Expr args : Array Expr motives : Array Expr innerType : Expr deriving Inhabited /-- Collection of variables used to keep track of the local context used in the `brecOn` proof. -/ structure BrecOnVariables where params : Array FVarId motives : Array FVarId indices : Array FVarId witness : FVarId indHyps : Array FVarId def mkContext (declName : Name) : MetaM Context := do let indVal ← getConstInfoInduct declName let typeInfos ← indVal.all.toArray.mapM getConstInfoInduct let motiveTypes ← typeInfos.mapM motiveType let motives ← motiveTypes.mapIdxM fun j motive => return (← motiveName motiveTypes j.val, motive) let headers ← typeInfos.mapM $ mkHeader motives indVal.numParams return { motives := motives typeInfos := typeInfos numParams := indVal.numParams headers := headers belowNames := indVal.all.toArray.map (· ++ `below) } where motiveName (motiveTypes : Array Expr) (i : Nat) : MetaM Name := if motiveTypes.size > 1 then mkFreshUserName s!"motive_{i.succ}" else mkFreshUserName "motive" mkHeader (motives : Array (Name × Expr)) (numParams : Nat) (indVal : InductiveVal) : MetaM Expr := do let header ← forallTelescopeReducing indVal.type fun xs t => do withNewBinderInfos (xs.map fun x => (x.fvarId!, BinderInfo.implicit)) $ mkForallFVars xs (← mkArrow (mkAppN (mkIndValConst indVal) xs) t) addMotives motives numParams header addMotives (motives : Array (Name × Expr)) (numParams : Nat) : Expr → MetaM Expr := motives.foldrM (fun (motiveName, motive) t => forallTelescopeReducing t fun xs s => do let motiveType ← instantiateForall motive xs[:numParams] withLocalDecl motiveName BinderInfo.implicit motiveType fun motive => do mkForallFVars (xs.insertAt numParams motive) s) motiveType (indVal : InductiveVal) : MetaM Expr := forallTelescopeReducing indVal.type fun xs _ => do mkForallFVars xs (← mkArrow (mkAppN (mkIndValConst indVal) xs) (mkSort levelZero)) mkIndValConst (indVal : InductiveVal) : Expr := mkConst indVal.name $ indVal.levelParams.map mkLevelParam partial def mkCtorType (ctx : Context) (belowIdx : Nat) (originalCtor : ConstructorVal) : MetaM Expr := forallTelescopeReducing originalCtor.type fun xs t => addHeaderVars { innerType := t indVal := #[] motives := #[] params := xs[:ctx.numParams] args := xs[ctx.numParams:] target := xs[:ctx.numParams] } where addHeaderVars (vars : Variables) := do let headersWithNames ← ctx.headers.mapIdxM fun idx header => return (ctx.belowNames[idx]!, fun _ : Array Expr => pure header) withLocalDeclsD headersWithNames fun xs => addMotives { vars with indVal := xs } addMotives (vars : Variables) := do let motiveBuilders ← ctx.motives.mapM fun (motiveName, motiveType) => return (motiveName, BinderInfo.implicit, fun _ : Array Expr => instantiateForall motiveType vars.params) withLocalDecls motiveBuilders fun xs => modifyBinders { vars with target := vars.target ++ xs, motives := xs } 0 modifyBinders (vars : Variables) (i : Nat) := do if i < vars.args.size then let binder := vars.args[i]! let binderType ← inferType binder if (← checkCount binderType) then mkBelowBinder vars binder binderType fun indValIdx x => mkMotiveBinder vars indValIdx binder binderType fun y => withNewBinderInfos #[(binder.fvarId!, BinderInfo.implicit)] do modifyBinders { vars with target := vars.target ++ #[binder, x, y]} i.succ else modifyBinders { vars with target := vars.target.push binder } i.succ else rebuild vars rebuild (vars : Variables) := vars.innerType.withApp fun _ args => do let hApp := mkAppN (mkConst originalCtor.name $ ctx.typeInfos[0]!.levelParams.map mkLevelParam) (vars.params ++ vars.args) let innerType := mkAppN vars.indVal[belowIdx]! $ vars.params ++ vars.motives ++ args[ctx.numParams:] ++ #[hApp] let x ← mkForallFVars vars.target innerType return replaceTempVars vars x replaceTempVars (vars : Variables) (ctor : Expr) := let levelParams := ctx.typeInfos[0]!.levelParams.map mkLevelParam ctor.replaceFVars vars.indVal $ ctx.belowNames.map fun indVal => mkConst indVal levelParams checkCount (domain : Expr) : MetaM Bool := do let run (x : StateRefT Nat MetaM Expr) : MetaM (Expr × Nat) := StateRefT'.run x 0 let (_, cnt) ← run <| transform domain fun e => do if let some name := e.constName? then if let some _ := ctx.typeInfos.findIdx? fun indVal => indVal.name == name then modify (· + 1) return TransformStep.visit e if cnt > 1 then throwError "only arrows are allowed as premises. Multiple recursive occurrences detected:{indentExpr domain}" return cnt == 1 mkBelowBinder (vars : Variables) (binder : Expr) (domain : Expr) {α : Type} (k : Nat → Expr → MetaM α) : MetaM α := do forallTelescopeReducing domain fun xs t => do let fail _ := do throwError "only trivial inductive applications supported in premises:{indentExpr t}" t.withApp fun f args => do if let some name := f.constName? then if let some idx := ctx.typeInfos.findIdx? fun indVal => indVal.name == name then let hApp := mkAppN binder xs let t := mkAppN vars.indVal[idx]! $ vars.params ++ vars.motives ++ args[ctx.numParams:] ++ #[hApp] let newDomain ← mkForallFVars xs t withLocalDecl (←copyVarName binder.fvarId!) binder.binderInfo newDomain (k idx) else fail () else fail () mkMotiveBinder (vars : Variables) (indValIdx : Nat) (binder : Expr) (domain : Expr) {α : Type} (k : Expr → MetaM α) : MetaM α := do forallTelescopeReducing domain fun xs t => do t.withApp fun _ args => do let hApp := mkAppN binder xs let t := mkAppN vars.motives[indValIdx]! $ args[ctx.numParams:] ++ #[hApp] let newDomain ← mkForallFVars xs t withLocalDecl (←copyVarName binder.fvarId!) binder.binderInfo newDomain k copyVarName (oldName : FVarId) : MetaM Name := do let binderDecl ← oldName.getDecl mkFreshUserName binderDecl.userName def mkConstructor (ctx : Context) (i : Nat) (ctor : Name) : MetaM Constructor := do let ctorInfo ← getConstInfoCtor ctor let name := ctor.updatePrefix ctx.belowNames[i]! let type ← mkCtorType ctx i ctorInfo return { name := name type := type } def mkInductiveType (ctx : Context) (i : Fin ctx.typeInfos.size) (indVal : InductiveVal) : MetaM InductiveType := do return { name := ctx.belowNames[i]! type := ctx.headers[i]! ctors := (← indVal.ctors.mapM (mkConstructor ctx i)) } def mkBelowDecl (ctx : Context) : MetaM Declaration := do let lparams := ctx.typeInfos[0]!.levelParams return Declaration.inductDecl lparams (ctx.numParams + ctx.motives.size) (←ctx.typeInfos.mapIdxM $ mkInductiveType ctx).toList ctx.typeInfos[0]!.isUnsafe partial def backwardsChaining (m : MVarId) (depth : Nat) : MetaM Bool := do if depth = 0 then return false else m.withContext do let lctx ← getLCtx let mTy ← m.getType lctx.anyM fun localDecl => if localDecl.isAuxDecl then return false else commitWhen do let (mvars, _, t) ← forallMetaTelescope localDecl.type if ←isDefEq mTy t then m.assign (mkAppN localDecl.toExpr mvars) mvars.allM fun v => v.mvarId!.isAssigned <||> backwardsChaining v.mvarId! (depth - 1) else return false partial def proveBrecOn (ctx : Context) (indVal : InductiveVal) (type : Expr) : MetaM Expr := do let main ← mkFreshExprSyntheticOpaqueMVar type let (m, vars) ← intros main.mvarId! let [m] ← applyIH m vars | throwError "applying the induction hypothesis should only return one goal" let ms ← induction m vars let ms ← applyCtors ms let maxDepth := maxBackwardChainingDepth.get $ ←getOptions ms.forM (closeGoal maxDepth) instantiateMVars main where intros (m : MVarId) : MetaM (MVarId × BrecOnVariables) := do let (params, m) ← m.introNP indVal.numParams let (motives, m) ← m.introNP ctx.motives.size let (indices, m) ← m.introNP indVal.numIndices let (witness, m) ← m.intro1P let (indHyps, m) ← m.introNP ctx.motives.size return (m, ⟨params, motives, indices, witness, indHyps⟩) applyIH (m : MVarId) (vars : BrecOnVariables) : MetaM (List MVarId) := do match (← vars.indHyps.findSomeM? fun ih => do try pure <| some <| (← m.apply <| mkFVar ih) catch _ => pure none) with | some goals => pure goals | none => throwError "cannot apply induction hypothesis: {MessageData.ofGoal m}" induction (m : MVarId) (vars : BrecOnVariables) : MetaM (List MVarId) := do let params := vars.params.map mkFVar let motives := vars.motives.map mkFVar let levelParams := indVal.levelParams.map mkLevelParam let motives ← ctx.motives.mapIdxM fun idx (_, motive) => do let motive ← instantiateForall motive params forallTelescopeReducing motive fun xs _ => do mkLambdaFVars xs <| mkAppN (mkConst ctx.belowNames[idx]! levelParams) $ (params ++ motives ++ xs) let recursorInfo ← getConstInfo $ mkRecName indVal.name let recLevels := if recursorInfo.numLevelParams > levelParams.length then levelZero::levelParams else levelParams let recursor := mkAppN (mkConst recursorInfo.name $ recLevels) $ params ++ motives m.apply recursor applyCtors (ms : List MVarId) : MetaM $ List MVarId := do let mss ← ms.toArray.mapIdxM fun _ m => do let m ← introNPRec m (← m.getType).withApp fun below args => m.withContext do args.back.withApp fun ctor _ => do let ctorName := ctor.constName!.updatePrefix below.constName! let ctor := mkConst ctorName below.constLevels! let ctorInfo ← getConstInfoCtor ctorName let (mvars, _, _) ← forallMetaTelescope ctorInfo.type let ctor := mkAppN ctor mvars m.apply ctor return mss.foldr List.append [] introNPRec (m : MVarId) : MetaM MVarId := do if (← m.getType).isForall then introNPRec (← m.intro1P).2 else return m closeGoal (maxDepth : Nat) (m : MVarId) : MetaM Unit := do unless (← m.isAssigned) do let m ← introNPRec m unless (← backwardsChaining m maxDepth) do m.withContext do throwError "couldn't solve by backwards chaining ({``maxBackwardChainingDepth} = {maxDepth}): {MessageData.ofGoal m}" def mkBrecOnDecl (ctx : Context) (idx : Nat) : MetaM Declaration := do let type ← mkType let indVal := ctx.typeInfos[idx]! let name := indVal.name ++ brecOnSuffix return Declaration.thmDecl { name := name levelParams := indVal.levelParams type := type value := ←proveBrecOn ctx indVal type } where mkType : MetaM Expr := forallTelescopeReducing ctx.headers[idx]! fun xs _ => do let params := xs[:ctx.numParams] let motives := xs[ctx.numParams:ctx.numParams + ctx.motives.size].toArray let indices := xs[ctx.numParams + ctx.motives.size:] let motiveBinders ← ctx.motives.mapIdxM $ mkIH params motives withLocalDeclsD motiveBinders fun ys => do mkForallFVars (xs ++ ys) (mkAppN motives[idx]! indices) mkIH (params : Array Expr) (motives : Array Expr) (idx : Fin ctx.motives.size) (motive : Name × Expr) : MetaM $ Name × (Array Expr → MetaM Expr) := do let name := if ctx.motives.size > 1 then mkFreshUserName s!"ih_{idx.val.succ}" else mkFreshUserName "ih" let ih ← instantiateForall motive.2 params let mkDomain (_ : Array Expr) : MetaM Expr := forallTelescopeReducing ih fun ys _ => do let levels := ctx.typeInfos[idx]!.levelParams.map mkLevelParam let args := params ++ motives ++ ys let premise := mkAppN (mkConst ctx.belowNames[idx.val]! levels) args let conclusion := mkAppN motives[idx]! ys mkForallFVars ys (←mkArrow premise conclusion) return (←name, mkDomain) /-- Given a constructor name, find the indices of the corresponding `below` version thereof. -/ partial def getBelowIndices (ctorName : Name) : MetaM $ Array Nat := do let ctorInfo ← getConstInfoCtor ctorName let belowCtorInfo ← getConstInfoCtor (ctorName.updatePrefix $ ctorInfo.induct ++ `below) forallTelescopeReducing ctorInfo.type fun xs _ => do loop xs belowCtorInfo.type #[] 0 0 where loop (xs : Array Expr) (rest : Expr) (belowIndices : Array Nat) (xIdx yIdx : Nat) : MetaM $ Array Nat := do if xIdx ≥ xs.size then return belowIndices else let x := xs[xIdx]! let xTy ← inferType x let yTy := rest.bindingDomain! if (← isDefEq xTy yTy) then let rest ← instantiateForall rest #[x] loop xs rest (belowIndices.push yIdx) (xIdx + 1) (yIdx + 1) else forallBoundedTelescope rest (some 1) fun _ rest => loop xs rest belowIndices xIdx (yIdx + 1) private def belowType (motive : Expr) (xs : Array Expr) (idx : Nat) : MetaM $ Name × Expr := do (← inferType xs[idx]!).withApp fun type args => do let indName := type.constName! let indInfo ← getConstInfoInduct indName let belowArgs := args[:indInfo.numParams] ++ #[motive] ++ args[indInfo.numParams:] ++ #[xs[idx]!] let belowType := mkAppN (mkConst (indName ++ `below) type.constLevels!) belowArgs return (indName, belowType) /-- This function adds an additional `below` discriminant to a matcher application. It is used for modifying the patterns, such that the structural recursion can use the new `below` predicate instead of the original one and thus be used prove structural recursion. It takes as parameters: - matcherApp: a matcher application - belowMotive: the motive, that the `below` type should carry - below: an expression from the local context that is the `below` version of a predicate and can be used for structural recursion - idx: the index of the original predicate discriminant. It returns: - A matcher application as an expression - A side-effect for adding the matcher to the environment -/ partial def mkBelowMatcher (matcherApp : MatcherApp) (belowMotive : Expr) (below : Expr) (idx : Nat) : MetaM $ Expr × MetaM Unit := do let mkMatcherInput ← getMkMatcherInputInContext matcherApp let (indName, _, motive, matchType) ← forallBoundedTelescope mkMatcherInput.matchType mkMatcherInput.numDiscrs fun xs t => do let (indName, belowType) ← belowType belowMotive xs idx let matchType ← withLocalDeclD (←mkFreshUserName `h_below) belowType fun h_below => do mkForallFVars (xs.push h_below) t let motive ← newMotive belowType xs pure (indName, belowType.replaceFVars xs matcherApp.discrs, motive, matchType) let lhss ← mkMatcherInput.lhss.mapM <| addBelowPattern indName let alts ← mkMatcherInput.lhss.zip lhss |>.toArray.zip matcherApp.alts |>.mapIdxM fun idx ((oldLhs, lhs), alt) => do withExistingLocalDecls (oldLhs.fvarDecls ++ lhs.fvarDecls) do lambdaTelescope alt fun xs t => do let oldFVars := oldLhs.fvarDecls.toArray let fvars := lhs.fvarDecls.toArray.map (·.toExpr) let xs := -- special case: if we had no free vars, i.e. there was a unit added and no we do have free vars, we get rid of the unit. match oldFVars.size, fvars.size with | 0, _+1 => xs[1:] | _, _ => xs let t := t.replaceFVars xs[:oldFVars.size] fvars[:oldFVars.size] trace[Meta.IndPredBelow.match] "xs = {xs}; oldFVars = {oldFVars.map (·.toExpr)}; fvars = {fvars}; new = {fvars[:oldFVars.size] ++ xs[oldFVars.size:] ++ fvars[oldFVars.size:]}" let newAlt ← mkLambdaFVars (fvars[:oldFVars.size] ++ xs[oldFVars.size:] ++ fvars[oldFVars.size:]) t trace[Meta.IndPredBelow.match] "alt {idx}:\n{alt} ↦ {newAlt}" pure newAlt let matcherName ← mkFreshUserName mkMatcherInput.matcherName withExistingLocalDecls (lhss.foldl (init := []) fun s v => s ++ v.fvarDecls) do for lhs in lhss do trace[Meta.IndPredBelow.match] "{lhs.patterns.map (·.toMessageData)}" let res ← Match.mkMatcher { matcherName, matchType, discrInfos := mkArray (mkMatcherInput.numDiscrs + 1) {}, lhss } res.addMatcher -- if a wrong index is picked, the resulting matcher can be type-incorrect. -- we check here, so that errors can propagate higher up the call stack. check res.matcher let newApp := mkApp res.matcher motive let newApp := mkAppN newApp <| matcherApp.discrs.push below let newApp := mkAppN newApp alts return (newApp, res.addMatcher) where addBelowPattern (indName : Name) (lhs : AltLHS) : MetaM AltLHS := do withExistingLocalDecls lhs.fvarDecls do let patterns := lhs.patterns.toArray let originalPattern := patterns[idx]! let (fVars, belowPattern) ← convertToBelow indName patterns[idx]! withExistingLocalDecls fVars.toList do let patterns := patterns.push belowPattern let patterns := patterns.set! idx (←toInaccessible originalPattern) return { lhs with patterns := patterns.toList, fvarDecls := lhs.fvarDecls ++ fVars.toList } /-- this function changes the type of the pattern from the original type to the `below` version thereof. Most of the cases don't apply. In order to change the type and the pattern to be type correct, we don't simply recursively change all occurrences, but rather, we recursively change occurences of the constructor. As such there are only a few cases: - the pattern is a constructor from the original type. Here we need to: - replace the constructor - copy original recursive patterns and convert them to below and reintroduce them in the correct position - turn original recursive patterns inaccessible - introduce free variables as needed. - it is an `as` pattern. Here the contstructor could be hidden inside of it. - it is a variable. Here you we need to introduce a fresh variable of a different type. -/ convertToBelow (indName : Name) (originalPattern : Pattern) : MetaM $ Array LocalDecl × Pattern := do match originalPattern with | Pattern.ctor ctorName us params fields => let ctorInfo ← getConstInfoCtor ctorName let belowCtor ← getConstInfoCtor $ ctorName.updatePrefix $ ctorInfo.induct ++ `below let belowIndices ← IndPredBelow.getBelowIndices ctorName let belowIndices := belowIndices[ctorInfo.numParams:].toArray.map (· - belowCtor.numParams) -- belowFieldOpts starts off with an array of empty fields. -- We then go over pattern's fields and set the appropriate fields to values. -- In general, there are fewer `fields` than `belowFieldOpts`, because the -- `belowCtor` carries a `below`, a non-`below` and a `motive` version of each -- field that occurs in a recursive application of the inductive predicate. -- `belowIndices` is a mapping from non-`below` to the `below` version of each field. let mut belowFieldOpts := mkArray belowCtor.numFields none let fields := fields.toArray for fieldIdx in [:fields.size] do belowFieldOpts := belowFieldOpts.set! belowIndices[fieldIdx]! (some fields[fieldIdx]!) let belowParams := params.toArray.push belowMotive let belowCtorExpr := mkAppN (mkConst belowCtor.name us) belowParams let (additionalFVars, belowFields) ← transformFields belowCtorExpr indName belowFieldOpts withExistingLocalDecls additionalFVars.toList do let ctor := Pattern.ctor belowCtor.name us belowParams.toList belowFields.toList trace[Meta.IndPredBelow.match] "{originalPattern.toMessageData} ↦ {ctor.toMessageData}" return (additionalFVars, ctor) | Pattern.as varId p hId => let (additionalFVars, p) ← convertToBelow indName p return (additionalFVars, Pattern.as varId p hId) | Pattern.var varId => let var := mkFVar varId let (_, tgtType) ← belowType belowMotive #[var] 0 withLocalDeclD (←mkFreshUserName `h) tgtType fun h => do let localDecl ← getFVarLocalDecl h return (#[localDecl], Pattern.var h.fvarId!) | p => return (#[], p) transformFields belowCtor indName belowFieldOpts := let rec loop (belowCtor : Expr) (belowFieldOpts : Array $ Option Pattern) (belowFields : Array Pattern) (additionalFVars : Array LocalDecl) : MetaM (Array LocalDecl × Array Pattern) := do if belowFields.size ≥ belowFieldOpts.size then pure (additionalFVars, belowFields) else if let some belowField := belowFieldOpts[belowFields.size]! then let belowFieldExpr ← belowField.toExpr let belowCtor := mkApp belowCtor belowFieldExpr let patTy ← inferType belowFieldExpr patTy.withApp fun f _ => do let constName := f.constName? if constName == indName then let (fvars, transformedField) ← convertToBelow indName belowField withExistingLocalDecls fvars.toList do let belowFieldOpts := belowFieldOpts.set! (belowFields.size + 1) transformedField let belowField := match belowField with | Pattern.ctor .. => Pattern.inaccessible belowFieldExpr | _ => belowField loop belowCtor belowFieldOpts (belowFields.push belowField) (additionalFVars ++ fvars) else loop belowCtor belowFieldOpts (belowFields.push belowField) additionalFVars else let ctorType ← inferType belowCtor withLocalDeclD (←mkFreshUserName `a) ctorType.bindingDomain! fun a => do let localDecl ← getFVarLocalDecl a loop (mkApp belowCtor a) belowFieldOpts (belowFields.push $ Pattern.var a.fvarId!) (additionalFVars.push localDecl) loop belowCtor belowFieldOpts #[] #[] toInaccessible : Pattern → MetaM Pattern | Pattern.inaccessible p => return Pattern.inaccessible p | Pattern.var v => return Pattern.var v | p => return Pattern.inaccessible $ ←p.toExpr newMotive (belowType : Expr) (ys : Array Expr) : MetaM Expr := lambdaTelescope matcherApp.motive fun xs t => do let numDiscrs := matcherApp.discrs.size withLocalDeclD (←mkFreshUserName `h_below) (belowType.replaceFVars ys xs) fun h_below => do let motive ← mkLambdaFVars (xs[:numDiscrs] ++ #[h_below] ++ xs[numDiscrs:]) t trace[Meta.IndPredBelow.match] "motive := {motive}" return motive def findBelowIdx (xs : Array Expr) (motive : Expr) : MetaM $ Option (Expr × Nat) := do xs.findSomeM? fun x => do let xTy ← inferType x xTy.withApp fun f _ => match f.constName?, xs.indexOf? x with | some name, some idx => do if (← isInductivePredicate name) then let (_, belowTy) ← belowType motive xs idx let below ← mkFreshExprSyntheticOpaqueMVar belowTy try trace[Meta.IndPredBelow.match] "{←Meta.ppGoal below.mvarId!}" if (← backwardsChaining below.mvarId! 10) then trace[Meta.IndPredBelow.match] "Found below term in the local context: {below}" if (← xs.anyM (isDefEq below)) then pure none else pure (below, idx.val) else trace[Meta.IndPredBelow.match] "could not find below term in the local context" pure none catch _ => pure none else pure none | _, _ => pure none def mkBelow (declName : Name) : MetaM Unit := do if (← isInductivePredicate declName) then let x ← getConstInfoInduct declName if x.isRec then let ctx ← IndPredBelow.mkContext declName let decl ← IndPredBelow.mkBelowDecl ctx addDecl decl trace[Meta.IndPredBelow] "added {ctx.belowNames}" ctx.belowNames.forM Lean.mkCasesOn for i in [:ctx.typeInfos.size] do try let decl ← IndPredBelow.mkBrecOnDecl ctx i addDecl decl catch e => trace[Meta.IndPredBelow] "failed to prove brecOn for {ctx.belowNames[i]!}\n{e.toMessageData}" else trace[Meta.IndPredBelow] "Not recursive" else trace[Meta.IndPredBelow] "Not inductive predicate" builtin_initialize registerTraceClass `Meta.IndPredBelow registerTraceClass `Meta.IndPredBelow.match end Lean.Meta.IndPredBelow