Compare commits

...

9 Commits

Author SHA1 Message Date
Leonardo de Moura
f6b968a1e7 test: grind? 2025-01-27 19:54:23 -08:00
Leonardo de Moura
aa0bb63efb fix: equation names to function name 2025-01-27 19:51:33 -08:00
Leonardo de Moura
2b1e1cce58 fix: do not includ match equations 2025-01-27 19:47:28 -08:00
Leonardo de Moura
acc1e4a11c fix: missing getAppFn 2025-01-27 19:39:00 -08:00
Leonardo de Moura
8a4b94b1f9 chore: add pretty printing hints 2025-01-27 19:30:51 -08:00
Leonardo de Moura
8b0dd806a6 feat: add mkGrindOnly 2025-01-27 19:21:46 -08:00
Leonardo de Moura
8271af7706 chore: split cases trace into two sets 2025-01-27 18:34:31 -08:00
Leonardo de Moura
e9b24e1647 chore: document trace 2025-01-27 18:29:17 -08:00
Leonardo de Moura
3f51b1bee0 feat: save grind trace 2025-01-27 18:25:00 -08:00
12 changed files with 225 additions and 46 deletions

View File

@@ -8,15 +8,15 @@ import Init.Tactics
namespace Lean.Parser.Attr
syntax grindEq := "="
syntax grindEqBoth := atomic("_" "=" "_")
syntax grindEqRhs := atomic("=" "_")
syntax grindEqBwd := atomic("" "=")
syntax grindBwd := ""
syntax grindFwd := ""
syntax grindUsr := &"usr"
syntax grindCases := &"cases"
syntax grindCasesEager := atomic(&"cases" &"eager")
syntax grindEq := "= "
syntax grindEqBoth := atomic("_" "=" "_ ")
syntax grindEqRhs := atomic("=" "_ ")
syntax grindEqBwd := atomic("" "= ")
syntax grindBwd := " "
syntax grindFwd := " "
syntax grindUsr := &"usr "
syntax grindCases := &"cases "
syntax grindCasesEager := atomic(&"cases" &"eager ")
syntax grindMod := grindEqBoth <|> grindEqRhs <|> grindEq <|> grindEqBwd <|> grindBwd <|> grindFwd <|> grindUsr <|> grindCasesEager <|> grindCases
@@ -30,6 +30,8 @@ The configuration for `grind`.
Passed to `grind` using, for example, the `grind (config := { matchEqs := true })` syntax.
-/
structure Config where
/-- If `trace` is `true`, `grind` records used E-matching theorems and case-splits. -/
trace : Bool := false
/-- Maximum number of case-splits in a proof search branch. It does not include splits performed during normalization. -/
splits : Nat := 8
/-- Maximum number of E-matching (aka heuristic theorem instantiation) rounds before each case split. -/

View File

