lean4-htt/src/Lean/Meta/Tactic/Contradiction.lean
2021-08-22 18:16:14 -07:00

161 lines
6.5 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/-
Copyright (c) 2021 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
import Lean.Meta.MatchUtil
import Lean.Meta.Tactic.Assumption
import Lean.Meta.Tactic.Cases
import Lean.Meta.Tactic.Apply
namespace Lean.Meta
structure Contradiction.Config where
useDecide : Bool := true
/- When checking for empty types, `searchFuel` specifies the number of goals visited. -/
searchFuel : Nat := 16
/- Support for hypotheses such as
```
h : (x y : Nat) (ys : List Nat) → x = 0 → y::ys = [a, b, c] → False
```
This kind of hypotheses appear when proving conditional equation theorems for match expressions. -/
genDiseq : Bool := false
-- We only consider inductives with no constructors and indexed families
private def isElimEmptyInductiveCandidate (fvarId : FVarId) : MetaM Bool := do
let localDecl ← getLocalDecl fvarId
let type ← whnfD localDecl.type
matchConstInduct type.getAppFn (fun _ => pure false) fun info _ => do
return info.ctors.length == 0 || info.numIndices > 0
namespace ElimEmptyInductive
abbrev M := StateRefT Nat MetaM
instance : MonadBacktrack SavedState M where
saveState := Meta.saveState
restoreState s := s.restore
partial def elim (mvarId : MVarId) (fvarId : FVarId) : M Bool := do
if (← get) == 0 then
trace[Meta.Tactic.contradiction] "elimEmptyInductive out-of-fuel"
return false
modify (. - 1)
-- We only consider inductives with no constructors and indexed families
commitWhen do
let subgoals ← try cases mvarId fvarId catch ex => trace[Meta.Tactic.contradiction] "{ex.toMessageData}"; return false
trace[Meta.Tactic.contradiction] "elimEmptyInductive, number subgoals: {subgoals.size}"
for subgoal in subgoals do
-- If one of the fields is uninhabited, then we are done
let found ← withMVarContext subgoal.mvarId do
for field in subgoal.fields do
let field := subgoal.subst.apply field
if field.isFVar then
if (← isElimEmptyInductiveCandidate field.fvarId!) then
if (← elim subgoal.mvarId field.fvarId!) then
return true
return false
unless found == true do -- TODO: check why we need true here
return false
return true
end ElimEmptyInductive
private def elimEmptyInductive (mvarId : MVarId) (fvarId : FVarId) (fuel : Nat) : MetaM Bool := do
withMVarContext mvarId do
if (← isElimEmptyInductiveCandidate fvarId) then
commitWhen do
ElimEmptyInductive.elim (← exfalso mvarId) fvarId |>.run' fuel
else
return false
/-- Return true if `e` is of the form `(x : α) → ... → s = t → ... → False` -/
private def isGenDiseq (e : Expr) : Bool :=
match e with
| Expr.forallE _ d b _ => (d.isEq || b.hasLooseBVar 0) && isGenDiseq b
| _ => e.isConstOf ``False
/--
Close goal if `localDecl` is a "generalized disequality". Example:
```
h : (x y : Nat) (ys : List Nat) → x = 0 → y::ys = [a, b, c] → False
```
This kind of hypotheses is created when we generate conditional equations for match expressions.
-/
private def processGenDiseq (mvarId : MVarId) (localDecl : LocalDecl) : MetaM Bool := do
assert! isGenDiseq localDecl.type
let val? ← withNewMCtxDepth do
let (args, _, _) ← forallMetaTelescope localDecl.type
for arg in args do
let argType ← inferType arg
if let some (_, lhs, rhs) ← matchEq? argType then
unless (← isDefEq lhs rhs) do
return none
unless (← isDefEq arg (← mkEqRefl lhs)) do
return none
let falseProof ← instantiateMVars (mkAppN localDecl.toExpr args)
if (← hasAssignableMVar falseProof) then
return none
return some (← mkFalseElim (← getMVarType mvarId) falseProof)
if let some val := val? then
assignExprMVar mvarId val
return true
else
return false
def contradictionCore (mvarId : MVarId) (config : Contradiction.Config) : MetaM Bool := do
withMVarContext mvarId do
checkNotAssigned mvarId `contradiction
for localDecl in (← getLCtx) do
unless localDecl.isAuxDecl do
-- (h : ¬ p) (h' : p)
if let some p ← matchNot? localDecl.type then
if let some pFVarId ← findLocalDeclWithType? p then
assignExprMVar mvarId (← mkAbsurd (← getMVarType mvarId) (mkFVar pFVarId) localDecl.toExpr)
return true
-- (h : x ≠ x)
if let some (_, lhs, rhs) ← matchNe? localDecl.type then
if (← isDefEq lhs rhs) then
assignExprMVar mvarId (← mkAbsurd (← getMVarType mvarId) (← mkEqRefl lhs) localDecl.toExpr)
return true
let mut isEq := false
-- (h : ctor₁ ... = ctor₂ ...)
if let some (_, lhs, rhs) ← matchEq? localDecl.type then
isEq := true
if let some lhsCtor ← matchConstructorApp? lhs then
if let some rhsCtor ← matchConstructorApp? rhs then
if lhsCtor.name != rhsCtor.name then
assignExprMVar mvarId (← mkNoConfusion (← getMVarType mvarId) localDecl.toExpr)
return true
-- (h : p) s.t. `decide p` evaluates to `false`
if config.useDecide && !localDecl.type.hasFVar then
let type ← instantiateMVars localDecl.type
if !type.hasMVar && !type.hasFVar then
try
let d ← mkDecide localDecl.type
let r ← withDefault <| whnf d
if r.isConstOf ``false then
let hn := mkAppN (mkConst ``of_decide_eq_false) <| d.getAppArgs.push (← mkEqRefl d)
assignExprMVar mvarId (← mkAbsurd (← getMVarType mvarId) localDecl.toExpr hn)
return true
catch _ =>
pure ()
-- "generalized" diseq
if config.genDiseq && isGenDiseq localDecl.type then
if (← processGenDiseq mvarId localDecl) then
return true
-- (h : <empty-inductive-type>)
unless isEq do
-- We do not use `elimEmptyInductive` for equality, since `cases h` produces a huge proof
-- when `(h : 10000 = 10001)`. TODO: `cases` add a threshold at `cases`
if (← elimEmptyInductive mvarId localDecl.fvarId config.searchFuel) then
return true
return false
def contradiction (mvarId : MVarId) (config : Contradiction.Config := {}) : MetaM Unit :=
unless (← contradictionCore mvarId config) do
throwTacticEx `contradiction mvarId ""
builtin_initialize registerTraceClass `Meta.Tactic.contradiction
end Lean.Meta