Skip to content

Commit

Permalink
test: add split_ifs mwe from Mathlib
Browse files Browse the repository at this point in the history
  • Loading branch information
leodemoura committed Nov 22, 2024
1 parent 9cf4541 commit 37248e8
Showing 1 changed file with 249 additions and 0 deletions.
249 changes: 249 additions & 0 deletions tests/lean/run/splitIfIssue.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
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

0 comments on commit 37248e8

Please sign in to comment.