-
Notifications
You must be signed in to change notification settings - Fork 429
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test: add
split_ifs
mwe from Mathlib
- Loading branch information
1 parent
9cf4541
commit 37248e8
Showing
1 changed file
with
249 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |