|
7 | 7 | prelude |
8 | 8 | public import Lean.Meta.Tactic.Grind.Types |
9 | 9 | public import Lean.Meta.Tactic.Grind.SearchM |
| 10 | +public import Lean.Meta.Tactic.Grind.Action |
10 | 11 | import Lean.Meta.Tactic.Grind.Intro |
11 | 12 | import Lean.Meta.Tactic.Grind.Cases |
12 | 13 | import Lean.Meta.Tactic.Grind.Util |
13 | 14 | import Lean.Meta.Tactic.Grind.CasesMatch |
14 | 15 | import Lean.Meta.Tactic.Grind.Internalize |
| 16 | +import Lean.Meta.Tactic.Grind.Anchor |
15 | 17 | public section |
16 | 18 | namespace Lean.Meta.Grind |
17 | 19 |
|
@@ -242,6 +244,131 @@ private def casesWithTrace (mvarId : MVarId) (major : Expr) : GoalM (List MVarId |
242 | 244 | saveCases declName false |
243 | 245 | cases mvarId major |
244 | 246 |
|
| 247 | +namespace Action |
| 248 | + |
| 249 | +/-- |
| 250 | +Given a `mvarId` associated with a subgoal created by `splitCore`, inspects the |
| 251 | +proof term assigned to `mvarId` and tries to extract the proof of `False` that does not |
| 252 | +depend on hypotheses introduced in the subgoal. |
| 253 | +For example: suppose the subgoal is of the form `p → q → False` where `p` and `q` are new |
| 254 | +hypotheses introduced during case analysis. If the proof is of the form `fun _ _ => h`, returns |
| 255 | +`some h`. |
| 256 | +-/ |
| 257 | +private def getFalseProof? (mvarId : MVarId) : MetaM (Option Expr) := mvarId.withContext do |
| 258 | + let proof ← instantiateMVars (mkMVar mvarId) |
| 259 | + go proof |
| 260 | +where |
| 261 | + go (proof : Expr) : MetaM (Option Expr) := do |
| 262 | + match_expr proof with |
| 263 | + | False.elim _ p => return some p |
| 264 | + | False.casesOn _ p => return some p |
| 265 | + | id α p => if α.isFalse then return some p else return none |
| 266 | + | _ => |
| 267 | + /- |
| 268 | + **Note**: `intros` tactics may hide the `False` proof behind a `casesOn` |
| 269 | + For example: suppose the subgoal has a type of the form `p₁ → q₁ ∧ q₂ → p₂ → False` |
| 270 | + The proof will be of the form `fun _ h => h.casesOn (fun _ _ => hf)` where `hf` is the proof |
| 271 | + of `False` we are looking for. |
| 272 | + Non-chronological backtracking currently fails in this kind of example. |
| 273 | + -/ |
| 274 | + let .lam _ _ b _ := proof | return none |
| 275 | + if b.hasLooseBVars then return none |
| 276 | + go b |
| 277 | + |
| 278 | +/-- |
| 279 | +Performs a case-split using `c`. |
| 280 | +Remark: `numCases` and `isRec` are computed using `checkSplitStatus`. |
| 281 | +-/ |
| 282 | +private def splitCore (c : SplitInfo) (numCases : Nat) (isRec : Bool) (stopAtFirstFailure : Bool) : Action := fun goal _ kp => do |
| 283 | + let mvarDecl ← goal.mvarId.getDecl |
| 284 | + let numIndices := mvarDecl.lctx.numIndices |
| 285 | + let mvarId ← goal.mkAuxMVar |
| 286 | + let cExpr := c.getExpr |
| 287 | + let (mvarIds, goal) ← GoalM.run goal do |
| 288 | + let gen ← getGeneration cExpr |
| 289 | + let genNew := if numCases > 1 || isRec then gen+1 else gen |
| 290 | + saveSplitDiagInfo cExpr genNew numCases c.source |
| 291 | + markCaseSplitAsResolved cExpr |
| 292 | + trace_goal[grind.split] "{cExpr}, generation: {gen}" |
| 293 | + let mvarIds ← if let .imp e h _ := c then |
| 294 | + casesWithTrace mvarId (mkGrindEM (e.forallDomain h)) |
| 295 | + else if (← isMatcherApp cExpr) then |
| 296 | + casesMatch mvarId cExpr |
| 297 | + else |
| 298 | + casesWithTrace mvarId (← mkCasesMajor cExpr) |
| 299 | + let subgoals := mvarIds.map fun mvarId => { goal with mvarId } |
| 300 | + let traceEnabled := (← getConfig).trace |
| 301 | + let mut seqNew : Array (List (TSyntax `grind)) := #[] |
| 302 | + let mut stuckNew : Array Goal := #[] |
| 303 | + for subgoal in subgoals do |
| 304 | + match (← kp subgoal) with |
| 305 | + | .stuck gs => |
| 306 | + if stopAtFirstFailure then |
| 307 | + /- |
| 308 | + **Note**: We don't need to assign `goal.mvarId` when `stopAtFirstFailure = true` |
| 309 | + because the caller will not be able to process the all failure/stuck goals anyway. |
| 310 | + -/ |
| 311 | + return .stuck gs |
| 312 | + else |
| 313 | + stuckNew := stuckNew ++ gs |
| 314 | + | .closed seq => |
| 315 | + if let some falseProof ← getFalseProof? subgoal.mvarId then |
| 316 | + goal.mvarId.assignFalseProof falseProof |
| 317 | + return .closed seq |
| 318 | + else if !seq.isEmpty then |
| 319 | + /- **Note**: if the sequence is empty, it means the user will never see this goal. -/ |
| 320 | + seqNew := seqNew.push seq |
| 321 | + if (← goal.mvarId.getType).isFalse then |
| 322 | + /- **Note**: We add the marker to assist `getFalseExpr?` -/ |
| 323 | + goal.mvarId.assign (mkExpectedPropHint (← instantiateMVars (mkMVar mvarId)) (mkConst ``False)) |
| 324 | + else |
| 325 | + goal.mvarId.assign (← instantiateMVars (mkMVar mvarId)) |
| 326 | + if stuckNew.isEmpty then |
| 327 | + if traceEnabled then |
| 328 | + let seqListNew ← if h : seqNew.size = 1 then |
| 329 | + pure seqNew[0] |
| 330 | + else |
| 331 | + seqNew.toList.mapM fun s => mkGrindNext s |
| 332 | + let mut seqListNew := seqListNew |
| 333 | + let anchor ← goal.withContext <| getAnchor cExpr |
| 334 | + -- **TODO**: compute the exact number of digits |
| 335 | + let numDigits := 4 |
| 336 | + let anchorPrefix := anchor >>> (64 - 16) |
| 337 | + let hexnum := mkNode `hexnum #[mkAtom (anchorToString numDigits anchorPrefix)] |
| 338 | + let cases ← `(grind| cases #$hexnum) |
| 339 | + seqListNew := cases :: seqListNew |
| 340 | + return .closed seqListNew |
| 341 | + else |
| 342 | + return .closed [] |
| 343 | + else |
| 344 | + return .stuck stuckNew.toList |
| 345 | + |
| 346 | +/-- |
| 347 | +Selects a case-split from the list of candidates, performs the split and applies |
| 348 | +continuation to all subgoals. |
| 349 | +If a subgoal is solved without using new hypotheses, closes the original goal using this proof. That is, |
| 350 | +it performs non-chronological backtracking. |
| 351 | +If `stopsAtFirstFailure = true`, it stops the search as soon as the given continuation cannot solve a subgoal. |
| 352 | +-/ |
| 353 | +def splitNext (stopAtFirstFailure := true) : Action := fun goal kna kp => do |
| 354 | + let (r, goal) ← GoalM.run goal selectNextSplit? |
| 355 | + let .some c numCases isRec _ := r |
| 356 | + | kna goal |
| 357 | + let cExpr := c.getExpr |
| 358 | + let gen := goal.getGeneration cExpr |
| 359 | + let x : Action := splitCore c numCases isRec stopAtFirstFailure >> intros gen |
| 360 | + x goal kna kp |
| 361 | + |
| 362 | +end Action |
| 363 | + |
| 364 | +/-! |
| 365 | +**------------------------------------------** |
| 366 | +**------------------------------------------** |
| 367 | +**TODO** Delete rest of the file |
| 368 | +**------------------------------------------** |
| 369 | +**------------------------------------------** |
| 370 | +-/ |
| 371 | + |
245 | 372 | /-- |
246 | 373 | Performs a case-split using `c`. |
247 | 374 | Remarks: |
|
0 commit comments