| /- | |
| 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.Transform | |
| import Lean.Meta.Tactic.Injection | |
| import Lean.Meta.Tactic.Apply | |
| import Lean.Meta.Tactic.Refl | |
| import Lean.Meta.Tactic.Cases | |
| import Lean.Meta.Tactic.Subst | |
| import Lean.Meta.Tactic.Simp.Types | |
| import Lean.Meta.Tactic.Assumption | |
| namespace Lean.Meta | |
| private def mkAnd? (args : Array Expr) : Option Expr := Id.run do | |
| if args.isEmpty then | |
| return none | |
| else | |
| let mut result := args.back | |
| for arg in args.reverse[1:] do | |
| result := mkApp2 (mkConst ``And) arg result | |
| return result | |
| def elimOptParam (type : Expr) : CoreM Expr := do | |
| Core.transform type fun e => | |
| if e.isAppOfArity ``optParam 2 then | |
| return TransformStep.visit (e.getArg! 0) | |
| else | |
| return .continue | |
| private partial def mkInjectiveTheoremTypeCore? (ctorVal : ConstructorVal) (useEq : Bool) : MetaM (Option Expr) := do | |
| let us := ctorVal.levelParams.map mkLevelParam | |
| let type ← elimOptParam ctorVal.type | |
| forallBoundedTelescope type ctorVal.numParams fun params type => | |
| forallTelescope type fun args1 resultType => do | |
| let jp (args2 args2New : Array Expr) : MetaM (Option Expr) := do | |
| let lhs := mkAppN (mkAppN (mkConst ctorVal.name us) params) args1 | |
| let rhs := mkAppN (mkAppN (mkConst ctorVal.name us) params) args2 | |
| let eq ← mkEq lhs rhs | |
| let mut eqs := #[] | |
| for arg1 in args1, arg2 in args2 do | |
| let arg1Type ← inferType arg1 | |
| if !(← isProp arg1Type) && arg1 != arg2 then | |
| eqs := eqs.push (← mkEqHEq arg1 arg2) | |
| if let some andEqs := mkAnd? eqs then | |
| let result ← if useEq then | |
| mkEq eq andEqs | |
| else | |
| mkArrow eq andEqs | |
| mkForallFVars params (← mkForallFVars args1 (← mkForallFVars args2New result)) | |
| else | |
| return none | |
| let rec mkArgs2 (i : Nat) (type : Expr) (args2 args2New : Array Expr) : MetaM (Option Expr) := do | |
| if h : i < args1.size then | |
| match (← whnf type) with | |
| | Expr.forallE n d b _ => | |
| let arg1 := args1.get ⟨i, h⟩ | |
| if arg1.occurs resultType then | |
| mkArgs2 (i + 1) (b.instantiate1 arg1) (args2.push arg1) args2New | |
| else | |
| withLocalDecl n (if useEq then BinderInfo.default else BinderInfo.implicit) d fun arg2 => | |
| mkArgs2 (i + 1) (b.instantiate1 arg2) (args2.push arg2) (args2New.push arg2) | |
| | _ => throwError "unexpected constructor type for '{ctorVal.name}'" | |
| else | |
| jp args2 args2New | |
| if useEq then | |
| mkArgs2 0 type #[] #[] | |
| else | |
| withNewBinderInfos (params.map fun param => (param.fvarId!, BinderInfo.implicit)) <| | |
| withNewBinderInfos (args1.map fun arg1 => (arg1.fvarId!, BinderInfo.implicit)) <| | |
| mkArgs2 0 type #[] #[] | |
| private def mkInjectiveTheoremType? (ctorVal : ConstructorVal) : MetaM (Option Expr) := | |
| mkInjectiveTheoremTypeCore? ctorVal false | |
| private def injTheoremFailureHeader (ctorName : Name) : MessageData := | |
| m!"failed to prove injectivity theorem for constructor '{ctorName}', use 'set_option genInjectivity false' to disable the generation" | |
| private def throwInjectiveTheoremFailure {α} (ctorName : Name) (mvarId : MVarId) : MetaM α := | |
| throwError "{injTheoremFailureHeader ctorName}{indentD <| MessageData.ofGoal mvarId}" | |
| private def solveEqOfCtorEq (ctorName : Name) (mvarId : MVarId) (h : FVarId) : MetaM Unit := do | |
| match (← injection mvarId h) with | |
| | InjectionResult.solved => unreachable! | |
| | InjectionResult.subgoal mvarId .. => | |
| (← mvarId.splitAnd).forM fun mvarId => | |
| unless (← mvarId.assumptionCore) do | |
| throwInjectiveTheoremFailure ctorName mvarId | |
| private def mkInjectiveTheoremValue (ctorName : Name) (targetType : Expr) : MetaM Expr := | |
| forallTelescopeReducing targetType fun xs type => do | |
| let mvar ← mkFreshExprSyntheticOpaqueMVar type | |
| solveEqOfCtorEq ctorName mvar.mvarId! xs.back.fvarId! | |
| mkLambdaFVars xs mvar | |
| def mkInjectiveTheoremNameFor (ctorName : Name) : Name := | |
| ctorName ++ `inj | |
| private def mkInjectiveTheorem (ctorVal : ConstructorVal) : MetaM Unit := do | |
| let some type ← mkInjectiveTheoremType? ctorVal | |
| | return () | |
| let value ← mkInjectiveTheoremValue ctorVal.name type | |
| let name := mkInjectiveTheoremNameFor ctorVal.name | |
| addDecl <| Declaration.thmDecl { | |
| name | |
| levelParams := ctorVal.levelParams | |
| type := (← instantiateMVars type) | |
| value := (← instantiateMVars value) | |
| } | |
| def mkInjectiveEqTheoremNameFor (ctorName : Name) : Name := | |
| ctorName ++ `injEq | |
| private def mkInjectiveEqTheoremType? (ctorVal : ConstructorVal) : MetaM (Option Expr) := | |
| mkInjectiveTheoremTypeCore? ctorVal true | |
| private def mkInjectiveEqTheoremValue (ctorName : Name) (targetType : Expr) : MetaM Expr := do | |
| forallTelescopeReducing targetType fun xs type => do | |
| let mvar ← mkFreshExprSyntheticOpaqueMVar type | |
| let [mvarId₁, mvarId₂] ← mvar.mvarId!.apply (mkConst ``Eq.propIntro) | |
| | throwError "unexpected number of subgoals when proving injective theorem for constructor '{ctorName}'" | |
| let (h, mvarId₁) ← mvarId₁.intro1 | |
| let (_, mvarId₂) ← mvarId₂.intro1 | |
| solveEqOfCtorEq ctorName mvarId₁ h | |
| let mvarId₂ ← mvarId₂.casesAnd | |
| if let some mvarId₂ ← mvarId₂.substEqs then | |
| try mvarId₂.refl catch _ => throwError (injTheoremFailureHeader ctorName) | |
| mkLambdaFVars xs mvar | |
| private def mkInjectiveEqTheorem (ctorVal : ConstructorVal) : MetaM Unit := do | |
| let some type ← mkInjectiveEqTheoremType? ctorVal | |
| | return () | |
| let value ← mkInjectiveEqTheoremValue ctorVal.name type | |
| let name := mkInjectiveEqTheoremNameFor ctorVal.name | |
| addDecl <| Declaration.thmDecl { | |
| name | |
| levelParams := ctorVal.levelParams | |
| type := (← instantiateMVars type) | |
| value := (← instantiateMVars value) | |
| } | |
| addSimpTheorem (ext := simpExtension) name (post := true) (inv := false) AttributeKind.global (prio := eval_prio default) | |
| register_builtin_option genInjectivity : Bool := { | |
| defValue := true | |
| descr := "generate injectivity theorems for inductive datatype constructors" | |
| } | |
| def mkInjectiveTheorems (declName : Name) : MetaM Unit := do | |
| if (← getEnv).contains ``Eq.propIntro && genInjectivity.get (← getOptions) && !(← isInductivePredicate declName) then | |
| let info ← getConstInfoInduct declName | |
| unless info.isUnsafe do | |
| -- We need to reset the local context here because `solveEqOfCtorEq` uses | |
| -- `assumptionCore`, which can reference "outside" free variables that | |
| -- were not introduced by `mkInjective(Eq)Theorem` and are not abstracted | |
| -- by `mkLambdaFVars`, thus adding a declaration with free variables. | |
| -- See https://github.com/leanprover/lean4/issues/2188 | |
| withLCtx {} {} do | |
| for ctor in info.ctors do | |
| let ctorVal ← getConstInfoCtor ctor | |
| if ctorVal.numFields > 0 then | |
| mkInjectiveTheorem ctorVal | |
| mkInjectiveEqTheorem ctorVal | |
| end Lean.Meta |