feat: induction using <term> (#3188)
right now, the `induction` tactic accepts a custom eliminator using the `using <ident>` syntax, but is restricted to identifiers. This limitation becomes annoying when the elminator has explicit parameters that are not targets, and the user (naturally) wants to be able to write ``` induction a, b, c using foo (x := …) ``` This generalizes the syntax to expressions and changes the code accordingly. This can be used to instantiate a multi-motive induction: ``` example (a : A) : True := by induction a using A.rec (motive_2 := fun b => True) case mkA b IH => exact trivial case A => exact trivial case mkB b IH => exact trivial ``` For this to work the term elaborator learned the `heedElabAsElim` flag, `true` by default. But in the default setting, `A.rec (motive_2 := fun b => True)` would fail to elaborate, because there is no expected type. So the induction tactic will elaborate in a mode where that attribute is simply ignored. As a side effect, the “failed to infer implicit target” error message is improved and prints the name of the implicit target that could not be instantiated.
This commit is contained in:
parent
f9e5f1f1fd
commit
550fa6994e
11 changed files with 369 additions and 67 deletions
|
|
@ -559,7 +559,7 @@ You can use `with` to provide the variables names for each constructor.
|
|||
- `induction e`, where `e` is an expression instead of a variable,
|
||||
generalizes `e` in the goal, and then performs induction on the resulting variable.
|
||||
- `induction e using r` allows the user to specify the principle of induction that should be used.
|
||||
Here `r` should be a theorem whose result type must be of the form `C t`,
|
||||
Here `r` should be a term whose result type must be of the form `C t`,
|
||||
where `C` is a bound variable and `t` is a (possibly empty) sequence of bound variables
|
||||
- `induction e generalizing z₁ ... zₙ`, where `z₁ ... zₙ` are variables in the local context,
|
||||
generalizes over `z₁ ... zₙ` before applying the induction but then introduces them in each goal.
|
||||
|
|
@ -567,7 +567,7 @@ You can use `with` to provide the variables names for each constructor.
|
|||
- Given `x : Nat`, `induction x with | zero => tac₁ | succ x' ih => tac₂`
|
||||
uses tactic `tac₁` for the `zero` case, and `tac₂` for the `succ` case.
|
||||
-/
|
||||
syntax (name := induction) "induction " term,+ (" using " ident)?
|
||||
syntax (name := induction) "induction " term,+ (" using " term)?
|
||||
(" generalizing" (ppSpace colGt term:max)+)? (inductionAlts)? : tactic
|
||||
|
||||
/-- A `generalize` argument, of the form `term = x` or `h : term = x`. -/
|
||||
|
|
@ -610,7 +610,7 @@ You can use `with` to provide the variables names for each constructor.
|
|||
performs cases on `e` as above, but also adds a hypothesis `h : e = ...` to each hypothesis,
|
||||
where `...` is the constructor instance for that particular case.
|
||||
-/
|
||||
syntax (name := cases) "cases " casesTarget,+ (" using " ident)? (inductionAlts)? : tactic
|
||||
syntax (name := cases) "cases " casesTarget,+ (" using " term)? (inductionAlts)? : tactic
|
||||
|
||||
/-- `rename_i x_1 ... x_n` renames the last `n` inaccessible names using the given names. -/
|
||||
syntax (name := renameI) "rename_i" (ppSpace colGt binderIdent)+ : tactic
|
||||
|
|
|
|||
|
|
@ -690,10 +690,10 @@ builtin_initialize elabAsElim : TagAttribute ←
|
|||
(applicationTime := .afterCompilation)
|
||||
fun declName => do
|
||||
let go : MetaM Unit := do
|
||||
discard <| getElimInfo declName
|
||||
let info ← getConstInfo declName
|
||||
if (← hasOptAutoParams info.type) then
|
||||
throwError "[elab_as_elim] attribute cannot be used in declarations containing optional and auto parameters"
|
||||
discard <| getElimInfo declName
|
||||
go.run' {} {}
|
||||
|
||||
/-! # Eliminator-like function application elaborator -/
|
||||
|
|
@ -937,6 +937,7 @@ def elabAppArgs (f : Expr) (namedArgs : Array NamedArg) (args : Array Arg)
|
|||
where
|
||||
/-- Return `some info` if we should elaborate as an eliminator. -/
|
||||
elabAsElim? : TermElabM (Option ElimInfo) := do
|
||||
unless (← read).heedElabAsElim do return none
|
||||
if explicit || ellipsis then return none
|
||||
let .const declName _ := f | return none
|
||||
unless (← shouldElabAsElim declName) do return none
|
||||
|
|
@ -957,8 +958,7 @@ where
|
|||
The idea is that the contribute to motive inference. See comment at `ElamElim.Context.extraArgsPos`.
|
||||
-/
|
||||
getElabAsElimExtraArgsPos (elimInfo : ElimInfo) : MetaM (Array Nat) := do
|
||||
let cinfo ← getConstInfo elimInfo.name
|
||||
forallTelescope cinfo.type fun xs type => do
|
||||
forallTelescope elimInfo.elimType fun xs type => do
|
||||
let resultArgs := type.getAppArgs
|
||||
let mut extraArgsPos := #[]
|
||||
for i in [:xs.size] do
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ structure Context where
|
|||
structure State where
|
||||
argPos : Nat := 0 -- current argument position
|
||||
targetPos : Nat := 0 -- current target at targetsStx
|
||||
motive : Option MVarId -- motive metavariable
|
||||
f : Expr
|
||||
fType : Expr
|
||||
alts : Array Alt := #[]
|
||||
|
|
@ -117,6 +118,7 @@ private def getFType : M Expr := do
|
|||
|
||||
structure Result where
|
||||
elimApp : Expr
|
||||
motive : MVarId
|
||||
alts : Array Alt := #[]
|
||||
others : Array MVarId := #[]
|
||||
|
||||
|
|
@ -134,12 +136,13 @@ partial def mkElimApp (elimInfo : ElimInfo) (targets : Array Expr) (tag : Name)
|
|||
let argPos := (← get).argPos
|
||||
if ctx.elimInfo.motivePos == argPos then
|
||||
let motive ← mkFreshExprMVar (← getArgExpectedType) MetavarKind.syntheticOpaque
|
||||
modify fun s => { s with motive := motive.mvarId! }
|
||||
addNewArg motive
|
||||
else if ctx.elimInfo.targetsPos.contains argPos then
|
||||
let s ← get
|
||||
let ctx ← read
|
||||
unless s.targetPos < ctx.targets.size do
|
||||
throwError "insufficient number of targets for '{elimInfo.name}'"
|
||||
throwError "insufficient number of targets for '{elimInfo.elimExpr}'"
|
||||
let target := ctx.targets[s.targetPos]!
|
||||
let expectedType ← getArgExpectedType
|
||||
let target ← withAssignableSyntheticOpaque <| Term.ensureHasType expectedType target
|
||||
|
|
@ -166,9 +169,8 @@ partial def mkElimApp (elimInfo : ElimInfo) (targets : Array Expr) (tag : Name)
|
|||
loop
|
||||
| _ =>
|
||||
pure ()
|
||||
let f ← Term.mkConst elimInfo.name
|
||||
let fType ← inferType f
|
||||
let (_, s) ← (loop).run { elimInfo := elimInfo, targets := targets } |>.run { f := f, fType := fType }
|
||||
let (_, s) ← (loop).run { elimInfo := elimInfo, targets := targets }
|
||||
|>.run { f := elimInfo.elimExpr, fType := elimInfo.elimType, motive := none }
|
||||
let mut others := #[]
|
||||
for mvarId in s.insts do
|
||||
try
|
||||
|
|
@ -179,7 +181,9 @@ partial def mkElimApp (elimInfo : ElimInfo) (targets : Array Expr) (tag : Name)
|
|||
mvarId.setKind .syntheticOpaque
|
||||
others := others.push mvarId
|
||||
let alts ← s.alts.filterM fun alt => return !(← alt.mvarId.isAssigned)
|
||||
return { elimApp := (← instantiateMVars s.f), alts, others := others }
|
||||
let some motive := s.motive |
|
||||
throwError "mkElimApp: motive not found"
|
||||
return { elimApp := (← instantiateMVars s.f), alts, others, motive }
|
||||
|
||||
/-- Given a goal `... targets ... |- C[targets]` associated with `mvarId`, assign
|
||||
`motiveArg := fun targets => C[targets]` -/
|
||||
|
|
@ -499,11 +503,36 @@ def getInductiveValFromMajor (major : Expr) : TacticM InductiveVal :=
|
|||
(fun _ => Meta.throwTacticEx `induction mvarId m!"major premise type is not an inductive type {indentExpr majorType}")
|
||||
(fun val _ => pure val)
|
||||
|
||||
-- `optElimId` is of the form `("using" ident)?`
|
||||
/--
|
||||
Elaborates the term in the `using` clause. We want to allow parameters to be instantiated
|
||||
(e.g. `using foo (p := …)`), but preserve other paramters, like the motives, as parameters,
|
||||
without turning them into MVars. So this uses `abstractMVars` at the end. This is inspired by
|
||||
`Lean.Elab.Tactic.addSimpTheorem`.
|
||||
|
||||
It also elaborates without `heedElabAsElim` so that users can use constants that are marked
|
||||
`elabAsElim` in the `using` clause`.
|
||||
-/
|
||||
private def elabTermForElim (stx : Syntax) : TermElabM Expr := do
|
||||
-- Short-circuit elaborating plain identifiers
|
||||
if stx.isIdent then
|
||||
if let some e ← Term.resolveId? stx (withInfo := true) then
|
||||
return e
|
||||
Term.withoutErrToSorry <| Term.withoutHeedElabAsElim do
|
||||
let e ← Term.elabTerm stx none (implicitLambda := false)
|
||||
Term.synthesizeSyntheticMVars (mayPostpone := false) (ignoreStuckTC := true)
|
||||
let e ← instantiateMVars e
|
||||
let e := e.eta
|
||||
if e.hasMVar then
|
||||
let r ← abstractMVars (levels := false) e
|
||||
return r.expr
|
||||
else
|
||||
return e
|
||||
|
||||
-- `optElimId` is of the form `("using" term)?`
|
||||
private def getElimNameInfo (optElimId : Syntax) (targets : Array Expr) (induction : Bool): TacticM ElimInfo := do
|
||||
if optElimId.isNone then
|
||||
if let some elimInfo ← getCustomEliminator? targets then
|
||||
return elimInfo
|
||||
if let some elimName ← getCustomEliminator? targets then
|
||||
return ← getElimInfo elimName
|
||||
unless targets.size == 1 do
|
||||
throwError "eliminator must be provided when multiple targets are used (use 'using <eliminator-name>'), and no default eliminator has been registered using attribute `[eliminator]`"
|
||||
let indVal ← getInductiveValFromMajor targets[0]!
|
||||
|
|
@ -514,12 +543,17 @@ private def getElimNameInfo (optElimId : Syntax) (targets : Array Expr) (inducti
|
|||
let elimName := if induction then mkRecName indVal.name else mkCasesOnName indVal.name
|
||||
getElimInfo elimName indVal.name
|
||||
else
|
||||
let elimId := optElimId[1]
|
||||
let elimName ← withRef elimId do resolveGlobalConstNoOverloadWithInfo elimId
|
||||
let elimTerm := optElimId[1]
|
||||
let elimExpr ← withRef elimTerm do elabTermForElim elimTerm
|
||||
-- not a precise check, but covers the common cases of T.recOn / T.casesOn
|
||||
-- as well as user defined T.myInductionOn to locate the constructors of T
|
||||
let baseName? := if ← isInductive elimName.getPrefix then some elimName.getPrefix else none
|
||||
withRef elimId <| getElimInfo elimName baseName?
|
||||
let baseName? ← do
|
||||
let some elimName := elimExpr.getAppFn.constName? | pure none
|
||||
if ← isInductive elimName.getPrefix then
|
||||
pure (some elimName.getPrefix)
|
||||
else
|
||||
pure none
|
||||
withRef elimTerm <| getElimExprInfo elimExpr baseName?
|
||||
|
||||
private def shouldGeneralizeTarget (e : Expr) : MetaM Bool := do
|
||||
if let .fvar fvarId .. := e then
|
||||
|
|
@ -557,8 +591,7 @@ private def generalizeTargets (exprs : Array Expr) : TacticM (Array Expr) := do
|
|||
let result ← withRef stx[1] do -- use target position as reference
|
||||
ElimApp.mkElimApp elimInfo targets tag
|
||||
trace[Elab.induction] "elimApp: {result.elimApp}"
|
||||
let elimArgs := result.elimApp.getAppArgs
|
||||
ElimApp.setMotiveArg mvarId elimArgs[elimInfo.motivePos]!.mvarId! targetFVarIds
|
||||
ElimApp.setMotiveArg mvarId result.motive targetFVarIds
|
||||
let optPreTac := getOptPreTacOfOptInductionAlts optInductionAlts
|
||||
mvarId.assign result.elimApp
|
||||
ElimApp.evalAlts elimInfo result.alts optPreTac alts initInfo (numGeneralized := n) (toClear := targetFVarIds)
|
||||
|
|
|
|||
|
|
@ -198,6 +198,8 @@ structure Context where
|
|||
sectionFVars : NameMap Expr := {}
|
||||
/-- Enable/disable implicit lambdas feature. -/
|
||||
implicitLambda : Bool := true
|
||||
/-- Heed `elab_as_elim` attribute. -/
|
||||
heedElabAsElim : Bool := true
|
||||
/-- Noncomputable sections automatically add the `noncomputable` modifier to any declaration we cannot generate code for. -/
|
||||
isNoncomputableSection : Bool := false
|
||||
/-- When `true` we skip TC failures. We use this option when processing patterns. -/
|
||||
|
|
@ -409,6 +411,17 @@ def withoutErrToSorryImp (x : TermElabM α) : TermElabM α :=
|
|||
def withoutErrToSorry [MonadFunctorT TermElabM m] : m α → m α :=
|
||||
monadMap (m := TermElabM) withoutErrToSorryImp
|
||||
|
||||
def withoutHeedElabAsElimImp (x : TermElabM α) : TermElabM α :=
|
||||
withReader (fun ctx => { ctx with heedElabAsElim := false }) x
|
||||
|
||||
/--
|
||||
Execute `x` without heeding the `elab_as_elim` attribute. Useful when there is
|
||||
no expected type (so `elabAppArgs` would fail), but expect that the user wants
|
||||
to use such constants.
|
||||
-/
|
||||
def withoutHeedElabAsElim [MonadFunctorT TermElabM m] : m α → m α :=
|
||||
monadMap (m := TermElabM) withoutHeedElabAsElimImp
|
||||
|
||||
/--
|
||||
Execute `x` but discard changes performed at `Term.State` and `Meta.State`.
|
||||
Recall that the `Environment` and `InfoState` are at `Core.State`. Thus, any updates to it will
|
||||
|
|
|
|||
|
|
@ -16,14 +16,15 @@ structure AbstractMVarsResult where
|
|||
namespace AbstractMVars
|
||||
|
||||
structure State where
|
||||
ngen : NameGenerator
|
||||
lctx : LocalContext
|
||||
mctx : MetavarContext
|
||||
nextParamIdx : Nat := 0
|
||||
paramNames : Array Name := #[]
|
||||
fvars : Array Expr := #[]
|
||||
lmap : HashMap LMVarId Level := {}
|
||||
emap : HashMap MVarId Expr := {}
|
||||
ngen : NameGenerator
|
||||
lctx : LocalContext
|
||||
mctx : MetavarContext
|
||||
nextParamIdx : Nat := 0
|
||||
paramNames : Array Name := #[]
|
||||
fvars : Array Expr := #[]
|
||||
lmap : HashMap LMVarId Level := {}
|
||||
emap : HashMap MVarId Expr := {}
|
||||
abstractLevels : Bool -- whether to abstract level mvars
|
||||
|
||||
abbrev M := StateM State
|
||||
|
||||
|
|
@ -42,6 +43,8 @@ def mkFreshFVarId : M FVarId :=
|
|||
return { name := (← mkFreshId) }
|
||||
|
||||
private partial def abstractLevelMVars (u : Level) : M Level := do
|
||||
if !(← get).abstractLevels then
|
||||
return u
|
||||
if !u.hasMVar then
|
||||
return u
|
||||
else
|
||||
|
|
@ -124,10 +127,13 @@ end AbstractMVars
|
|||
new fresh universe metavariables, and instantiate the `(m_i : A_i)` in the lambda-expression
|
||||
with new fresh metavariables.
|
||||
|
||||
If `levels := false`, then level metavariables are not abstracted.
|
||||
|
||||
Application: we use this method to cache the results of type class resolution. -/
|
||||
def abstractMVars (e : Expr) : MetaM AbstractMVarsResult := do
|
||||
def abstractMVars (e : Expr) (levels : Bool := true): MetaM AbstractMVarsResult := do
|
||||
let e ← instantiateMVars e
|
||||
let (e, s) := AbstractMVars.abstractExprMVars e { mctx := (← getMCtx), lctx := (← getLCtx), ngen := (← getNGen) }
|
||||
let (e, s) := AbstractMVars.abstractExprMVars e
|
||||
{ mctx := (← getMCtx), lctx := (← getLCtx), ngen := (← getNGen), abstractLevels := levels }
|
||||
setNGen s.ngen
|
||||
setMCtx s.mctx
|
||||
let e := s.lctx.mkLambda s.fvars e
|
||||
|
|
|
|||
|
|
@ -15,16 +15,24 @@ structure ElimAltInfo where
|
|||
numFields : Nat
|
||||
deriving Repr, Inhabited
|
||||
|
||||
/--
|
||||
Information about an eliminator as used by `induction` or `cases`.
|
||||
|
||||
Created from an expression by `getElimInfo`. This typically contains level metavariables that
|
||||
are instantiated as we go (e.g. in `addImplicitTargets`), so this is single use.
|
||||
-/
|
||||
structure ElimInfo where
|
||||
name : Name
|
||||
elimExpr : Expr
|
||||
elimType : Expr
|
||||
motivePos : Nat
|
||||
targetsPos : Array Nat := #[]
|
||||
altsInfo : Array ElimAltInfo := #[]
|
||||
deriving Repr, Inhabited
|
||||
|
||||
def getElimInfo (declName : Name) (baseDeclName? : Option Name := none) : MetaM ElimInfo := do
|
||||
let declInfo ← getConstInfo declName
|
||||
forallTelescopeReducing declInfo.type fun xs type => do
|
||||
def getElimExprInfo (elimExpr : Expr) (baseDeclName? : Option Name := none) : MetaM ElimInfo := do
|
||||
let elimType ← inferType elimExpr
|
||||
trace[Elab.induction] "eliminator {indentExpr elimExpr}\nhas type{indentExpr elimType}"
|
||||
forallTelescopeReducing elimType fun xs type => do
|
||||
let motive := type.getAppFn
|
||||
let targets := type.getAppArgs
|
||||
unless motive.isFVar && targets.all (·.isFVar) && targets.size > 0 do
|
||||
|
|
@ -36,10 +44,10 @@ def getElimInfo (declName : Name) (baseDeclName? : Option Name := none) : MetaM
|
|||
unless motiveResultType.isSort do
|
||||
throwError "motive result type must be a sort{indentExpr motiveType}"
|
||||
let some motivePos ← pure (xs.indexOf? motive) |
|
||||
throwError "unexpected eliminator type{indentExpr declInfo.type}"
|
||||
throwError "unexpected eliminator type{indentExpr elimType}"
|
||||
let targetsPos ← targets.mapM fun target => do
|
||||
match xs.indexOf? target with
|
||||
| none => throwError "unexpected eliminator type{indentExpr declInfo.type}"
|
||||
| none => throwError "unexpected eliminator type{indentExpr elimType}"
|
||||
| some targetPos => pure targetPos.val
|
||||
let mut altsInfo := #[]
|
||||
let env ← getEnv
|
||||
|
|
@ -55,55 +63,60 @@ def getElimInfo (declName : Name) (baseDeclName? : Option Name := none) : MetaM
|
|||
let altDeclName := base ++ name
|
||||
if env.contains altDeclName then some altDeclName else none
|
||||
altsInfo := altsInfo.push { name, declName?, numFields }
|
||||
pure { name := declName, motivePos, targetsPos, altsInfo }
|
||||
pure { elimExpr, elimType, motivePos, targetsPos, altsInfo }
|
||||
|
||||
def getElimInfo (elimName : Name) (baseDeclName? : Option Name := none) : MetaM ElimInfo := do
|
||||
getElimExprInfo (← mkConstWithFreshMVarLevels elimName) baseDeclName?
|
||||
|
||||
/--
|
||||
Eliminators/recursors may have implicit targets. For builtin recursors, all indices are implicit targets.
|
||||
Given an eliminator and the sequence of explicit targets, this methods returns a new sequence containing
|
||||
implicit and explicit targets.
|
||||
-/
|
||||
partial def addImplicitTargets (elimInfo : ElimInfo) (targets : Array Expr) : MetaM (Array Expr) :=
|
||||
withNewMCtxDepth do
|
||||
let f ← mkConstWithFreshMVarLevels elimInfo.name
|
||||
let targets ← collect (← inferType f) 0 0 #[]
|
||||
let targets ← targets.mapM instantiateMVars
|
||||
for target in targets do
|
||||
if (← hasAssignableMVar target) then
|
||||
throwError "failed to infer implicit target, it contains unresolved metavariables{indentExpr target}"
|
||||
return targets
|
||||
partial def addImplicitTargets (elimInfo : ElimInfo) (targets : Array Expr) : MetaM (Array Expr) := do
|
||||
let (implicitMVars, targets) ← collect elimInfo.elimType 0 0 #[] #[]
|
||||
for mvar in implicitMVars do
|
||||
unless ← mvar.isAssigned do
|
||||
let name := (←mvar.getDecl).userName
|
||||
if name.isAnonymous || name.hasMacroScopes then
|
||||
throwError "failed to infer implicit target"
|
||||
else
|
||||
throwError "failed to infer implicit target {(←mvar.getDecl).userName}"
|
||||
targets.mapM instantiateMVars
|
||||
where
|
||||
collect (type : Expr) (argIdx targetIdx : Nat) (targets' : Array Expr) : MetaM (Array Expr) := do
|
||||
collect (type : Expr) (argIdx targetIdx : Nat) (implicits : Array MVarId) (targets' : Array Expr) :
|
||||
MetaM (Array MVarId × Array Expr) := do
|
||||
match (← whnfD type) with
|
||||
| Expr.forallE _ d b bi =>
|
||||
| Expr.forallE n d b bi =>
|
||||
if elimInfo.targetsPos.contains argIdx then
|
||||
if bi.isExplicit then
|
||||
unless targetIdx < targets.size do
|
||||
throwError "insufficient number of targets for '{elimInfo.name}'"
|
||||
throwError "insufficient number of targets for '{elimInfo.elimExpr}'"
|
||||
let target := targets[targetIdx]!
|
||||
let targetType ← inferType target
|
||||
unless (← isDefEq d targetType) do
|
||||
throwError "target{indentExpr target}\n{← mkHasTypeButIsExpectedMsg targetType d}"
|
||||
collect (b.instantiate1 target) (argIdx+1) (targetIdx+1) (targets'.push target)
|
||||
collect (b.instantiate1 target) (argIdx+1) (targetIdx+1) implicits (targets'.push target)
|
||||
else
|
||||
let implicitTarget ← mkFreshExprMVar d
|
||||
collect (b.instantiate1 implicitTarget) (argIdx+1) targetIdx (targets'.push implicitTarget)
|
||||
let implicitTarget ← mkFreshExprMVar (type? := d) (userName := n)
|
||||
collect (b.instantiate1 implicitTarget) (argIdx+1) targetIdx (implicits.push implicitTarget.mvarId!) (targets'.push implicitTarget)
|
||||
else
|
||||
collect (b.instantiate1 (← mkFreshExprMVar d)) (argIdx+1) targetIdx targets'
|
||||
collect (b.instantiate1 (← mkFreshExprMVar d)) (argIdx+1) targetIdx implicits targets'
|
||||
| _ =>
|
||||
return targets'
|
||||
return (implicits, targets')
|
||||
|
||||
structure CustomEliminator where
|
||||
typeNames : Array Name
|
||||
elimInfo : ElimInfo
|
||||
elimName : Name -- NB: Do not store the ElimInfo, it can contain MVars
|
||||
deriving Inhabited, Repr
|
||||
|
||||
structure CustomEliminators where
|
||||
map : SMap (Array Name) ElimInfo := {}
|
||||
map : SMap (Array Name) Name := {}
|
||||
deriving Inhabited, Repr
|
||||
|
||||
def addCustomEliminatorEntry (es : CustomEliminators) (e : CustomEliminator) : CustomEliminators :=
|
||||
match es with
|
||||
| { map := map } => { map := map.insert e.typeNames e.elimInfo }
|
||||
| { map := map } => { map := map.insert e.typeNames e.elimName }
|
||||
|
||||
builtin_initialize customEliminatorExt : SimpleScopedEnvExtension CustomEliminator CustomEliminators ←
|
||||
registerSimpleScopedEnvExtension {
|
||||
|
|
@ -112,9 +125,9 @@ builtin_initialize customEliminatorExt : SimpleScopedEnvExtension CustomEliminat
|
|||
finalizeImport := fun { map := map } => { map := map.switch }
|
||||
}
|
||||
|
||||
def mkCustomEliminator (declName : Name) : MetaM CustomEliminator := do
|
||||
let info ← getConstInfo declName
|
||||
let elimInfo ← getElimInfo declName
|
||||
def mkCustomEliminator (elimName : Name) : MetaM CustomEliminator := do
|
||||
let elimInfo ← getElimInfo elimName
|
||||
let info ← getConstInfo elimName
|
||||
forallTelescopeReducing info.type fun xs _ => do
|
||||
let mut typeNames := #[]
|
||||
for i in [:elimInfo.targetsPos.size] do
|
||||
|
|
@ -134,7 +147,7 @@ def mkCustomEliminator (declName : Name) : MetaM CustomEliminator := do
|
|||
let xType ← inferType x
|
||||
let .const typeName .. := xType.getAppFn | throwError "unexpected eliminator target type{indentExpr xType}"
|
||||
typeNames := typeNames.push typeName
|
||||
return { typeNames, elimInfo }
|
||||
return { typeNames, elimName}
|
||||
|
||||
def addCustomEliminator (declName : Name) (attrKind : AttributeKind) : MetaM Unit := do
|
||||
let e ← mkCustomEliminator declName
|
||||
|
|
@ -151,7 +164,7 @@ builtin_initialize
|
|||
def getCustomEliminators : CoreM CustomEliminators := do
|
||||
return customEliminatorExt.getState (← getEnv)
|
||||
|
||||
def getCustomEliminator? (targets : Array Expr) : MetaM (Option ElimInfo) := do
|
||||
def getCustomEliminator? (targets : Array Expr) : MetaM (Option Name) := do
|
||||
let mut key := #[]
|
||||
for target in targets do
|
||||
let targetType := (← instantiateMVars (← inferType target)).headBeta
|
||||
|
|
|
|||
225
tests/lean/indUsingTerm.lean
Normal file
225
tests/lean/indUsingTerm.lean
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
namespace Ex0
|
||||
|
||||
-- NB: no parameter
|
||||
theorem elim_without_param {motive : Nat → Prop} (case1 : ∀ n, motive n) (n : Nat) : motive n :=
|
||||
case1 _
|
||||
|
||||
example (n : Nat) : n = n := by
|
||||
induction n using elim_without_param
|
||||
case case1 n => rfl
|
||||
|
||||
example (n : Nat) : n = n := by
|
||||
induction n using @elim_without_param
|
||||
case case1 n => rfl
|
||||
|
||||
example (n : Nat) : n = n := by
|
||||
induction n using (elim_without_param) -- Error: unexpected eliminator resulting type
|
||||
case case1 n => rfl
|
||||
|
||||
end Ex0
|
||||
|
||||
namespace Ex1
|
||||
|
||||
-- NB: p before motive
|
||||
theorem elim_with_param (p : Bool) {motive : Nat → Prop} (case1 : ∀ n, motive n) (n : Nat) : motive n :=
|
||||
if p then case1 _ else case1 _
|
||||
|
||||
example (n : Nat) : n = n := by
|
||||
induction n using elim_with_param
|
||||
case p => exact true -- uninstantiated parameters becomes goal
|
||||
case case1 n => rfl
|
||||
|
||||
example (n : Nat) : n = n := by
|
||||
induction n using @elim_with_param
|
||||
case p => exact true -- uninstantiated parameters becomes goal
|
||||
case case1 n => rfl
|
||||
|
||||
example (n : Nat) : n = n := by
|
||||
induction n using elim_with_param (p := true)
|
||||
case case1 n => rfl
|
||||
|
||||
example (n : Nat) : n = n := by
|
||||
induction n using elim_with_param true
|
||||
case case1 n => rfl
|
||||
|
||||
example (n : Nat) : n = n := by
|
||||
-- not really a useful idiom, but it better works:
|
||||
induction n using fun motive case1 n => @elim_with_param true motive case1 n
|
||||
case case1 n => rfl
|
||||
|
||||
example (n : Nat) : n = n := by
|
||||
-- NB: no renaming of cases this way?
|
||||
induction n using fun motive the_case n => @elim_with_param true motive the_case n
|
||||
case case1 n => rfl
|
||||
|
||||
end Ex1
|
||||
|
||||
namespace Ex2
|
||||
|
||||
-- NB: p after motive
|
||||
theorem elim_with_param {motive : Nat → Prop} (case1 : ∀ n, motive n) (n : Nat) (p : Bool) : motive n :=
|
||||
if p then case1 _ else case1 _
|
||||
|
||||
example (n : Nat) : n = n := by
|
||||
induction n using elim_with_param
|
||||
case p => exact true
|
||||
case case1 n => rfl
|
||||
|
||||
example (n : Nat) : n = n := by
|
||||
induction n using @elim_with_param
|
||||
case p => exact true
|
||||
case case1 n => rfl
|
||||
|
||||
example (n : Nat) : n = n := by
|
||||
induction n using elim_with_param (p := true)
|
||||
case case1 n => rfl
|
||||
|
||||
example (n : Nat) : n = n := by
|
||||
induction n using @elim_with_param (p := true)
|
||||
case case1 n => rfl
|
||||
|
||||
example (n : Nat) : n = n := by
|
||||
-- This renames the cases with unhelpful names
|
||||
induction n using elim_with_param _ _ true
|
||||
case x_1 n => rfl
|
||||
|
||||
example (n : Nat) : n = n := by
|
||||
-- We can put in custom names
|
||||
induction n using elim_with_param ?case2 _ true
|
||||
case case2 n => rfl
|
||||
|
||||
example (n : Nat) : n = n := by
|
||||
-- not really a useful idiom, but it works:
|
||||
induction n using fun motive case1 n => @elim_with_param motive case1 n true
|
||||
case case1 n => rfl
|
||||
|
||||
end Ex2
|
||||
|
||||
namespace Ex3
|
||||
|
||||
-- Mutual induction, multiple motives
|
||||
|
||||
mutual
|
||||
inductive A : Type u where | mkA : B → A | A : A
|
||||
inductive B : Type u where | mkB : A → B
|
||||
end
|
||||
|
||||
-- NB: A.rec is configured as elab_as_elim,
|
||||
-- but in `using` it should not matter
|
||||
|
||||
-- For comparision, a copy without that attribute
|
||||
noncomputable def A_rec := @A.rec
|
||||
|
||||
set_option linter.unusedVariables false
|
||||
|
||||
example (a : A) : True := by
|
||||
-- motive_2 becomes a mvar
|
||||
induction a using A.rec
|
||||
case mkA b IH =>
|
||||
refine True.rec ?_ IH -- A hack to instantiate the motive_2 mvar
|
||||
exact trivial
|
||||
case A => exact trivial
|
||||
case mkB b IH => show True; exact trivial
|
||||
|
||||
example (a : A) : True := by
|
||||
-- motive_2 becomes a mvar
|
||||
induction a using A_rec
|
||||
case mkA b IH =>
|
||||
refine True.rec ?_ IH -- A hack to instantiate the motive_2 mvar
|
||||
exact trivial
|
||||
case A => exact trivial
|
||||
case mkB b IH => show True; exact trivial -- Error: type mismatch
|
||||
|
||||
example (a : A) : True := by
|
||||
-- motive_2 becomes a mvar
|
||||
induction a using @A.rec
|
||||
case mkA b IH =>
|
||||
refine True.rec ?_ IH -- A hack to instantiate the motive_2 mvar
|
||||
exact trivial
|
||||
case A => exact trivial
|
||||
case mkB b IH => show True; exact trivial
|
||||
|
||||
example (a : A) : True := by
|
||||
-- motive_2 becomes a mvar
|
||||
induction a using @A_rec
|
||||
case mkA b IH =>
|
||||
refine True.rec ?_ IH -- A hack to instantiate the motive_2 mvar
|
||||
exact trivial
|
||||
case A => exact trivial
|
||||
case mkB b IH => show True; exact trivial
|
||||
|
||||
example (a : A) : True := by
|
||||
induction a using fun motive_1 => @A.rec motive_1 (motive_2 := fun b => True)
|
||||
case mkA b IH => exact trivial
|
||||
case A => exact trivial
|
||||
case mkB b IH => exact trivial
|
||||
|
||||
example (a : A) : True := by
|
||||
induction a using fun motive_1 => @A_rec motive_1 (motive_2 := fun b => True)
|
||||
case mkA b IH => exact trivial
|
||||
case A => exact trivial
|
||||
case mkB b IH => exact trivial
|
||||
|
||||
example (a : A) : True := by
|
||||
induction a using @A.rec (motive_2 := fun b => True)
|
||||
case mkA b IH => exact trivial
|
||||
case A => exact trivial
|
||||
case mkB b IH => exact trivial
|
||||
|
||||
example (a : A) : True := by
|
||||
induction a using @A_rec (motive_2 := fun b => True)
|
||||
case mkA b IH => exact trivial
|
||||
case A => exact trivial
|
||||
case mkB b IH => exact trivial
|
||||
|
||||
example (a : A) : True := by
|
||||
-- A.rec is marked elab_as_elim, and normally elaborating
|
||||
-- #check A.rec (motive_2 := fun b => True)
|
||||
-- fails with
|
||||
-- > failed to elaborate eliminator, expected type is not available
|
||||
-- so we elaborate with heedElabAsElim := false
|
||||
induction a using A.rec (motive_2 := fun b => True)
|
||||
case mkA b IH => exact trivial
|
||||
case A => exact trivial
|
||||
case mkB b IH => exact trivial
|
||||
|
||||
example (a : A) : True := by
|
||||
induction a using A_rec (motive_2 := fun b => True)
|
||||
case mkA b IH => exact trivial
|
||||
case A => exact trivial
|
||||
case mkB b IH => exact trivial
|
||||
|
||||
end Ex3
|
||||
|
||||
namespace Ex4
|
||||
|
||||
-- We can use parameters as elaborators
|
||||
|
||||
set_option linter.unusedVariables false in
|
||||
example
|
||||
(α : Type u)
|
||||
(ela : ∀ {motive : α → Prop} (case1 : ∀ (x : α), motive x) (x : α), motive x)
|
||||
(x : α)
|
||||
: x = x := by
|
||||
induction x using ela
|
||||
case case1 x => rfl
|
||||
|
||||
end Ex4
|
||||
|
||||
namespace Ex5
|
||||
|
||||
-- Multiple motives, different number of extra assumptions
|
||||
|
||||
mutual
|
||||
inductive A : Type u where | mkA : B → A | A : A
|
||||
inductive B : Type u where | mkB : A → B
|
||||
end
|
||||
|
||||
set_option linter.unusedVariables false in
|
||||
example (a : A) : True := by
|
||||
induction a using A.rec (motive_2 := fun b => (heq : b = b) -> True)
|
||||
case mkA b IH => exact trivial
|
||||
case A => exact trivial
|
||||
case mkB b IH h => exact trivial
|
||||
|
||||
end Ex5
|
||||
0
tests/lean/indUsingTerm.lean.expected.out
Normal file
0
tests/lean/indUsingTerm.lean.expected.out
Normal file
|
|
@ -28,3 +28,16 @@ example (n : Nat) (m : Fin n) : n ≤ m := by
|
|||
case case1 => sorry
|
||||
|
||||
end Ex3
|
||||
|
||||
namespace Ex4
|
||||
|
||||
-- anonymous implicit target
|
||||
|
||||
theorem elim_with_implicit_target (motive : Bool → Nat → Prop)
|
||||
(case1 : ∀ m k, motive m k) {_ : Bool} (m : Nat) : motive ‹Bool› m := case1 _ _
|
||||
|
||||
example (n m : Nat) : n ≤ m := by
|
||||
induction m using elim_with_implicit_target
|
||||
case case1 => sorry
|
||||
|
||||
end Ex4
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
indimpltarget.lean:6:2-6:45: error: failed to infer implicit target, it contains unresolved metavariables
|
||||
?m
|
||||
indimpltarget.lean:16:2-16:45: error: failed to infer implicit target, it contains unresolved metavariables
|
||||
?m
|
||||
indimpltarget.lean:6:2-6:45: error: failed to infer implicit target m
|
||||
indimpltarget.lean:16:2-16:45: error: failed to infer implicit target n
|
||||
indimpltarget.lean:26:0-26:7: warning: declaration uses 'sorry'
|
||||
indimpltarget.lean:40:2-40:45: error: failed to infer implicit target
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ inductionErrors.lean:12:12-12:27: error: unsolved goals
|
|||
case upper.h
|
||||
q d : Nat
|
||||
⊢ q + Nat.succ d > q
|
||||
inductionErrors.lean:16:19-16:26: error: unknown constant 'elimEx2'
|
||||
inductionErrors.lean:16:19-16:26: error: unknown identifier 'elimEx2'
|
||||
inductionErrors.lean:22:2-25:45: error: insufficient number of targets for 'elimEx'
|
||||
inductionErrors.lean:28:16-28:23: error: unexpected eliminator resulting type
|
||||
Nat
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue