import Lean.Elab.Tactic.Location import Lean.Meta.Tactic.SplitIf import Lean.Elab.Tactic.Simp section Mathlib.Tactic.Core open Lean Elab Tactic namespace Lean.Elab.Tactic #check Parser.Tactic.allGoals def allGoals (tac : TacticM Unit) : TacticM Unit := do let mvarIds ← getGoals let mut mvarIdsNew := #[] for mvarId in mvarIds do unless (← mvarId.isAssigned) do setGoals [mvarId] try tac mvarIdsNew := mvarIdsNew ++ (← getUnsolvedGoals) catch ex => if (← read).recover then logException ex mvarIdsNew := mvarIdsNew.push mvarId else throw ex setGoals mvarIdsNew.toList /-- Simulates the `<;>` tactic combinator. First runs `tac1` and then runs `tac2` on all newly-generated subgoals. -/ def andThenOnSubgoals (tac1 : TacticM Unit) (tac2 : TacticM Unit) : TacticM Unit := focus do tac1; allGoals tac2 end Lean.Elab.Tactic end Mathlib.Tactic.Core section Mathlib.Tactic.SplitIfs namespace Mathlib.Tactic open Lean Elab.Tactic Parser.Tactic Lean.Meta /-- A position where a split may apply. -/ private inductive SplitPosition | target | hyp (fvarId: FVarId) /-- Collects a list of positions pointed to by `loc` and their types. -/ private def getSplitCandidates (loc : Location) : TacticM (List (SplitPosition × Expr)) := match loc with | Location.wildcard => do let candidates ← (← getLCtx).getFVarIds.mapM (fun fvarId ↦ do let typ ← instantiateMVars (← inferType (mkFVar fvarId)) return (SplitPosition.hyp fvarId, typ)) pure ((SplitPosition.target, ← getMainTarget) :: candidates.toList) | Location.targets hyps tgt => do let candidates ← (← hyps.mapM getFVarId).mapM (fun fvarId ↦ do let typ ← instantiateMVars (← inferType (mkFVar fvarId)) return (SplitPosition.hyp fvarId, typ)) if tgt then return (SplitPosition.target, ← getMainTarget) :: candidates.toList else return candidates.toList /-- Return the condition and decidable instance of an `if` expression to case split. -/ private partial def findIfToSplit? (e : Expr) : Option (Expr × Expr) := match e.find? fun e => (e.isIte || e.isDIte) && !(e.getArg! 1 5).hasLooseBVars with | some iteApp => let cond := iteApp.getArg! 1 5 let dec := iteApp.getArg! 2 5 -- Try to find a nested `if` in `cond` findIfToSplit? cond |>.getD (cond, dec) | none => none /-- Finds an if condition to split. If successful, returns the position and the condition. -/ private def findIfCondAt (loc : Location) : TacticM (Option (SplitPosition × Expr)) := do for (pos, e) in (← getSplitCandidates loc) do if let some (cond, _) := findIfToSplit? e then return some (pos, cond) return none /-- `Simp.Discharge` strategy to use in `reduceIfsAt`. Delegates to `SplitIf.discharge?`, and additionally supports discharging `True`, to better match the behavior of mathlib3's `split_ifs`. -/ private def discharge? (e : Expr) : SimpM (Option Expr) := do let e ← instantiateMVars e if let some e1 ← (← SplitIf.mkDischarge? false) e then return some e1 if e.isConstOf `True then return some (mkConst `True.intro) return none /-- Simplifies if-then-else expressions after cases have been split out. -/ private def reduceIfsAt (loc : Location) : TacticM Unit := do let ctx ← SplitIf.getSimpContext let ctx ← ctx.setConfig { ctx.config with failIfUnchanged := false } let _ ← simpLocation ctx (← ({} : Simp.SimprocsArray).add `reduceCtorEq false) discharge? loc pure () /-- Splits a single if-then-else expression and then reduces the resulting goals. Has a similar effect as `SplitIf.splitIfTarget?` or `SplitIf.splitIfLocalDecl?` from core Lean 4. We opt not to use those library functions so that we can better mimic the behavior of mathlib3's `split_ifs`. -/ private def splitIf1 (cond : Expr) (hName : Name) (loc : Location) : TacticM Unit := do let splitCases := evalTactic (← `(tactic| by_cases $(mkIdent hName) : $(← Elab.Term.exprToSyntax cond))) andThenOnSubgoals splitCases (reduceIfsAt loc) /-- Pops off the front of the list of names, or generates a fresh name if the list is empty. -/ private def getNextName (hNames: IO.Ref (List (TSyntax `Lean.binderIdent))) : MetaM Name := do match ← hNames.get with | [] => mkFreshUserName `h | n::ns => do hNames.set ns if let `(binderIdent| $x:ident) := n then pure x.getId else pure `_ /-- Returns `true` if the condition or its negation already appears as a hypothesis. -/ private def valueKnown (cond : Expr) : TacticM Bool := do let not_cond := mkApp (mkConst `Not) cond for h in ← getLocalHyps do let ty ← instantiateMVars (← inferType h) if cond == ty then return true if not_cond == ty then return true return false /-- Main loop of split_ifs. Pulls names for new hypotheses from `hNames`. Stops if it encounters a condition in the passed-in `List Expr`. -/ private partial def splitIfsCore (loc : Location) (hNames : IO.Ref (List (TSyntax `Lean.binderIdent))) : List Expr → TacticM Unit := fun done ↦ withMainContext do let some (_,cond) ← findIfCondAt loc | Meta.throwTacticEx `split_ifs (← getMainGoal) "no if-then-else conditions to split" -- If `cond` is `¬p` then use `p` instead. let cond := if cond.isAppOf `Not then cond.getAppArgs[0]! else cond if done.contains cond then return () let no_split ← valueKnown cond if no_split then andThenOnSubgoals (reduceIfsAt loc) (splitIfsCore loc hNames (cond::done) <|> pure ()) else do let hName ← getNextName hNames andThenOnSubgoals (splitIf1 cond hName loc) ((splitIfsCore loc hNames (cond::done)) <|> pure ()) /-- Splits all if-then-else-expressions into multiple goals. Given a goal of the form `g (if p then x else y)`, `split_ifs` will produce two goals: `p ⊢ g x` and `¬p ⊢ g y`. If there are multiple ite-expressions, then `split_ifs` will split them all, starting with a top-most one whose condition does not contain another ite-expression. `split_ifs at *` splits all ite-expressions in all hypotheses as well as the goal. `split_ifs with h₁ h₂ h₃` overrides the default names for the hypotheses. -/ syntax (name := splitIfs) "split_ifs" (location)? (" with" (ppSpace colGt binderIdent)+)? : tactic elab_rules : tactic | `(tactic| split_ifs $[$loc:location]? $[with $withArg*]?) => let loc := match loc with | none => Location.targets #[] true | some loc => expandLocation loc let names := match withArg with | none => [] | some args => args.toList withMainContext do let names ← IO.mkRef names splitIfsCore loc names [] for name in ← names.get do logWarningAt name m!"unused name: {name}" end Mathlib.Tactic end Mathlib.Tactic.SplitIfs section Mathlib.Order.Defs class Preorder (α : Type _) extends LE α, LT α where class PartialOrder (α : Type _) extends Preorder α where class LinearOrder (α : Type _) extends PartialOrder α, Min α, Max α, Ord α where /-- In a linearly ordered type, we assume the order relations are all decidable. -/ decidableLE : DecidableRel (· ≤ · : α → α → Prop) /-- In a linearly ordered type, we assume the order relations are all decidable. -/ decidableEq : DecidableEq α /-- In a linearly ordered type, we assume the order relations are all decidable. -/ decidableLT : DecidableRel (· < · : α → α → Prop) min := fun a b => if a ≤ b then a else b max := fun a b => if a ≤ b then b else a variable [LinearOrder α] {a b c : α} instance (priority := 900) (a b : α) : Decidable (a < b) := LinearOrder.decidableLT a b instance (priority := 900) (a b : α) : Decidable (a ≤ b) := LinearOrder.decidableLE a b instance (priority := 900) (a b : α) : Decidable (a = b) := LinearOrder.decidableEq a b end Mathlib.Order.Defs section Mathlib.Order.Lattice universe u v w variable {α : Type u} class SemilatticeSup (α : Type u) extends PartialOrder α where sup : α → α → α class Lattice (α : Type u) extends SemilatticeSup α end Mathlib.Order.Lattice open Lean Meta Elab Tactic in /-- `guard_goal_nums n` succeeds if there are exactly `n` goals and fails otherwise. -/ elab (name := guardGoalNums) "guard_goal_nums " n:num : tactic => do let numGoals := (← getGoals).length guard (numGoals = n.getNat) <|> throwError "expected {n.getNat} goals but found {numGoals}" example (a m tl : α) (f : α → β) [LinearOrder β] : (if f (if f a < f m then m else a) < f tl then some tl else some (if f a < f m then m else a)) = if f a < f (if f m < f tl then tl else m) then some (if f m < f tl then tl else m) else some a := by split_ifs guard_goal_nums 7 all_goals sorry instance LinearOrder.toLattice {α : Type u} [o : LinearOrder α] : Lattice α := let __spread := o { __spread with sup := max } example (a m tl : α) (f : α → β) [LinearOrder β] : (if f (if f a < f m then m else a) < f tl then some tl else some (if f a < f m then m else a)) = if f a < f (if f m < f tl then tl else m) then some (if f m < f tl then tl else m) else some a := by split_ifs guard_goal_nums 7 all_goals sorry