Built with
doc-gen4, running Lean4.
Bubbles (
) indicate interactive fragments: hover for details, tap to reveal contents.
Use
Ctrl+โCtrl+โto navigate,
Ctrl+๐ฑ๏ธto focus.
On Mac, use
Cmdinstead of
Ctrl.
/-
Copyright (c) 2022 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro
-/
import Lean
/-!
# `casesm`, `cases_type`, `constructorm` tactics
These tactics implement repeated `cases` / `constructor` on anything satisfying a predicate.
-/
namespace Lean.MVarId
/--
Core tactic for `casesm` and `cases_type`. Calls `cases` on all fvars in `g` for which
`matcher ldecl.type` returns true.
* `recursive`: if true, it calls itself repeatedly on the resulting subgoals
* `allowSplit`: if false, it will skip any hypotheses where `cases` returns more than one subgoal.
* `throwOnNoMatch`: if true, then throws an error if no match is found
-/
partial def casesMatching (matcher : Expr โ MetaM Bool) (recursive := false) (allowSplit := true)
(throwOnNoMatch := !recursive) (g : MVarId) : MetaM (List MVarId) := do
let result := (โ go g).toList
if throwOnNoMatch && result == [g] then
throwError "no match": ?m.2443 ?m.2444
throwErrorthrowError "no match": ?m.2443 ?m.2444
"no match"
else
return result
where
/-- Auxiliary for `casesMatching`. Accumulates generated subgoals in `acc`. -/
go (g : MVarId) (acc : Array MVarId := #[]) : MetaM (Array MVarId) :=
g.withContext do
for ldecl in โ getLCtx do
if ldecl.isImplementationDetail then continue
if โ matcher ldecl.type then
let mut acc := acc
let subgoals โ if allowSplit then
g.cases ldecl.fvarId
else
let s โ saveState
let subgoals โ g.cases ldecl.fvarId
if subgoals.size > 1 then
s.restore
continue: ?m.1067 ?m.1069
continue
else
pure: {f : Type ?u.1149 โ Type ?u.1148} โ [self : Pure f] โ {ฮฑ : Type ?u.1149} โ ฮฑ โ f ฮฑ
pure subgoals
for subgoal in subgoals do
if recursive then
acc โ go subgoal.mvarId acc
else
acc := acc.push subgoal.mvarId
return acc
return (acc.push g)
def casesType (heads : Array Name) (recursive := false) (allowSplit := true) :
MVarId โ MetaM (List MVarId) :=
let matcher: (ty : ?m.7890) โ ?m.7950 ty (?m.7949 ty)
matcher ty := pure: {f : Type ?u.7895 โ Type ?u.7894} โ [self : Pure f] โ {ฮฑ : Type ?u.7895} โ ฮฑ โ f ฮฑ
pure <|
if let .const n .. := ty.headBeta.getAppFn then heads.contains n else false
casesMatching matcher: (ty : ?m.7890) โ ?m.7950 ty (?m.7949 ty)
matcher recursive allowSplit
end Lean.MVarId
namespace Mathlib.Tactic
open Lean Meta Elab Tactic MVarId
/-- Elaborate a list of terms with holes into a list of patterns. -/
def elabPatterns (pats : Array Term) : TermElabM (Array AbstractMVarsResult: Type
AbstractMVarsResult) :=
withTheReader: (ฯ : Type ?u.8821) โ
{m : Type ?u.8821 โ Type ?u.8820} โ [inst : MonadWithReaderOf ฯ m] โ {ฮฑ : Type ?u.8821} โ (ฯ โ ฯ) โ m ฮฑ โ m ฮฑ
withTheReader Term.Context (fun ctx โฆ { ctx with ignoreTCFailures := true }) <|
Term.withoutErrToSorry <|
pats.mapM: {ฮฑ : Type ?u.8862} โ
{ฮฒ : Type ?u.8861} โ {m : Type ?u.8861 โ Type ?u.8860} โ [inst : Monad m] โ (ฮฑ โ m ฮฒ) โ Array ฮฑ โ m (Array ฮฒ)
mapM fun p โฆ Term.withoutModifyingElabMetaStateWithInfo do
withRef p <| abstractMVars (โ Term.elabTerm p none): ?m.8980
(โ Term.elabTerm(โ Term.elabTerm p none): ?m.8980
p(โ Term.elabTerm p none): ?m.8980
none(โ Term.elabTerm p none): ?m.8980
)
/-- Returns true if any of the patterns match the expression. -/
def matchPatterns (pats : Array AbstractMVarsResult: Type
AbstractMVarsResult) (e : Expr) : MetaM Bool := do
let e โ instantiateMVars e
pats.anyM fun p โฆ return (โ Conv.matchPattern? p e): ?m.12918
(โ Conv.matchPattern?(โ Conv.matchPattern? p e): ?m.12918
p(โ Conv.matchPattern? p e): ?m.12918
e(โ Conv.matchPattern? p e): ?m.12918
) matches some (_, #[])
/--
* `casesm p` applies the `cases` tactic to a hypothesis `h : type`
if `type` matches the pattern `p`.
* `casesm p_1, ..., p_n` applies the `cases` tactic to a hypothesis `h : type`
if `type` matches one of the given patterns.
* `casesm* p` is a more efficient and compact version of `ยท repeat casesm p`.
It is more efficient because the pattern is compiled once.
Example: The following tactic destructs all conjunctions and disjunctions in the current context.
```
casesm* _ โจ _, _ โง _
```
-/
elab (name := casesM) "casesm" recursive:"*"? ppSpace pats:term,+ : tactic => do
let pats โ elabPatterns pats.getElems
liftMetaTactic (casesMatching (matchPatterns pats) recursive.isSome)
/-- Common implementation of `cases_type` and `cases_type!`. -/
def elabCasesType (heads : Array Ident)
(recursive := false) (allowSplit := true) : TacticM Unit := do
let heads โ heads.mapM: {ฮฑ : Type ?u.16023} โ
{ฮฒ : Type ?u.16022} โ {m : Type ?u.16022 โ Type ?u.16021} โ [inst : Monad m] โ (ฮฑ โ m ฮฒ) โ Array ฮฑ โ m (Array ฮฒ)
mapM resolveGlobalConstNoOverloadWithInfo
liftMetaTactic (casesType heads recursive allowSplit)
/--
* `cases_type I` applies the `cases` tactic to a hypothesis `h : (I ...)`
* `cases_type I_1 ... I_n` applies the `cases` tactic to a hypothesis
`h : (I_1 ...)` or ... or `h : (I_n ...)`
* `cases_type* I` is shorthand for `ยท repeat cases_type I`
* `cases_type! I` only applies `cases` if the number of resulting subgoals is <= 1.
Example: The following tactic destructs all conjunctions and disjunctions in the current goal.
```
cases_type* Or And
```
-/
elab (name := casesType) "cases_type" recursive:"*"? ppSpace heads:(colGt ident)+ : tactic =>
elabCasesType heads recursive.isSome true
@[inherit_doc casesType]
elab (name := casesType!) "cases_type!" recursive:"*"? ppSpace heads:(colGt ident)+ : tactic =>
elabCasesType heads recursive.isSome false
/--
Core tactic for `constructorm`. Calls `constructor` on all subgoals for which
`matcher ldecl.type` returns true.
* `recursive`: if true, it calls itself repeatedly on the resulting subgoals
* `throwOnNoMatch`: if true, throws an error if no match is found
-/
partial def constructorMatching (g : MVarId) (matcher : Expr โ MetaM Bool)
(recursive := false) (throwOnNoMatch := !recursive): MetaM (List MVarId) := do
let result โ
(if recursive then (do
let result โ go g
pure: {f : Type ?u.20758 โ Type ?u.20757} โ [self : Pure f] โ {ฮฑ : Type ?u.20758} โ ฮฑ โ f ฮฑ
pure result.toList)
else
(g.withContext do
if โ matcher (โ g.getType): ?m.20897
(โ g(โ g.getType): ?m.20897
.getType(โ g.getType): ?m.20897
) then g.constructor else pure: {f : Type ?u.21024 โ Type ?u.21023} โ [self : Pure f] โ {ฮฑ : Type ?u.21024} โ ฮฑ โ f ฮฑ
pure [g]))
if throwOnNoMatch && [g] == result then
throwError "no match": ?m.21167 ?m.21168
throwErrorthrowError "no match": ?m.21167 ?m.21168
"no match"
else
return result
where
/-- Auxiliary for `constructorMatching`. Accumulates generated subgoals in `acc`. -/
go (g : MVarId) (acc : Array MVarId := #[]) : MetaM (Array MVarId) :=
g.withContext do
if โ matcher (โ g.getType): ?m.19796
(โ g(โ g.getType): ?m.19796
.getType(โ g.getType): ?m.19796
) then
let mut acc := acc
for g' in โ g.constructor do
acc โ go g' acc
return acc
return (acc.push g)
/--
* `constructorm p_1, ..., p_n` applies the `constructor` tactic to the main goal
if `type` matches one of the given patterns.
* `constructorm* p` is a more efficient and compact version of `ยท repeat constructorm p`.
It is more efficient because the pattern is compiled once.
Example: The following tactic proves any theorem like `True โง (True โจ True)` consisting of
and/or/true:
```
constructorm* _ โจ _, _ โง _, True
```
-/
elab (name := constructorM) "constructorm" recursive:"*"? ppSpace pats:term,+ : tactic => do
let pats โ elabPatterns pats.getElems
liftMetaTactic (constructorMatching ยท (matchPatterns pats) recursive.isSome)