@@ -6,6 +6,7 @@ Authors: Leonardo de Moura
prelude
import Init.Grind.Tactics
import Lean.Meta.Tactic.Grind
import Lean.Meta.Tactic.TryThis
import Lean.Elab.Command
import Lean.Elab.Tactic.Basic
import Lean.Elab.Tactic.Config
@@ -82,7 +83,7 @@ def elabGrindParams (params : Grind.Params) (ps : TSyntaxArray ``Parser.Tactic.
| _ => throwError "unexpected `grind` parameter{indentD p}"
return params
where
addEMatchTheorem (params : Grind.Params) (declName : Name) (kind : Grind.TheoremKind) : MetaM Grind.Params := do
addEMatchTheorem (params : Grind.Params) (declName : Name) (kind : Grind.EMatchTheoremKind) : MetaM Grind.Params := do
let info getConstInfo declName
match info with
| .thmInfo _ =>
@@ -113,11 +114,12 @@ def grind
(mvarId : MVarId) (config : Grind.Config)
(only : Bool)
(ps : TSyntaxArray ``Parser.Tactic.grindParam)
(mainDeclName : Name) (fallback : Grind.Fallback) : MetaM Unit := do
(mainDeclName : Name) (fallback : Grind.Fallback) : MetaM Grind.Trace := do
let params mkGrindParams config only ps
let result Grind.main mvarId params mainDeclName fallback
if result.hasFailures then
throwError "`grind` failed\n{← result.toMessageData}"
return result.trace
private def elabFallback (fallback? : Option Term) : TermElabM (Grind.GoalM Unit) := do
let some fallback := fallback? | return (pure ())
@@ -142,26 +144,80 @@ private def evalGrindCore
(only : Option Syntax)
(params : Option (Syntax.TSepArray `Lean.Parser.Tactic.grindParam ","))
(fallback? : Option Term)
(_trace : Bool) -- TODO
: TacticM Unit := do
(trace : Bool)
: TacticM Grind.Trace := do
let fallback elabFallback fallback?
let only := only.isSome
let params := if let some params := params then params.getElems else #[]
logWarningAt ref "The `grind` tactic is experimental and still under development. Avoid using it in production projects"
let declName := ( Term.getDeclName?).getD `_grind
let config elabGrindConfig config
withMainContext do liftMetaFinishingTactic (grind · config only params declName fallback)
let mut config elabGrindConfig config
if trace then
config := { config with trace }
withMainContext do
let result grind ( getMainGoal) config only params declName fallback
replaceMainGoal []
return result
private def mkGrindOnly
(config : TSyntax `Lean.Parser.Tactic.optConfig)
(fallback? : Option Term)
(trace : Grind.Trace)
: MetaM (TSyntax `tactic) := do
let mut params := #[]
let mut foundFns : NameSet := {}
for { origin, kind } in trace.thms.toList do
if let .decl declName := origin then
unless Match.isMatchEqnTheorem ( getEnv) declName do
if let some declName isEqnThm? declName then
unless foundFns.contains declName do
foundFns := foundFns.insert declName
let decl : Ident := mkIdent ( unresolveNameGlobalAvoidingLocals declName)
let param `(Parser.Tactic.grindParam| $decl:ident)
params := params.push param
else
let decl : Ident := mkIdent ( unresolveNameGlobalAvoidingLocals declName)
let param match kind with
| .eqLhs => `(Parser.Tactic.grindParam| = $decl)
| .eqRhs => `(Parser.Tactic.grindParam| =_ $decl)
| .eqBoth => `(Parser.Tactic.grindParam| _=_ $decl)
| .eqBwd => `(Parser.Tactic.grindParam| = $decl)
| .bwd => `(Parser.Tactic.grindParam| $decl)
| .fwd => `(Parser.Tactic.grindParam| $decl)
| .user => `(Parser.Tactic.grindParam| usr $decl)
| .default => `(Parser.Tactic.grindParam| $decl:ident)
params := params.push param
for declName in trace.eagerCases.toList do
let decl : Ident := mkIdent ( unresolveNameGlobalAvoidingLocals declName)
let param `(Parser.Tactic.grindParam| cases eager $decl)
params := params.push param
for declName in trace.cases.toList do
unless trace.eagerCases.contains declName do
let decl : Ident := mkIdent ( unresolveNameGlobalAvoidingLocals declName)
let param `(Parser.Tactic.grindParam| cases $decl)
params := params.push param
let result if let some fallback := fallback? then
`(tactic| grind $config:optConfig only on_failure $fallback)
else
`(tactic| grind $config:optConfig only)
if params.isEmpty then
return result
else
let paramsStx := #[mkAtom "[", (mkAtom ",").mkSep params, mkAtom "]"]
return result.raw.setArg 3 (mkNullNode paramsStx)
@[builtin_tactic Lean.Parser.Tactic.grind] def evalGrind : Tactic := fun stx => do
match stx with
| `(tactic| grind $config:optConfig $[only%$only]? $[ [$params:grindParam,*] ]? $[on_failure $fallback?]?) =>
evalGrindCore stx config only params fallback? false
discard <| evalGrindCore stx config only params fallback? false
| _ => throwUnsupportedSyntax
@[builtin_tactic Lean.Parser.Tactic.grindTrace] def evalGrindTrace : Tactic := fun stx => do
match stx with
| `(tactic| grind? $config:optConfig $[only%$only]? $[ [$params:grindParam,*] ]? $[on_failure $fallback?]?) =>
evalGrindCore stx config only params fallback? true
| `(tactic| grind?%$tk $config:optConfig $[only%$only]? $[ [$params:grindParam,*] ]? $[on_failure $fallback?]?) =>
let trace evalGrindCore stx config only params fallback? true
let stx mkGrindOnly config fallback? trace
Tactic.TryThis.addSuggestion tk stx (origSpan? := getRef)
| _ => throwUnsupportedSyntax
end Lean.Elab.Tactic

View File

@@ -33,6 +33,7 @@ namespace Lean
/-! Trace options for `grind` users -/
builtin_initialize registerTraceClass `grind
builtin_initialize registerTraceClass `grind.trace
builtin_initialize registerTraceClass `grind.assert
builtin_initialize registerTraceClass `grind.eqc
builtin_initialize registerTraceClass `grind.internalize

View File

@@ -10,7 +10,7 @@ import Lean.Meta.Tactic.Grind.Cases
namespace Lean.Meta.Grind
inductive AttrKind where
| ematch (k : TheoremKind)
| ematch (k : EMatchTheoremKind)
| cases (eager : Bool)
| infer

View File

@@ -242,17 +242,17 @@ private def annotateMatchEqnType (prop : Expr) (initApp : Expr) : M Expr := do
Stores new theorem instance in the state.
Recall that new instances are internalized later, after a full round of ematching.
-/
private def addNewInstance (origin : Origin) (proof : Expr) (generation : Nat) : M Unit := do
private def addNewInstance (thm : EMatchTheorem) (proof : Expr) (generation : Nat) : M Unit := do
let proof instantiateMVars proof
if grind.debug.proofs.get ( getOptions) then
check proof
let mut prop inferType proof
if Match.isMatchEqnTheorem ( getEnv) origin.key then
if Match.isMatchEqnTheorem ( getEnv) thm.origin.key then
prop annotateMatchEqnType prop ( read).initApp
else if ( isEqnThm origin.key) then
else if ( isEqnThm thm.origin.key) then
prop annotateEqnTypeConds prop
trace_goal[grind.ematch.instance] "{← origin.pp}: {prop}"
addTheoremInstance proof prop (generation+1)
trace_goal[grind.ematch.instance] "{← thm.origin.pp}: {prop}"
addTheoremInstance thm proof prop (generation+1)
/--
After processing a (multi-)pattern, use the choice assignment to instantiate the proof.
@@ -301,13 +301,13 @@ private partial def instantiateTheorem (c : Choice) : M Unit := withDefault do w
return ()
let proof := mkAppN proof mvars
if ( mvars.allM (·.mvarId!.isAssigned)) then
addNewInstance thm.origin proof c.gen
addNewInstance thm proof c.gen
else
let mvars mvars.filterM fun mvar => return !( mvar.mvarId!.isAssigned)
if let some mvarBad mvars.findM? fun mvar => return !( isProof mvar) then
reportIssue m!"failed to instantiate {← thm.origin.pp}, failed to instantiate non propositional argument with type{indentExpr (← inferType mvarBad)}"
let proof mkLambdaFVars (binderInfoForMVars := .default) mvars ( instantiateMVars proof)
addNewInstance thm.origin proof c.gen
addNewInstance thm proof c.gen
/-- Process choice stack until we don't have more choices to be processed. -/
private def processChoices : M Unit := do

View File

@@ -92,11 +92,11 @@ instance : BEq Origin where
instance : Hashable Origin where
hash a := hash a.key
inductive TheoremKind where
inductive EMatchTheoremKind where
| eqLhs | eqRhs | eqBoth | eqBwd | fwd | bwd | default | user /- pattern specified using `grind_pattern` command -/
deriving Inhabited, BEq, Repr
deriving Inhabited, BEq, Repr, Hashable
private def TheoremKind.toAttribute : TheoremKind String
private def EMatchTheoremKind.toAttribute : EMatchTheoremKind String
| .eqLhs => "[grind =]"
| .eqRhs => "[grind =_]"
| .eqBoth => "[grind _=_]"
@@ -106,7 +106,7 @@ private def TheoremKind.toAttribute : TheoremKind → String
| .default => "[grind]"
| .user => "[grind]"
private def TheoremKind.explainFailure : TheoremKind String
private def EMatchTheoremKind.explainFailure : EMatchTheoremKind String
| .eqLhs => "failed to find pattern in the left-hand side of the theorem's conclusion"
| .eqRhs => "failed to find pattern in the right-hand side of the theorem's conclusion"
| .eqBoth => unreachable! -- eqBoth is a macro
@@ -131,7 +131,7 @@ structure EMatchTheorem where
symbols : List HeadIndex
origin : Origin
/-- The `kind` is used for generating the `patterns`. We save it here to implement `grind?`. -/
kind : TheoremKind
kind : EMatchTheoremKind
deriving Inhabited
/-- Set of E-matching theorems. -/
@@ -534,7 +534,7 @@ private def ppParamsAt (proof : Expr) (numParams : Nat) (paramPos : List Nat) :
Creates an E-matching theorem for a theorem with proof `proof`, `numParams` parameters, and the given set of patterns.
Pattern variables are represented using de Bruijn indices.
-/
def mkEMatchTheoremCore (origin : Origin) (levelParams : Array Name) (numParams : Nat) (proof : Expr) (patterns : List Expr) (kind : TheoremKind): MetaM EMatchTheorem := do
def mkEMatchTheoremCore (origin : Origin) (levelParams : Array Name) (numParams : Nat) (proof : Expr) (patterns : List Expr) (kind : EMatchTheoremKind) : MetaM EMatchTheorem := do
let (patterns, symbols, bvarFound) NormalizePattern.main patterns
if symbols.isEmpty then
throwError "invalid pattern for `{← origin.pp}`{indentD (patterns.map ppPattern)}\nthe pattern does not contain constant symbols for indexing"
@@ -557,7 +557,7 @@ private def getProofFor (declName : Name) : CoreM Expr := do
Creates an E-matching theorem for `declName` with `numParams` parameters, and the given set of patterns.
Pattern variables are represented using de Bruijn indices.
-/
def mkEMatchTheorem (declName : Name) (numParams : Nat) (patterns : List Expr) (kind : TheoremKind) : MetaM EMatchTheorem := do
def mkEMatchTheorem (declName : Name) (numParams : Nat) (patterns : List Expr) (kind : EMatchTheoremKind) : MetaM EMatchTheorem := do
mkEMatchTheoremCore (.decl declName) #[] numParams ( getProofFor declName) patterns kind
/--
@@ -602,7 +602,7 @@ def mkEMatchEqTheorem (declName : Name) (normalizePattern := true) (useLhs : Boo
Adds an E-matching theorem to the environment.
See `mkEMatchTheorem`.
-/
def addEMatchTheorem (declName : Name) (numParams : Nat) (patterns : List Expr) (kind : TheoremKind) : MetaM Unit := do
def addEMatchTheorem (declName : Name) (numParams : Nat) (patterns : List Expr) (kind : EMatchTheoremKind) : MetaM Unit := do
ematchTheoremsExt.add ( mkEMatchTheorem declName numParams patterns kind)
/--
@@ -696,7 +696,7 @@ private def collectPatterns? (proof : Expr) (xs : Array Expr) (searchPlaces : Ar
| return none
return some (ps, s.symbols.toList)
def mkEMatchTheoremWithKind? (origin : Origin) (levelParams : Array Name) (proof : Expr) (kind : TheoremKind) : MetaM (Option EMatchTheorem) := do
def mkEMatchTheoremWithKind? (origin : Origin) (levelParams : Array Name) (proof : Expr) (kind : EMatchTheoremKind) : MetaM (Option EMatchTheorem) := do
if kind == .eqLhs then
return ( mkEMatchEqTheoremCore origin levelParams proof (normalizePattern := true) (useLhs := true))
else if kind == .eqRhs then
@@ -726,7 +726,7 @@ where
levelParams, origin, kind
}
def mkEMatchTheoremForDecl (declName : Name) (thmKind : TheoremKind) : MetaM EMatchTheorem := do
def mkEMatchTheoremForDecl (declName : Name) (thmKind : EMatchTheoremKind) : MetaM EMatchTheorem := do
let some thm mkEMatchTheoremWithKind? (.decl declName) #[] ( getProofFor declName) thmKind
| throwError "`@{thmKind.toAttribute} theorem {declName}` {thmKind.explainFailure}, consider using different options or the `grind_pattern` command"
return thm
@@ -736,7 +736,7 @@ def mkEMatchEqTheoremsForDef? (declName : Name) : MetaM (Option (Array EMatchThe
eqns.mapM fun eqn => do
mkEMatchEqTheorem eqn (normalizePattern := true)
private def addGrindEqAttr (declName : Name) (attrKind : AttributeKind) (thmKind : TheoremKind) (useLhs := true) : MetaM Unit := do
private def addGrindEqAttr (declName : Name) (attrKind : AttributeKind) (thmKind : EMatchTheoremKind) (useLhs := true) : MetaM Unit := do
if ( getConstInfo declName).isTheorem then
ematchTheoremsExt.add ( mkEMatchEqTheorem declName (normalizePattern := true) (useLhs := useLhs)) attrKind
else if let some thms mkEMatchEqTheoremsForDef? declName then
@@ -763,7 +763,7 @@ def EMatchTheorems.eraseDecl (s : EMatchTheorems) (declName : Name) : MetaM EMat
throwErr
return s.erase <| .decl declName
def addEMatchAttr (declName : Name) (attrKind : AttributeKind) (thmKind : TheoremKind) : MetaM Unit := do
def addEMatchAttr (declName : Name) (attrKind : AttributeKind) (thmKind : EMatchTheoremKind) : MetaM Unit := do
if thmKind == .eqLhs then
addGrindEqAttr declName attrKind thmKind (useLhs := true)
else if thmKind == .eqRhs then

View File

@@ -53,7 +53,7 @@ private def isEqTrueHyp? (proof : Expr) : Option FVarId := Id.run do
return some fvarId
/-- Similar to `mkEMatchTheoremWithKind?`, but swallow any exceptions. -/
private def mkEMatchTheoremWithKind'? (origin : Origin) (proof : Expr) (kind : TheoremKind) : MetaM (Option EMatchTheorem) := do
private def mkEMatchTheoremWithKind'? (origin : Origin) (proof : Expr) (kind : EMatchTheoremKind) : MetaM (Option EMatchTheorem) := do
try
mkEMatchTheoremWithKind? origin #[] proof kind
catch _ =>

View File

@@ -74,12 +74,15 @@ private def introNext (goal : Goal) (generation : Nat) : GrindM IntroResult := d
else
return .done
def isEagerCasesCandidate (goal : Goal) (type : Expr) : Bool := Id.run do
private def isEagerCasesCandidate (goal : Goal) (type : Expr) : Bool := Id.run do
let .const declName _ := type.getAppFn | return false
return goal.casesTypes.isEagerSplit declName
private def applyCases? (goal : Goal) (fvarId : FVarId) : MetaM (Option (List Goal)) := goal.mvarId.withContext do
if isEagerCasesCandidate goal ( fvarId.getType) then
private def applyCases? (goal : Goal) (fvarId : FVarId) : GrindM (Option (List Goal)) := goal.mvarId.withContext do
let type whnfD ( fvarId.getType)
if isEagerCasesCandidate goal type then
if let .const declName _ := type.getAppFn then
saveCases declName true
let mvarIds cases goal.mvarId (mkFVar fvarId)
return mvarIds.map fun mvarId => { goal with mvarId }
else

View File

@@ -93,6 +93,7 @@ structure Result where
skipped : List Goal
issues : List MessageData
config : Grind.Config
trace : Trace
def Result.hasFailures (r : Result) : Bool :=
!r.failures.isEmpty
@@ -113,7 +114,8 @@ def main (mvarId : MVarId) (params : Params) (mainDeclName : Name) (fallback : F
let (failures, skipped) solve goals fallback
trace[grind.debug.final] "{← ppGoals goals}"
let issues := ( get).issues
return { failures, skipped, issues, config := params.config }
let trace := ( get).trace
return { failures, skipped, issues, config := params.config, trace }
go.run mainDeclName params fallback
end Lean.Meta.Grind

View File

@@ -190,6 +190,9 @@ def splitNext : GrindTactic := fun goal => do
casesMatch ( get).mvarId c
else
let major mkCasesMajor c
if ( getConfig).trace then
if let .const declName _ := ( whnfD ( inferType major)).getAppFn then
saveCases declName false
cases ( get).mvarId major
let goal get
let goals := mvarIds.map fun mvarId => { goal with mvarId }

View File

@@ -65,6 +65,23 @@ instance : BEq CongrTheoremCacheKey where
instance : Hashable CongrTheoremCacheKey where
hash a := mixHash (unsafe ptrAddrUnsafe a.f).toUInt64 (hash a.numArgs)
structure EMatchTheoremTrace where
origin : Origin
kind : EMatchTheoremKind
deriving BEq, Hashable
/--
E-match theorems and case-splits performed by `grind`.
Note that it may contain elements that are not needed by the final proof.
For example, `grind` instantiated the theorem, but theorem instance was not actually used
in the proof.
-/
structure Trace where
thms : PHashSet EMatchTheoremTrace := {}
eagerCases : PHashSet Name := {}
cases : PHashSet Name := {}
deriving Inhabited
/-- State for the `GrindM` monad. -/
structure State where
/-- `ShareCommon` (aka `Hashconsing`) state. -/
@@ -91,6 +108,8 @@ structure State where
users when `grind` fails.
-/
issues : List MessageData := []
/-- `trace` for `grind?` -/
trace : Trace := {}
private opaque MethodsRefPointed : NonemptyType.{0}
private def MethodsRef : Type := MethodsRefPointed.type
@@ -117,6 +136,17 @@ def getNatZeroExpr : GrindM Expr := do
def getMainDeclName : GrindM Name :=
return ( readThe Context).mainDeclName
def saveEMatchTheorem (thm : EMatchTheorem) : GrindM Unit := do
if ( getConfig).trace then
modify fun s => { s with trace.thms := s.trace.thms.insert { origin := thm.origin, kind := thm.kind } }
def saveCases (declName : Name) (eager : Bool) : GrindM Unit := do
if ( getConfig).trace then
if eager then
modify fun s => { s with trace.eagerCases := s.trace.eagerCases.insert declName }
else
modify fun s => { s with trace.cases := s.trace.cases.insert declName }
@[inline] def getMethodsRef : GrindM MethodsRef :=
read
@@ -138,9 +168,9 @@ Applies hash-consing to `e`. Recall that all expressions in a `grind` goal have
been hash-consed. We perform this step before we internalize expressions.
-/
def shareCommon (e : Expr) : GrindM Expr := do
modifyGet fun { scState, nextThmIdx, congrThms, trueExpr, falseExpr, natZExpr, simpStats, lastTag, issues } =>
modifyGet fun { scState, nextThmIdx, congrThms, trueExpr, falseExpr, natZExpr, simpStats, lastTag, issues, trace } =>
let (e, scState) := ShareCommon.State.shareCommon scState e
(e, { scState, nextThmIdx, congrThms, trueExpr, falseExpr, natZExpr, simpStats, lastTag, issues })
(e, { scState, nextThmIdx, congrThms, trueExpr, falseExpr, natZExpr, simpStats, lastTag, issues, trace })
/-- Returns `true` if `e` is the internalized `True` expression. -/
def isTrueExpr (e : Expr) : GrindM Bool :=
@@ -458,7 +488,8 @@ def addNewFact (proof : Expr) (prop : Expr) (generation : Nat) : GoalM Unit := d
modify fun s => { s with newFacts := s.newFacts.enqueue { proof, prop, generation } }
/-- Adds a new theorem instance produced using E-matching. -/
def addTheoremInstance (proof : Expr) (prop : Expr) (generation : Nat) : GoalM Unit := do
def addTheoremInstance (thm : EMatchTheorem) (proof : Expr) (prop : Expr) (generation : Nat) : GoalM Unit := do
saveEMatchTheorem thm
addNewFact proof prop generation
modify fun s => { s with numInstances := s.numInstances + 1 }

View File

@@ -0,0 +1,81 @@
attribute [grind =] List.length_cons
attribute [grind ] List.getElem?_eq_getElem
attribute [grind =] List.length_replicate
attribute [grind =] List.getElem_replicate
attribute [grind =] List.getElem?_eq_none
attribute [grind =] List.getElem?_eq_some_iff
attribute [grind =] getElem!_pos
attribute [grind =] Option.map_some' Option.map_none'
attribute [grind =] List.getElem?_map
attribute [grind =] List.getElem?_replicate
attribute [grind =] List.getLast?_eq_some_iff
attribute [grind] List.mem_concat_self
attribute [grind =] List.getElem_cons_zero in
attribute [grind =] List.getElem?_cons_zero in
/--
info: Try this: grind only [= List.getElem?_replicate, = List.getElem?_eq_none]
-/
#guard_msgs (info) in
theorem getElem?_replicate' : (List.replicate n a)[m]? = if m < n then some a else none := by
grind?
/--
info: Try this: grind only [= List.length_cons]
-/
#guard_msgs (info) in
example : 0 < (x :: t).length := by
grind?
/--
info: Try this: grind only [= List.getElem?_replicate, = List.getElem?_eq_some_iff, = List.getElem?_map, =
List.getElem_replicate, = List.getElem?_eq_none, = Option.map_some', = Option.map_none', = List.length_replicate, →
List.getElem?_eq_getElem, cases And, cases Or, cases Exists]
-/
#guard_msgs (info) in
theorem map_replicate' : (List.replicate n a).map f = List.replicate n (f a) := by
grind?
/--
info: Try this: grind only [= List.getLast?_eq_some_iff, List.mem_concat_self, cases Exists]
-/
#guard_msgs (info) in
theorem mem_of_getLast?_eq_some' {xs : List α} {a : α} (h : xs.getLast? = some a) : a xs := by
grind?
def f : Nat Nat
| 0 => 1
| _ => 2
/--
info: Try this: grind only
-/
#guard_msgs (info) in
example : x = 0 f x = 1 := by
unfold f
grind? -- should not include match equations
attribute [grind] f
/--
info: Try this: grind only [f]
-/
#guard_msgs (info) in
example : x = 0 f x = 1 := by
grind? [f]
opaque g : Nat Nat
theorem gthm : g (g x) = g x := sorry
grind_pattern gthm => g (g x)
/--
info: Try this: grind only [usr gthm]
-/
#guard_msgs (info) in
example : g (g (g x)) = g x := by
grind?