This PR marks the context argument of `ReaderT` as borrowed, causing a wide spread of useful borrow annotations throughout the entire meta stack which reduces RC pressure. This introduces a crucial new behavior: When modifying `ReaderT` context, e.g. through `withReader` this will almost always cause an allocation. Given that the `ReaderT` context is frequently used in a non-linear fashion anyways we think this is an acceptable behavior.
1144 lines
47 KiB
Text
1144 lines
47 KiB
Text
/-
|
||
Copyright (c) 2019 Microsoft Corporation. All rights reserved.
|
||
Released under Apache 2.0 license as described in the file LICENSE.
|
||
Authors: Leonardo de Moura
|
||
-/
|
||
module
|
||
|
||
prelude
|
||
public import Lean.Structure
|
||
public import Lean.Util.Recognizers
|
||
public import Lean.Util.SafeExponentiation
|
||
public import Lean.Meta.GetUnfoldableConst
|
||
public import Lean.Meta.CtorRecognizer
|
||
public import Lean.Meta.Match.MatcherInfo
|
||
public import Lean.Meta.Match.MatchPatternAttr
|
||
public import Lean.Meta.Transform
|
||
import Init.Data.Range.Polymorphic.Iterators
|
||
|
||
public section
|
||
|
||
namespace Lean.Expr
|
||
|
||
def toCtorIfLit : Expr → MetaM Expr
|
||
| .lit (.natVal v) =>
|
||
if v == 0 then return mkConst ``Nat.zero
|
||
else return mkApp (mkConst ``Nat.succ) (mkRawNatLit (v-1))
|
||
| .lit (.strVal v) =>
|
||
Lean.Meta.whnf (mkApp (mkConst ``String.ofList) (toExpr v.toList))
|
||
| e => return e
|
||
|
||
end Lean.Expr
|
||
|
||
namespace Lean.Meta
|
||
|
||
-- ===========================
|
||
/-! # Smart unfolding support -/
|
||
-- ===========================
|
||
|
||
set_option compiler.ignoreBorrowAnnotation true in
|
||
/--
|
||
Forward declaration. It is defined in the module `src/Lean/Elab/PreDefinition/Structural/Eqns.lean`.
|
||
It is possible to avoid this hack if we move `Structural.EqnInfo` and `Structural.eqnInfoExt`
|
||
to this module.
|
||
-/
|
||
@[extern "lean_get_structural_rec_arg_pos"]
|
||
opaque getStructuralRecArgPos? (declName : Name) : CoreM (Option Nat)
|
||
|
||
def smartUnfoldingSuffix := "_sunfold"
|
||
|
||
@[inline] def mkSmartUnfoldingNameFor (declName : Name) : Name :=
|
||
Name.mkStr declName smartUnfoldingSuffix
|
||
|
||
def hasSmartUnfoldingDecl (env : Environment) (declName : Name) : Bool :=
|
||
env.contains (mkSmartUnfoldingNameFor declName)
|
||
|
||
register_builtin_option smartUnfolding : Bool := {
|
||
defValue := true
|
||
descr := "when computing weak head normal form, use auxiliary definition created for functions defined by structural recursion"
|
||
}
|
||
|
||
/-- Add auxiliary annotation to indicate the `match`-expression `e` must be reduced when performing smart unfolding. -/
|
||
def markSmartUnfoldingMatch (e : Expr) : Expr :=
|
||
mkAnnotation `sunfoldMatch e
|
||
|
||
def smartUnfoldingMatch? (e : Expr) : Option Expr :=
|
||
annotation? `sunfoldMatch e
|
||
|
||
/-- Add auxiliary annotation to indicate expression `e` (a `match` alternative rhs) was successfully reduced by smart unfolding. -/
|
||
def markSmartUnfoldingMatchAlt (e : Expr) : Expr :=
|
||
mkAnnotation `sunfoldMatchAlt e
|
||
|
||
def smartUnfoldingMatchAlt? (e : Expr) : Option Expr :=
|
||
annotation? `sunfoldMatchAlt e
|
||
|
||
-- ===========================
|
||
/-! # Helper methods -/
|
||
-- ===========================
|
||
|
||
def isAuxDef (constName : Name) : MetaM Bool := do
|
||
let env ← getEnv
|
||
return isAuxRecursor env constName
|
||
|
||
/--
|
||
Retrieves `ConstInfo` for `declName`.
|
||
Remark: if `ignoreTransparency = false`, then `getUnfoldableConst?` is used.
|
||
For example, if `ignoreTransparency = false` and `transparencyMode = .reducible` and `declName` is not reducible,
|
||
then the result is `none`.
|
||
-/
|
||
private def getConstInfo? (declName : Name) (ignoreTransparency : Bool) : MetaM (Option ConstantInfo) := do
|
||
if ignoreTransparency then
|
||
return (← getEnv).find? declName
|
||
else
|
||
getUnfoldableConst? declName
|
||
|
||
/--
|
||
Similar to `getConstInfo?` but using `getUnfoldableConstNoEx?`.
|
||
-/
|
||
private def getConstInfoNoEx? (declName : Name) (ignoreTransparency : Bool) : MetaM (Option ConstantInfo) := do
|
||
if ignoreTransparency then
|
||
return (← getEnv).find? declName
|
||
else
|
||
getUnfoldableConstNoEx? declName
|
||
|
||
/--
|
||
If `e` is of the form `Expr.const declName us`, executes `k info us` if
|
||
- `declName` is in the `Environment` and (is unfoldable or `ignoreTransparency = true`)
|
||
- `info` is the `ConstantInfo` associated with `declName`.
|
||
|
||
Otherwise executes `failK`.
|
||
-/
|
||
@[inline] private def matchConstAux {α} (e : Expr) (failK : Unit → MetaM α) (k : ConstantInfo → List Level → MetaM α) (ignoreTransparency := false) : MetaM α := do
|
||
let .const declName lvls := e
|
||
| failK ()
|
||
let some cinfo ← getConstInfo? declName ignoreTransparency
|
||
| failK ()
|
||
k cinfo lvls
|
||
|
||
-- ===========================
|
||
/-! # Helper functions for reducing recursors -/
|
||
-- ===========================
|
||
|
||
private def getFirstCtor (d : Name) : MetaM (Option Name) := do
|
||
let some (ConstantInfo.inductInfo { ctors := ctor::_, ..}) := (← getEnv).find? d |
|
||
return none
|
||
return some ctor
|
||
|
||
private def mkNullaryCtor (type : Expr) (nparams : Nat) : MetaM (Option Expr) := do
|
||
let .const d lvls := type.getAppFn
|
||
| return none
|
||
let (some ctor) ← getFirstCtor d | pure none
|
||
return mkAppN (mkConst ctor lvls) (type.getAppArgs.shrink nparams)
|
||
|
||
private def getRecRuleFor (recVal : RecursorVal) (major : Expr) : Option RecursorRule :=
|
||
match major.getAppFn with
|
||
| .const fn _ => recVal.rules.find? fun r => r.ctor == fn
|
||
| _ => none
|
||
|
||
private def toCtorWhenK (recVal : RecursorVal) (major : Expr) : MetaM Expr := do
|
||
let majorType ← inferType major
|
||
let majorType ← instantiateMVars (← whnf majorType)
|
||
let majorTypeI := majorType.getAppFn
|
||
if !majorTypeI.isConstOf recVal.getMajorInduct then
|
||
return major
|
||
else if majorType.hasExprMVar && majorType.getAppArgs[recVal.numParams...*].any Expr.hasExprMVar then
|
||
return major
|
||
else do
|
||
let (some newCtorApp) ← mkNullaryCtor majorType recVal.numParams | pure major
|
||
let newType ← inferType newCtorApp
|
||
/- TODO: check whether changing reducibility to default hurts performance here.
|
||
We do that to make sure auxiliary `Eq.rec` introduced by the `match`-compiler
|
||
are reduced even when `TransparencyMode.reducible` (like in `simp`).
|
||
|
||
We use `withNewMCtxDepth` to make sure metavariables at `majorType` are not assigned.
|
||
For example, given `major : Eq ?x y`, we don't want to apply K by assigning `?x := y`.
|
||
-/
|
||
if (← withAtLeastTransparency TransparencyMode.default <| withNewMCtxDepth <| isDefEq majorType newType) then
|
||
return newCtorApp
|
||
else
|
||
return major
|
||
|
||
/--
|
||
Create the `i`th projection `major`. It tries to use the auto-generated projection functions if available. Otherwise falls back
|
||
to `Expr.proj`.
|
||
-/
|
||
def mkProjFn (ctorVal : ConstructorVal) (us : List Level) (params : Array Expr) (i : Nat) (major : Expr) : CoreM Expr := do
|
||
match getStructureInfo? (← getEnv) ctorVal.induct with
|
||
| none => return mkProj ctorVal.induct i major
|
||
| some info => match info.getProjFn? i with
|
||
| none => return mkProj ctorVal.induct i major
|
||
| some projFn => return mkApp (mkAppN (mkConst projFn us) params) major
|
||
|
||
/--
|
||
If `major` is not a constructor application, and its type is a non-recursive structure `C ...`, then return `C.mk major.1 ... major.n`
|
||
|
||
\pre `inductName` is `C`.
|
||
|
||
If `Meta.Config.etaStruct` is `false` or the condition above does not hold, this method just returns `major`. -/
|
||
private def toCtorWhenStructure (inductName : Name) (major : Expr) : MetaM Expr := do
|
||
unless (← useEtaStruct inductName) do
|
||
return major
|
||
let env ← getEnv
|
||
if !isNonRecStructure env inductName then
|
||
return major
|
||
else if let some _ ← isConstructorApp? major then
|
||
return major
|
||
else
|
||
let majorType ← inferType major
|
||
let majorType ← instantiateMVars (← whnf majorType)
|
||
let majorTypeI := majorType.getAppFn
|
||
if !majorTypeI.isConstOf inductName then
|
||
return major
|
||
match majorType.getAppFn with
|
||
| Expr.const d us =>
|
||
if (← whnfD (← inferType majorType)) == mkSort Level.zero then
|
||
return major -- We do not perform eta for propositions, see implementation in the kernel
|
||
else
|
||
let some ctorName ← getFirstCtor d | pure major
|
||
let ctorInfo ← getConstInfoCtor ctorName
|
||
let params := majorType.getAppArgs.shrink ctorInfo.numParams
|
||
let mut result := mkAppN (mkConst ctorName us) params
|
||
for i in *...ctorInfo.numFields do
|
||
result := mkApp result (← mkProjFn ctorInfo us params i major)
|
||
return result
|
||
| _ => return major
|
||
|
||
|
||
-- Helper predicate that returns `true` for inductive predicates used to define functions by well-founded recursion.
|
||
private def isWFRec (declName : Name) : Bool :=
|
||
declName == ``Acc.rec || declName == ``WellFounded.rec
|
||
|
||
/--
|
||
Helper method for `reduceRec`.
|
||
We use it to ensure we don't expose `Nat.add` when reducing `Nat.rec`.
|
||
We we use the following trick, if `e` can be expressed as an offset `(a, k)` with `k > 0`,
|
||
we create a new expression `Nat.succ e'` where `e'` is `a` for `k = 1`, or `a + (k-1)` for `k > 1`.
|
||
See issue #3022
|
||
-/
|
||
private def cleanupNatOffsetMajor (e : Expr) : MetaM Expr := do
|
||
let some (e, k) ← isOffset? e | return e
|
||
if k = 0 then
|
||
return e
|
||
else if k = 1 then
|
||
return mkNatSucc e
|
||
else
|
||
return mkNatSucc (mkNatAdd e (toExpr (k - 1)))
|
||
|
||
/-- Auxiliary function for reducing recursor applications. -/
|
||
private def reduceRec (recVal : RecursorVal) (recLvls : List Level) (recArgs : Array Expr) (failK : Unit → MetaM α) (successK : Expr → MetaM α) : MetaM α :=
|
||
let majorIdx := recVal.getMajorIdx
|
||
if h : majorIdx < recArgs.size then do
|
||
let major := recArgs[majorIdx]
|
||
let mut major ← if isWFRec recVal.name && (← getTransparency) == .default then
|
||
-- If recursor is `Acc.rec` or `WellFounded.rec` and transparency is default,
|
||
-- then we bump transparency to .all to make sure we can unfold defs defined by WellFounded recursion.
|
||
-- We use this trick because we abstract nested proofs occurring in definitions.
|
||
-- Alternative design: do not abstract nested proofs used to justify well-founded recursion.
|
||
withTransparency .all <| whnf major
|
||
else
|
||
whnf major
|
||
if recVal.k then
|
||
major ← toCtorWhenK recVal major
|
||
major ← major.toCtorIfLit
|
||
major ← cleanupNatOffsetMajor major
|
||
major ← toCtorWhenStructure recVal.getMajorInduct major
|
||
match getRecRuleFor recVal major with
|
||
| some rule =>
|
||
let majorArgs := major.getAppArgs
|
||
if recLvls.length != recVal.levelParams.length then
|
||
failK ()
|
||
else
|
||
let rhs := rule.rhs.instantiateLevelParams recVal.levelParams recLvls
|
||
-- Apply parameters, motives and minor premises from recursor application.
|
||
let rhs := mkAppRange rhs 0 (recVal.numParams+recVal.numMotives+recVal.numMinors) recArgs
|
||
/- The number of parameters in the constructor is not necessarily
|
||
equal to the number of parameters in the recursor when we have
|
||
nested inductive types. -/
|
||
let nparams := majorArgs.size - rule.nfields
|
||
let rhs := mkAppRange rhs nparams majorArgs.size majorArgs
|
||
let rhs := mkAppRange rhs (majorIdx + 1) recArgs.size recArgs
|
||
successK rhs
|
||
| none => failK ()
|
||
else
|
||
failK ()
|
||
|
||
-- ===========================
|
||
/-! # Helper functions for reducing Quot.lift and Quot.ind -/
|
||
-- ===========================
|
||
|
||
/-- Auxiliary function for reducing `Quot.lift` and `Quot.ind` applications. -/
|
||
private def reduceQuotRec (recVal : QuotVal) (recArgs : Array Expr) (failK : Unit → MetaM α) (successK : Expr → MetaM α) : MetaM α :=
|
||
let process (majorPos argPos : Nat) : MetaM α :=
|
||
if h : majorPos < recArgs.size then do
|
||
let major := recArgs[majorPos]
|
||
let major ← whnf major
|
||
match major with
|
||
| Expr.app (Expr.app (Expr.app (Expr.const majorFn _) _) _) majorArg => do
|
||
let some (ConstantInfo.quotInfo { kind := QuotKind.ctor, .. }) := (← getEnv).find? majorFn | failK ()
|
||
let f := recArgs[argPos]!
|
||
let r := mkApp f majorArg
|
||
let recArity := majorPos + 1
|
||
successK <| mkAppRange r recArity recArgs.size recArgs
|
||
| _ => failK ()
|
||
else
|
||
failK ()
|
||
match recVal.kind with
|
||
| QuotKind.lift => process 5 3
|
||
| QuotKind.ind => process 4 3
|
||
| _ => failK ()
|
||
|
||
-- ===========================
|
||
/-! # Helper function for extracting "stuck term" -/
|
||
-- ===========================
|
||
|
||
mutual
|
||
private partial def isRecStuck? (recVal : RecursorVal) (recArgs : Array Expr) : MetaM (Option MVarId) :=
|
||
if recVal.k then
|
||
-- TODO: improve this case
|
||
return none
|
||
else do
|
||
let majorIdx := recVal.getMajorIdx
|
||
if h : majorIdx < recArgs.size then do
|
||
let major := recArgs[majorIdx]
|
||
let major ← whnf major
|
||
getStuckMVar? major
|
||
else
|
||
return none
|
||
|
||
private partial def isQuotRecStuck? (recVal : QuotVal) (recArgs : Array Expr) : MetaM (Option MVarId) :=
|
||
let process? (majorPos : Nat) : MetaM (Option MVarId) :=
|
||
if h : majorPos < recArgs.size then do
|
||
let major := recArgs[majorPos]
|
||
let major ← whnf major
|
||
getStuckMVar? major
|
||
else
|
||
return none
|
||
match recVal.kind with
|
||
| QuotKind.lift => process? 5
|
||
| QuotKind.ind => process? 4
|
||
| _ => return none
|
||
|
||
/-- Return `some (Expr.mvar mvarId)` if metavariable `mvarId` is blocking reduction. -/
|
||
partial def getStuckMVar? (e : Expr) : MetaM (Option MVarId) := do
|
||
match e with
|
||
| .mdata _ e => getStuckMVar? e
|
||
| .proj _ _ e => getStuckMVar? (← whnf e)
|
||
| .mvar .. =>
|
||
let e ← instantiateMVars e
|
||
match e with
|
||
| .mvar mvarId => return some mvarId
|
||
| _ => getStuckMVar? e
|
||
| .app f .. =>
|
||
let f := f.getAppFn
|
||
match f with
|
||
| .mvar .. =>
|
||
let e ← instantiateMVars e
|
||
match e.getAppFn with
|
||
| .mvar mvarId => return some mvarId
|
||
| _ => getStuckMVar? e
|
||
| .const fName _ =>
|
||
match (← getEnv).find? fName with
|
||
| some <| .recInfo recVal => isRecStuck? recVal e.getAppArgs
|
||
| some <| .quotInfo recVal => isQuotRecStuck? recVal e.getAppArgs
|
||
| _ =>
|
||
unless e.hasExprMVar do return none
|
||
let args := e.getAppArgs
|
||
-- Projection function support
|
||
if let some projInfo ← getProjectionFnInfo? fName then
|
||
-- This branch is relevant if `e` is a type class projection that is stuck because the instance has not been synthesized yet.
|
||
unless projInfo.fromClass do return none
|
||
-- First check whether `e`s instance is stuck.
|
||
if let some major := args[projInfo.numParams]? then
|
||
if let some mvarId ← getStuckMVar? (← whnf major) then
|
||
return mvarId
|
||
/-
|
||
Then, recurse on the explicit arguments
|
||
We want to detect the stuck instance in terms such as
|
||
`HAdd.hAdd Nat Nat Nat (instHAdd Nat instAddNat) n (OfNat.ofNat Nat 2 ?m)`
|
||
See issue https://github.com/leanprover/lean4/issues/1408 for an example where this is needed.
|
||
-/
|
||
let info ← getFunInfo f
|
||
for pinfo in info.paramInfo, arg in args do
|
||
if pinfo.isExplicit then
|
||
if let some mvarId ← getStuckMVar? arg then
|
||
return some mvarId
|
||
return none
|
||
-- Auxiliary parent projections created for diamond inheritance (not registered as projections).
|
||
else if let some auxInfo ← getAuxParentProjectionInfo? fName then
|
||
unless auxInfo.fromClass do return none
|
||
if let some major := args[auxInfo.numParams]? then
|
||
if let some mvarId ← getStuckMVar? (← whnf major) then
|
||
return mvarId
|
||
return none
|
||
else
|
||
return none
|
||
| .proj _ _ e => getStuckMVar? (← whnf e)
|
||
| _ => return none
|
||
| _ => return none
|
||
end
|
||
|
||
-- ===========================
|
||
/-! # Weak Head Normal Form auxiliary combinators -/
|
||
-- ===========================
|
||
|
||
/-- Auxiliary combinator for handling easy WHNF cases. It takes a function for handling the "hard" cases as an argument -/
|
||
@[specialize] partial def whnfEasyCases (e : Expr) (k : Expr → MetaM Expr) : MetaM Expr := do
|
||
match e with
|
||
| .forallE .. => return e
|
||
| .lam .. => return e
|
||
| .sort .. => return e
|
||
| .lit .. => return e
|
||
| .bvar .. => panic! "loose bvar in expression"
|
||
| .letE .. => k e
|
||
| .const .. => k e
|
||
| .app .. => k e
|
||
| .proj .. => k e
|
||
| .mdata _ e => whnfEasyCases e k
|
||
| .fvar fvarId =>
|
||
let decl ← fvarId.getDecl
|
||
match decl with
|
||
| .ldecl (value := v) (nondep := false) .. =>
|
||
-- Let-declarations marked as implementation detail should always be unfolded
|
||
-- We initially added this feature for `simp`, and added it here for consistency.
|
||
let cfg ← getConfig
|
||
if !decl.isImplementationDetail && !cfg.zetaDelta then
|
||
if !(← read).zetaDeltaSet.contains fvarId then
|
||
return e
|
||
if (← read).trackZetaDelta then
|
||
addZetaDeltaFVarId fvarId
|
||
whnfEasyCases v k
|
||
| _ => return e
|
||
| .mvar mvarId =>
|
||
match (← getExprMVarAssignment? mvarId) with
|
||
| some v => whnfEasyCases v k
|
||
| none => return e
|
||
|
||
@[specialize] private def deltaDefinition (c : ConstantInfo) (lvls : List Level)
|
||
(failK : Unit → MetaM α) (successK : Expr → MetaM α) : MetaM α := do
|
||
if c.levelParams.length != lvls.length then
|
||
failK ()
|
||
else
|
||
successK (← instantiateValueLevelParams c lvls)
|
||
|
||
@[specialize] private def deltaBetaDefinition (c : ConstantInfo) (lvls : List Level) (revArgs : Array Expr)
|
||
(failK : Unit → MetaM α) (successK : Expr → MetaM α) (preserveMData := false) : MetaM α := do
|
||
if c.levelParams.length != lvls.length then
|
||
failK ()
|
||
else
|
||
let val ← instantiateValueLevelParams c lvls
|
||
let val := val.betaRev revArgs (preserveMData := preserveMData)
|
||
successK val
|
||
|
||
inductive ReduceMatcherResult where
|
||
| reduced (val : Expr)
|
||
| stuck (val : Expr)
|
||
| notMatcher
|
||
| partialApp
|
||
|
||
/-!
|
||
The "match" compiler uses dependent if-then-else `dite` and other auxiliary declarations to compile match-expressions such as
|
||
```
|
||
match v with
|
||
| 'a' => 1
|
||
| 'b' => 2
|
||
| _ => 3
|
||
```
|
||
because it is more efficient than using `casesOn` recursors.
|
||
The method `reduceMatcher?` fails if these auxiliary definitions cannot be unfolded in the current
|
||
transparency setting. This is problematic because tactics such as `simp` use `TransparencyMode.reducible`, and
|
||
most users assume that expressions such as
|
||
```
|
||
match 0 with
|
||
| 0 => 1
|
||
| 100 => 2
|
||
| _ => 3
|
||
```
|
||
should reduce in any transparency mode.
|
||
|
||
Thus, if the transparency mode is `.reducible` or `.instances`, we first
|
||
eagerly unfold `dite` constants used in the auxiliary match-declaration, and then
|
||
use a custom `canUnfoldAtMatcher` predicate for `whnfMatcher`.
|
||
|
||
Remark: we used to include `dite` (and `ite`) as auxiliary declarations to unfold at
|
||
`canUnfoldAtMatcher`, but this is problematic because the `dite`/`ite` may occur in the
|
||
discriminant. See issue #5388.
|
||
|
||
This solution is not very modular because modifications at the `match` compiler require changes here.
|
||
We claim this is defensible because it is reducing the auxiliary declaration defined by the `match` compiler.
|
||
|
||
Remark: if the eager unfolding is problematic, we may cache the result.
|
||
We may also consider not using `dite` in the `match`-compiler and use `Decidable.casesOn`, but it will require changes
|
||
in how we generate equation lemmas.
|
||
|
||
Alternative solution: tactics that use `TransparencyMode.reducible` should rely on the equations we generated for match-expressions.
|
||
This solution is also not perfect because the match-expression above will not reduce during type checking when we are not using
|
||
`TransparencyMode.default` or `TransparencyMode.all`.
|
||
-/
|
||
|
||
/--
|
||
Eagerly unfold `dite` constants in `e`. This is an auxiliary function used to reduce match expressions.
|
||
See comment above.
|
||
-/
|
||
private def unfoldNestedDIte (e : Expr) : CoreM Expr := do
|
||
if e.find? (fun e => e.isAppOf ``dite) matches some _ then
|
||
Core.transform e fun e => do
|
||
if let .const ``dite us := e then
|
||
let constInfo ← getConstInfo ``dite
|
||
let e ← instantiateValueLevelParams constInfo us
|
||
return .done e
|
||
else
|
||
return .continue
|
||
else
|
||
return e
|
||
|
||
/--
|
||
Auxiliary predicate for `whnfMatcher`.
|
||
See comment above.
|
||
-/
|
||
def canUnfoldAtMatcher (cfg : Config) (info : ConstantInfo) : CoreM Bool := do
|
||
match cfg.transparency with
|
||
| .all => return true
|
||
| .default => return !(← isIrreducible info.name)
|
||
| _ =>
|
||
let status ← getReducibilityStatus info.name
|
||
if status matches .reducible | .implicitReducible then
|
||
return true
|
||
else if hasMatchPatternAttribute (← getEnv) info.name then
|
||
return true
|
||
else
|
||
return info.name == ``decEq
|
||
|| info.name == ``Nat.decEq
|
||
|| info.name == ``Char.ofNat || info.name == ``Char.ofNatAux
|
||
|| info.name == ``String.decEq || info.name == ``List.hasDecEq
|
||
|| info.name == ``Fin.ofNat
|
||
|| info.name == ``Fin.ofNat -- It is used to define `BitVec` literals
|
||
|| info.name == ``UInt8.ofNat || info.name == ``UInt8.decEq
|
||
|| info.name == ``UInt16.ofNat || info.name == ``UInt16.decEq
|
||
|| info.name == ``UInt32.ofNat || info.name == ``UInt32.decEq
|
||
|| info.name == ``UInt64.ofNat || info.name == ``UInt64.decEq
|
||
/- Remark: we need to unfold the following two definitions because they are used for `Fin`, and
|
||
lazy unfolding at `isDefEq` does not unfold projections. -/
|
||
|| info.name == ``HMod.hMod || info.name == ``Mod.mod
|
||
|
||
private def whnfMatcher (e : Expr) : MetaM Expr := do
|
||
/- When reducing `match` expressions, if the reducibility setting is at `TransparencyMode.reducible`,
|
||
we increase it to `TransparencyMode.instances`. We use the `TransparencyMode.reducible` in many places (e.g., `simp`),
|
||
and this setting prevents us from reducing `match` expressions where the discriminants are terms such as `OfNat.ofNat α n inst`.
|
||
For example, `simp [Int.div]` will not unfold the application `Int.div 2 1` occurring in the target.
|
||
|
||
TODO: consider other solutions; investigate whether the solution above produces counterintuitive behavior. -/
|
||
if (← getTransparency) matches .instances | .reducible then
|
||
-- Also unfold some default-reducible constants; see `canUnfoldAtMatcher`
|
||
withTransparency .instances <| withCanUnfoldPred canUnfoldAtMatcher do
|
||
whnf e
|
||
else
|
||
-- Do NOT use `canUnfoldAtMatcher` here as it does not affect all/default reducibility and inhibits caching (#2564).
|
||
-- In the future, we want to work on better reduction strategies that do not require caching.
|
||
whnf e
|
||
|
||
def reduceMatcher? (e : Expr) : MetaM ReduceMatcherResult := do
|
||
let .const declName declLevels := e.getAppFn
|
||
| return .notMatcher
|
||
let some info ← getMatcherInfo? declName
|
||
| return .notMatcher
|
||
let args := e.getAppArgs
|
||
let prefixSz := info.numParams + 1 + info.numDiscrs
|
||
if args.size < prefixSz + info.numAlts then
|
||
return ReduceMatcherResult.partialApp
|
||
let constInfo ← getConstInfo declName
|
||
let mut f ← instantiateValueLevelParams constInfo declLevels
|
||
if (← getTransparency) matches .instances | .reducible then
|
||
f ← unfoldNestedDIte f
|
||
let auxApp := mkAppN f args[*...prefixSz]
|
||
let auxAppType ← inferType auxApp
|
||
forallBoundedTelescope auxAppType info.numAlts fun hs _ => do
|
||
let auxApp ← whnfMatcher (mkAppN auxApp hs)
|
||
let auxAppFn := auxApp.getAppFn
|
||
let mut i := prefixSz
|
||
for h in hs do
|
||
if auxAppFn == h then
|
||
let result := mkAppN args[i]! auxApp.getAppArgs
|
||
let result := mkAppN result args[(prefixSz + info.numAlts)...args.size]
|
||
return ReduceMatcherResult.reduced result.headBeta
|
||
i := i + 1
|
||
return ReduceMatcherResult.stuck auxApp
|
||
|
||
def projectCore? (e : Expr) (i : Nat) : MetaM (Option Expr) := do
|
||
let e ← e.toCtorIfLit
|
||
matchConstCtor e.getAppFn (fun _ => pure none) fun ctorVal _ =>
|
||
let numArgs := e.getAppNumArgs
|
||
let idx := ctorVal.numParams + i
|
||
if idx < numArgs then
|
||
return some (e.getArg! idx)
|
||
else
|
||
return none
|
||
|
||
def project? (e : Expr) (i : Nat) : MetaM (Option Expr) := do
|
||
projectCore? (← whnf e) i
|
||
|
||
/-- Reduce kernel projection `Expr.proj ..` expression. -/
|
||
def reduceProj? (e : Expr) : MetaM (Option Expr) := do
|
||
match e with
|
||
| .proj _ i c => project? c i
|
||
| _ => return none
|
||
|
||
/--
|
||
Auxiliary method for reducing terms of the form `?m t_1 ... t_n` where `?m` is delayed assigned.
|
||
Recall that we can only expand a delayed assignment when all holes/metavariables in the assigned value have been "filled".
|
||
-/
|
||
private def whnfDelayedAssigned? (f' : Expr) (e : Expr) : MetaM (Option Expr) := do
|
||
if f'.isMVar then
|
||
match (← getDelayedMVarAssignment? f'.mvarId!) with
|
||
| none => return none
|
||
| some { fvars, mvarIdPending } =>
|
||
let args := e.getAppArgs
|
||
if fvars.size > args.size then
|
||
-- Insufficient number of argument to expand delayed assignment
|
||
return none
|
||
else
|
||
let newVal ← instantiateMVars (mkMVar mvarIdPending)
|
||
if newVal.hasExprMVar then
|
||
-- Delayed assignment still contains metavariables
|
||
return none
|
||
else
|
||
let newVal := newVal.abstract fvars
|
||
let result := newVal.instantiateRevRange 0 fvars.size args
|
||
return mkAppRange result fvars.size args.size args
|
||
else
|
||
return none
|
||
|
||
/--
|
||
Zeta reduces `let`s/`have`s.
|
||
If `zetaHave` is false, then `have`s are not zeta reduced.
|
||
|
||
Auxiliary function for `whnfCore` and `Simp.reduceStep`, to implement the `zeta` option.
|
||
This function does not implement `zetaUnused` logic,
|
||
which is instead the responsibility of `consumeUnusedLet`.
|
||
The `expandLet` function works with expressions with loose bound variables,
|
||
and thus determining whether a let variable is used isn't an O(1) operation.
|
||
|
||
Note: since `expandLet` and `consumeUnusedLet` are separated like this, a consequence is that
|
||
in the `+zeta -zetaHave +zetaUnused` configuration, then `whnfCore` has quadratic complexity
|
||
when reducing a sequence of alternating `let`s and `have`s where the `let`s are used but the `have`s are unused.
|
||
-/
|
||
partial def expandLet (e : Expr) (vs : Array Expr) (zetaHave : Bool := true) : Expr :=
|
||
if let .letE _ _ v b nondep := e then
|
||
if !nondep || zetaHave then
|
||
expandLet b (vs.push <| v.instantiateRev vs) zetaHave
|
||
else
|
||
e.instantiateRev vs
|
||
else
|
||
e.instantiateRev vs
|
||
|
||
/--
|
||
Consumes unused `let`s/`have`s.
|
||
If `consumeNondep` is false, then `have`s are not consumed.
|
||
|
||
Auxiliary function for `whnfCore`, `isDefEqQuick`, and `Simp.reduceStep`,
|
||
to implement the `zetaUnused` option.
|
||
In the case of `isDefEqQuick`, it is also used when `zeta` is set.
|
||
-/
|
||
partial def consumeUnusedLet (e : Expr) (consumeNondep : Bool := false) : Expr :=
|
||
match e with
|
||
| .letE _ _ _ b nondep => if b.hasLooseBVars || (nondep && !consumeNondep) then e else consumeUnusedLet b consumeNondep
|
||
| _ => e
|
||
|
||
/--
|
||
Apply beta-reduction, zeta-reduction (i.e., unfold let local-decls), iota-reduction,
|
||
expand let-expressions, expand assigned meta-variables, unfold aux declarations.
|
||
-/
|
||
partial def whnfCore (e : Expr) : MetaM Expr :=
|
||
go e
|
||
where
|
||
go (e : Expr) : MetaM Expr :=
|
||
whnfEasyCases e fun e => do
|
||
trace[Meta.whnf] e
|
||
match e with
|
||
| .const .. => pure e
|
||
| .letE _ _ v b nondep =>
|
||
let cfg ← getConfig
|
||
if cfg.zeta && (!nondep || cfg.zetaHave) then
|
||
go <| expandLet b #[v] (zetaHave := cfg.zetaHave)
|
||
else if cfg.zetaUnused && !b.hasLooseBVars then
|
||
go <| consumeUnusedLet b
|
||
else
|
||
return e
|
||
| .app f .. =>
|
||
let cfg ← getConfig
|
||
let f := f.getAppFn
|
||
let f' ← go f
|
||
/-
|
||
If `f'` is a lambda, then we perform beta-reduction here IF
|
||
1- `cfg.beta` is enabled, OR
|
||
2- `f` was not a lambda expression. That is, `f` was reduced, and the beta-reduction step is part
|
||
of this step. This is similar to allowing beta-reduction while unfolding expressions even if `cfg.beta := false`.
|
||
|
||
We added case 2 because a failure at `norm_cast`. See test `6123_mod_cast.lean`.
|
||
Another possible fix to this test is to set `beta := true` at the `Simp.Config` value at
|
||
`NormCast.lean`.
|
||
-/
|
||
if f'.isLambda && (cfg.beta || !f.isLambda) then
|
||
let revArgs := e.getAppRevArgs
|
||
go <| f'.betaRev revArgs
|
||
else if let some eNew ← whnfDelayedAssigned? f' e then
|
||
go eNew
|
||
else
|
||
let e := if f == f' then e else e.updateFn f'
|
||
unless cfg.iota do return e
|
||
match (← reduceMatcher? e) with
|
||
| .reduced eNew => go eNew
|
||
| .partialApp => pure e
|
||
| .stuck _ => pure e
|
||
| .notMatcher =>
|
||
let .const cname lvls := f'.getAppFn | return e
|
||
let some cinfo := (← getEnv).find? cname | return e
|
||
match cinfo with
|
||
| .recInfo rec => reduceRec rec lvls e.getAppArgs (fun _ => return e) (fun e => do recordUnfold cinfo.name; go e)
|
||
| .quotInfo rec => reduceQuotRec rec e.getAppArgs (fun _ => return e) (fun e => do recordUnfold cinfo.name; go e)
|
||
| c@(.defnInfo _) => do
|
||
if (← isAuxDef c.name) then
|
||
recordUnfold c.name
|
||
deltaBetaDefinition c lvls e.getAppRevArgs (fun _ => return e) go
|
||
else
|
||
return e
|
||
| .axiomInfo val => recordUnfoldAxiom val.name; return e
|
||
| _ => return e
|
||
| .proj _ i c =>
|
||
let k (c : Expr) := do
|
||
match (← projectCore? c i) with
|
||
| some e => go e
|
||
| none => return e
|
||
match (← getConfig).proj with
|
||
| .no => return e
|
||
| .yes => k (← go c)
|
||
| .yesWithDelta => k (← whnf c)
|
||
-- Remark: If the current transparency setting is `reducible`, we should not increase it to `instances`
|
||
| .yesWithDeltaI => k (← whnfAtMostI c)
|
||
| _ => unreachable!
|
||
|
||
/--
|
||
Recall that `_sunfold` auxiliary definitions contains the markers: `markSmartUnfoldingMatch` (*) and `markSmartUnfoldingMatchAlt` (**).
|
||
For example, consider the following definition
|
||
```
|
||
def r (i j : Nat) : Nat :=
|
||
i +
|
||
match j with
|
||
| Nat.zero => 1
|
||
| Nat.succ j =>
|
||
i + match j with
|
||
| Nat.zero => 2
|
||
| Nat.succ j => r i j
|
||
```
|
||
produces the following `_sunfold` auxiliary definition with the markers
|
||
```
|
||
def r._sunfold (i j : Nat) : Nat :=
|
||
i +
|
||
(*) match j with
|
||
| Nat.zero => (**) 1
|
||
| Nat.succ j =>
|
||
i + (*) match j with
|
||
| Nat.zero => (**) 2
|
||
| Nat.succ j => (**) r i j
|
||
```
|
||
|
||
`match` expressions marked with `markSmartUnfoldingMatch` (*) must be reduced, otherwise the resulting term is not definitionally
|
||
equal to the given expression. The recursion may be interrupted as soon as the annotation `markSmartUnfoldingAlt` (**) is reached.
|
||
|
||
For example, the term `r i j.succ.succ` reduces to the definitionally equal term `i + i * r i j`
|
||
-/
|
||
partial def smartUnfoldingReduce? (e : Expr) : MetaM (Option Expr) :=
|
||
go e |>.run
|
||
where
|
||
go (e : Expr) : OptionT MetaM Expr := do
|
||
match e with
|
||
| .letE n t v b nondep => mapLetDecl n t (← go v) (nondep := nondep) fun x => go (b.instantiate1 x)
|
||
| .lam .. => lambdaTelescope e fun xs b => do mkLambdaFVars xs (← go b)
|
||
| .app f a .. => return mkApp (← go f) (← go a)
|
||
| .proj _ _ s => return e.updateProj! (← go s)
|
||
| .mdata _ b =>
|
||
if let some m := smartUnfoldingMatch? e then
|
||
goMatch m
|
||
else
|
||
return e.updateMData! (← go b)
|
||
| _ => return e
|
||
|
||
goMatch (e : Expr) : OptionT MetaM Expr := do
|
||
match (← reduceMatcher? e) with
|
||
| ReduceMatcherResult.reduced e =>
|
||
if let some alt := smartUnfoldingMatchAlt? e then
|
||
return alt
|
||
else
|
||
go e
|
||
| ReduceMatcherResult.stuck e' =>
|
||
let mvarId ← getStuckMVar? e'
|
||
/- Try to "unstuck" by resolving pending TC problems -/
|
||
if (← Meta.synthPending mvarId) then
|
||
goMatch e
|
||
else
|
||
failure
|
||
| _ => failure
|
||
|
||
/--
|
||
Returns `true` if `declName` is the name of class field.
|
||
-/
|
||
private def isProjInst (declName : Name) : MetaM Bool := do
|
||
let some { fromClass := true, .. } ← getProjectionFnInfo? declName | return false
|
||
return true
|
||
|
||
/--
|
||
Auxiliary method for unfolding a class projection. Recall that class fields are not marked with
|
||
`[reducible]`, but we want to reduce them when the transparency setting is `.instances`.
|
||
For example, given `a b : Nat`, the term `a ≤ b` is `LE.le Nat instLENat a b`.
|
||
Unfolding the class field `LE.le` gives `instLENat.1 a b`. This method goes further and
|
||
reduces the instance projection to return `Nat.le a b`.
|
||
-/
|
||
partial def unfoldProjInst? (e : Expr) : MetaM (Option Expr) := do
|
||
let f := e.getAppFn
|
||
let .const declName us := f | return none
|
||
unless (← isProjInst declName) do return none
|
||
let some fInfo ← withDefault <| getUnfoldableConst? declName | return none
|
||
deltaBetaDefinition fInfo us e.getAppRevArgs (fun _ => pure none) fun e' => do
|
||
/-
|
||
Continuing the example: after delta-beta reducing `LE.le`, we get `e'` which is
|
||
`instLENat.1 a b`. We consider the unfolding successful if this instance projection
|
||
reduces to `Nat.le a b` using `.instances` reducibility.
|
||
-/
|
||
let some r ← withReducibleAndInstances <| reduceProj? e'.getAppFn | return none
|
||
recordUnfold declName
|
||
return some <| mkAppN r e'.getAppArgs |>.headBeta
|
||
|
||
/--
|
||
Auxiliary method for unfolding a class projection when transparency is set to `TransparencyMode.instances`.
|
||
Recall that class instance projections are not marked with `[reducible]` because we want them to be
|
||
in "reducible canonical form".
|
||
See `unfoldProjInst?`
|
||
-/
|
||
partial def unfoldProjInstWhenInstances? (e : Expr) : MetaM (Option Expr) := do
|
||
if (← getTransparency) matches .instances then
|
||
unfoldProjInst? e
|
||
else
|
||
return none
|
||
|
||
/--
|
||
When `true`, unfolding a `[reducible]` class field at `TransparencyMode.reducible` also unfolds
|
||
the associated instance projection at `TransparencyMode.instances`.
|
||
|
||
**Motivation:** Consider `a ≤ b` where `a b : Nat` and `LE.le` is `[reducible]`. Unfolding `LE.le`
|
||
gives `instLENat.1 a b`, which is stuck because `instLENat` is `[implicit_reducible]` (not
|
||
`[reducible]`). Similarly, `stM m (ExceptT ε m) α` unfolds to an instance projection that is stuck
|
||
at `.reducible`. Without this option, marking a class field as `[reducible]` is pointless when the
|
||
instance providing it is only `[implicit_reducible]`. This option makes the `[reducible]` annotation
|
||
on class fields work as the user expects by temporarily bumping to `.instances` for the projection.
|
||
|
||
See `unfoldDefault` for the implementation.
|
||
-/
|
||
register_builtin_option backward.whnf.reducibleClassField : Bool := {
|
||
defValue := true
|
||
descr := "enables better support for unfolding type class fields marked as `[reducible]`"
|
||
}
|
||
|
||
/--
|
||
Default unfolding function. `e` is of the form `f.{us} a₁ ... aₙ`, and `fInfo` is the `ConstantInfo` for `f`.
|
||
This function has special support for unfolding class fields.
|
||
The support is particularly important when the user marks a class field as `[reducible]` and
|
||
the transparency mode is `.reducible`. For example, suppose `e` is `a ≤ b` where `a b : Nat`,
|
||
and `LE.le` is marked as `[reducible]`. Simply unfolding `LE.le` would give `instLENat.1 a b`,
|
||
which would be stuck because `instLENat` has transparency `[implicit_reducible]`. To avoid this, when we unfold
|
||
a `[reducible]` class field, we also unfold the associated projection `instLENat.1` using
|
||
`.instances` reducibility, ultimately returning `Nat.le a b`.
|
||
-/
|
||
private def unfoldDefault (fInfo : ConstantInfo) (us : List Level) (e : Expr) : MetaM (Option Expr) := do
|
||
if fInfo.hasValue then
|
||
recordUnfold fInfo.name
|
||
deltaBetaDefinition fInfo us e.getAppRevArgs (fun _ => pure none) fun e => do
|
||
if !backward.whnf.reducibleClassField.get (← getOptions) then
|
||
return some e
|
||
else if !(← getTransparency) matches .reducible then
|
||
return some e
|
||
else if (← isProjInst fInfo.name) then
|
||
let some r ← withReducibleAndInstances <| reduceProj? e.getAppFn | return some e
|
||
return mkAppN r e.getAppArgs |>.headBeta
|
||
else
|
||
return some e
|
||
else
|
||
if fInfo.isAxiom then
|
||
recordUnfoldAxiom fInfo.name
|
||
return none
|
||
|
||
mutual
|
||
/--
|
||
Unfold definition using "smart unfolding" if possible.
|
||
If `ignoreTransparency = true`, then the definition is unfolded even if the transparency setting does not allow it.
|
||
-/
|
||
partial def unfoldDefinition? (e : Expr) (ignoreTransparency := false) : MetaM (Option Expr) :=
|
||
match e with
|
||
| .app f _ =>
|
||
matchConstAux (ignoreTransparency := ignoreTransparency) f.getAppFn (fun _ => unfoldProjInstWhenInstances? e) fun fInfo fLvls => do
|
||
if fInfo.levelParams.length != fLvls.length then
|
||
return none
|
||
else
|
||
let unfoldDefault (_ : Unit) : MetaM (Option Expr) :=
|
||
unfoldDefault fInfo fLvls e
|
||
if smartUnfolding.get (← getOptions) then
|
||
match ((← getEnv).find? (skipRealize := true) (mkSmartUnfoldingNameFor fInfo.name)) with
|
||
| some fAuxInfo@(.defnInfo _) =>
|
||
-- We use `preserveMData := true` to make sure the smart unfolding annotation are not erased in an over-application.
|
||
deltaBetaDefinition fAuxInfo fLvls e.getAppRevArgs (preserveMData := true) (fun _ => pure none) fun e₁ => do
|
||
let some r ← smartUnfoldingReduce? e₁ | return none
|
||
/-
|
||
If `smartUnfoldingReduce?` succeeds, we should still check whether the argument the
|
||
structural recursion is recursing on reduces to a constructor.
|
||
This extra check is necessary in definitions (see issue #1081) such as
|
||
```
|
||
inductive Vector (α : Type u) : Nat → Type u where
|
||
| nil : Vector α 0
|
||
| cons : α → Vector α n → Vector α (n+1)
|
||
|
||
def Vector.insert (a: α) (i : Fin (n+1)) (xs : Vector α n) : Vector α (n+1) :=
|
||
match i, xs with
|
||
| ⟨0, _⟩, xs => cons a xs
|
||
| ⟨i+1, h⟩, cons x xs => cons x (xs.insert a ⟨i, Nat.lt_of_succ_lt_succ h⟩)
|
||
```
|
||
The structural recursion is being performed using the vector `xs`. That is, we used `Vector.brecOn` to define
|
||
`Vector.insert`. Thus, an application `xs.insert a ⟨0, h⟩` is **not** definitionally equal to
|
||
`Vector.cons a xs` because `xs` is not a constructor application (the `Vector.brecOn` application is blocked).
|
||
|
||
Remark 1: performing structural recursion on `Fin (n+1)` is not an option here because it is a `Subtype` and
|
||
and the repacking in recursive applications confuses the structural recursion module.
|
||
|
||
Remark 2: the match expression reduces reduces to `cons a xs` when the discriminants are `⟨0, h⟩` and `xs`.
|
||
|
||
Remark 3: this check is unnecessary in most cases, but we don't need dependent elimination to trigger the issue
|
||
fixed by this extra check. Here is another example that triggers the issue fixed by this check.
|
||
```
|
||
def f : Nat → Nat → Nat
|
||
| 0, y => y
|
||
| x+1, y+1 => f (x-2) y
|
||
| x+1, 0 => 0
|
||
|
||
theorem ex : f 0 y = y := rfl
|
||
```
|
||
|
||
Remark 4: the `return some r` in the following `let` is not a typo. Binport generated .olean files do not
|
||
store the position of recursive arguments for definitions using structural recursion.
|
||
Thus, we should keep `return some r` until Mathlib has been ported to Lean 3.
|
||
Note that the `Vector` example above does not even work in Lean 3.
|
||
-/
|
||
let some recArgPos ← getStructuralRecArgPos? fInfo.name
|
||
| recordUnfold fInfo.name; return some r
|
||
let numArgs := e.getAppNumArgs
|
||
if recArgPos >= numArgs then return none
|
||
let recArg := e.getArg! recArgPos numArgs
|
||
if !(← isConstructorApp (← whnfMatcher recArg)) then return none
|
||
recordUnfold fInfo.name
|
||
return some r
|
||
| _ =>
|
||
if (← getMatcherInfo? fInfo.name).isSome then
|
||
-- Recall that `whnfCore` tries to reduce "matcher" applications.
|
||
return none
|
||
else
|
||
unfoldDefault ()
|
||
else
|
||
unfoldDefault ()
|
||
| .const declName lvls => do
|
||
let some cinfo ← getConstInfoNoEx? declName ignoreTransparency | pure none
|
||
-- check smart unfolding only after `getUnfoldableConstNoEx?` because smart unfoldings have a
|
||
-- significant chance of not existing and `Environment.contains` misses are more costly
|
||
if smartUnfolding.get (← getOptions) && (← getEnv).contains (mkSmartUnfoldingNameFor declName) then
|
||
return none
|
||
else
|
||
unless cinfo.hasValue do
|
||
if cinfo.isAxiom then
|
||
recordUnfoldAxiom cinfo.name
|
||
return none
|
||
deltaDefinition cinfo lvls
|
||
(fun _ => pure none)
|
||
(fun e => do recordUnfold declName; pure (some e))
|
||
| _ => return none
|
||
end
|
||
|
||
def unfoldDefinition (e : Expr) : MetaM Expr := do
|
||
let some e ← unfoldDefinition? e | throwError "failed to unfold definition{indentExpr e}"
|
||
return e
|
||
|
||
@[specialize] partial def whnfHeadPred (e : Expr) (pred : Expr → MetaM Bool) : MetaM Expr :=
|
||
whnfEasyCases e fun e => do
|
||
let e ← whnfCore e
|
||
if (← pred e) then
|
||
match (← unfoldDefinition? e) with
|
||
| some e => whnfHeadPred e pred
|
||
| none => return e
|
||
else
|
||
return e
|
||
|
||
def whnfUntil (e : Expr) (declName : Name) : MetaM (Option Expr) := do
|
||
let e ← whnfHeadPred e (fun e => return !e.isAppOf declName)
|
||
if e.isAppOf declName then
|
||
return e
|
||
else
|
||
return none
|
||
|
||
/-- Applies `whnfCore` while unfolding type annotations (`outParam`/`optParam`/etc.). -/
|
||
partial def whnfCoreUnfoldingAnnotations (e : Expr) : MetaM Expr :=
|
||
whnfHeadPred e (fun e => return e.isTypeAnnotation)
|
||
|
||
/-- Try to reduce matcher/recursor/quot applications. We say they are all "morally" recursor applications. -/
|
||
def reduceRecMatcher? (e : Expr) : MetaM (Option Expr) := do
|
||
if !e.isApp then
|
||
return none
|
||
else match (← reduceMatcher? e) with
|
||
| .reduced e => return e
|
||
| _ =>
|
||
let .const cname lvls := e.getAppFn | return none
|
||
let some cinfo := (← getEnv).find? cname | return none
|
||
match cinfo with
|
||
| .recInfo «rec» => reduceRec «rec» lvls e.getAppArgs (fun _ => pure none) (fun e => do recordUnfold cinfo.name; pure (some e))
|
||
| .quotInfo «rec» => reduceQuotRec «rec» e.getAppArgs (fun _ => pure none) (fun e => do recordUnfold cinfo.name; pure (some e))
|
||
| c@(.defnInfo _) =>
|
||
if (← isAuxDef c.name) then
|
||
deltaBetaDefinition c lvls e.getAppRevArgs (fun _ => pure none) (fun e => do recordUnfold c.name; pure (some e))
|
||
else
|
||
return none
|
||
| _ => return none
|
||
|
||
unsafe def reduceBoolNativeUnsafe (constName : Name) : MetaM Bool := evalConstCheck Bool `Bool constName
|
||
unsafe def reduceNatNativeUnsafe (constName : Name) : MetaM Nat := evalConstCheck Nat `Nat constName
|
||
@[implemented_by reduceBoolNativeUnsafe] opaque reduceBoolNative (constName : Name) : MetaM Bool
|
||
@[implemented_by reduceNatNativeUnsafe] opaque reduceNatNative (constName : Name) : MetaM Nat
|
||
|
||
def reduceNative? (e : Expr) : MetaM (Option Expr) :=
|
||
match e with
|
||
| Expr.app (Expr.const fName _) (Expr.const argName _) =>
|
||
if fName == ``Lean.reduceBool then do
|
||
return toExpr (← reduceBoolNative argName)
|
||
else if fName == ``Lean.reduceNat then do
|
||
return toExpr (← reduceNatNative argName)
|
||
else
|
||
return none
|
||
| _ =>
|
||
return none
|
||
|
||
@[inline] def withNatValue (a : Expr) (k : Nat → MetaM (Option α)) : MetaM (Option α) := do
|
||
if !a.hasExprMVar && a.hasFVar then
|
||
return none
|
||
let a ← instantiateMVars a
|
||
if a.hasExprMVar || a.hasFVar then
|
||
return none
|
||
let a ← whnf a
|
||
match a with
|
||
| .const ``Nat.zero _ => k 0
|
||
| .lit (.natVal v) => k v
|
||
| _ => return none
|
||
|
||
def reduceUnaryNatOp (f : Nat → Nat) (a : Expr) : MetaM (Option Expr) :=
|
||
withNatValue a fun a =>
|
||
return mkRawNatLit <| f a
|
||
|
||
def reduceBinNatOp (f : Nat → Nat → Nat) (a b : Expr) : MetaM (Option Expr) :=
|
||
withNatValue a fun a =>
|
||
withNatValue b fun b => do
|
||
trace[Meta.isDefEq.whnf.reduceBinOp] "{a} op {b}"
|
||
return mkRawNatLit <| f a b
|
||
|
||
def reducePow (a b : Expr) : MetaM (Option Expr) :=
|
||
withNatValue a fun a =>
|
||
withNatValue b fun b => OptionT.run do
|
||
guard (← checkExponent b)
|
||
trace[Meta.isDefEq.whnf.reduceBinOp] "{a} ^ {b}"
|
||
return mkRawNatLit <| a ^ b
|
||
|
||
def reduceBinNatPred (f : Nat → Nat → Bool) (a b : Expr) : MetaM (Option Expr) := do
|
||
withNatValue a fun a =>
|
||
withNatValue b fun b =>
|
||
return toExpr <| f a b
|
||
|
||
def reduceNat? (e : Expr) : MetaM (Option Expr) :=
|
||
match e with
|
||
| .app (.const fn _) a =>
|
||
if fn == ``Nat.succ then
|
||
reduceUnaryNatOp Nat.succ a
|
||
else
|
||
return none
|
||
| .app (.app (.const fn _) a1) a2 =>
|
||
match fn with
|
||
| ``Nat.add => reduceBinNatOp Nat.add a1 a2
|
||
| ``Nat.sub => reduceBinNatOp Nat.sub a1 a2
|
||
| ``Nat.mul => reduceBinNatOp Nat.mul a1 a2
|
||
| ``Nat.div => reduceBinNatOp Nat.div a1 a2
|
||
| ``Nat.mod => reduceBinNatOp Nat.mod a1 a2
|
||
| ``Nat.pow => reducePow a1 a2
|
||
| ``Nat.gcd => reduceBinNatOp Nat.gcd a1 a2
|
||
| ``Nat.beq => reduceBinNatPred Nat.beq a1 a2
|
||
| ``Nat.ble => reduceBinNatPred Nat.ble a1 a2
|
||
| ``Nat.land => reduceBinNatOp Nat.land a1 a2
|
||
| ``Nat.lor => reduceBinNatOp Nat.lor a1 a2
|
||
| ``Nat.xor => reduceBinNatOp Nat.xor a1 a2
|
||
| ``Nat.shiftLeft => reduceBinNatOp Nat.shiftLeft a1 a2
|
||
| ``Nat.shiftRight => reduceBinNatOp Nat.shiftRight a1 a2
|
||
| _ => return none
|
||
| _ =>
|
||
return none
|
||
|
||
|
||
@[inline] private def useWHNFCache (e : Expr) : MetaM Bool := do
|
||
-- We cache only closed terms without expr metavars.
|
||
-- Potential refinement: cache if `e` is not stuck at a metavariable
|
||
if e.hasFVar || e.hasExprMVar || (← read).canUnfold?.isSome then
|
||
return false
|
||
else
|
||
return true
|
||
|
||
@[inline] private def cached? (useCache : Bool) (e : Expr) : MetaM (Option Expr) := do
|
||
if useCache then
|
||
return (← get).cache.whnf.find? (← mkExprConfigCacheKey e)
|
||
else
|
||
return none
|
||
|
||
private def cache (useCache : Bool) (e r : Expr) : MetaM Expr := do
|
||
if useCache then
|
||
let key ← mkExprConfigCacheKey e
|
||
modify fun s => { s with cache.whnf := s.cache.whnf.insert key r }
|
||
return r
|
||
|
||
set_option compiler.ignoreBorrowAnnotation true in
|
||
@[export lean_whnf]
|
||
partial def whnfImp (e : Expr) : MetaM Expr :=
|
||
withIncRecDepth <| whnfEasyCases e fun e => do
|
||
let useCache ← useWHNFCache e
|
||
match (← cached? useCache e) with
|
||
| some e' => pure e'
|
||
| none =>
|
||
withTraceNode `Meta.whnf (fun _ => return m!"Non-easy whnf: {e}") do
|
||
checkSystem "whnf"
|
||
let e' ← whnfCore e
|
||
match (← reduceNat? e') with
|
||
| some v => cache useCache e v
|
||
| none =>
|
||
match (← reduceNative? e') with
|
||
| some v => cache useCache e v
|
||
| none =>
|
||
match (← unfoldDefinition? e') with
|
||
| some e'' => cache useCache e (← whnfImp e'')
|
||
| none => cache useCache e e'
|
||
|
||
/-- If `e` is a projection function that satisfies `p`, then reduce it -/
|
||
def reduceProjOf? (e : Expr) (p : Name → Bool) : MetaM (Option Expr) := do
|
||
if !e.isApp then
|
||
pure none
|
||
else match e.getAppFn with
|
||
| .const name .. => do
|
||
let env ← getEnv
|
||
match env.getProjectionStructureName? name with
|
||
| some structName =>
|
||
if p structName then
|
||
Meta.unfoldDefinition? e
|
||
else
|
||
pure none
|
||
| none => pure none
|
||
| _ => pure none
|
||
|
||
builtin_initialize
|
||
registerTraceClass `Meta.whnf
|
||
registerTraceClass `Meta.isDefEq.whnf.reduceBinOp
|
||
|
||
end Lean.Meta
|