/- Copyright (c) 2019 Microsoft Corporation. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Leonardo de Moura -/ import Lean.ToExpr import Lean.AuxRecursor import Lean.Meta.Basic import Lean.Meta.LevelDefEq namespace Lean namespace Meta /- =========================== Smart unfolding support =========================== -/ def smartUnfoldingSuffix := "_sunfold" @[inline] def mkSmartUnfoldingNameFor (n : Name) : Name := mkNameStr n smartUnfoldingSuffix /- =========================== Helper methods =========================== -/ variables {m : Type → Type} [MonadLiftT MetaM m] private def isAuxDefImp? (constName : Name) : MetaM Bool := do env ← getEnv; pure (isAuxRecursor env constName || isNoConfusion env constName) @[inline] def isAuxDef? (constName : Name) : m Bool := liftMetaM $ isAuxDefImp? constName @[inline] private def matchConstAux {α} (e : Expr) (failK : Unit → MetaM α) (k : ConstantInfo → List Level → MetaM α) : MetaM α := match e with | Expr.const name lvls _ => do (some cinfo) ← getConst? name | failK (); k cinfo lvls | _ => failK () /- =========================== Helper functions for reducing recursors =========================== -/ private def getFirstCtor (d : Name) : MetaM (Option Name) := do some (ConstantInfo.inductInfo { ctors := ctor::_, ..}) ← getConstNoEx? d | pure none; pure (some ctor) private def mkNullaryCtor (type : Expr) (nparams : Nat) : MetaM (Option Expr) := match type.getAppFn with | Expr.const d lvls _ => do (some ctor) ← getFirstCtor d | pure none; pure $ mkAppN (mkConst ctor lvls) (type.getAppArgs.shrink nparams) | _ => pure none def toCtorIfLit : Expr → Expr | Expr.lit (Literal.natVal v) _ => if v == 0 then mkConst `Nat.zero else mkApp (mkConst `Nat.succ) (mkNatLit (v-1)) | Expr.lit (Literal.strVal v) _ => mkApp (mkConst `String.mk) (toExpr v.toList) | e => e private def getRecRuleFor (rec : RecursorVal) (major : Expr) : Option RecursorRule := match major.getAppFn with | Expr.const fn _ _ => rec.rules.find? $ fun r => r.ctor == fn | _ => none private def toCtorWhenK (rec : RecursorVal) (major : Expr) : MetaM (Option Expr) := do majorType ← inferType major; majorType ← whnf majorType; let majorTypeI := majorType.getAppFn; if !majorTypeI.isConstOf rec.getInduct then pure none else if majorType.hasExprMVar && majorType.getAppArgs.anyFrom rec.nparams Expr.hasExprMVar then pure none else do (some newCtorApp) ← mkNullaryCtor majorType rec.nparams | pure none; newType ← inferType newCtorApp; defeq ← Meta.isExprDefEqAux majorType newType; pure $ if defeq then newCtorApp else none /-- Auxiliary function for reducing recursor applications. -/ private def reduceRec {α} (rec : RecursorVal) (recLvls : List Level) (recArgs : Array Expr) (failK : Unit → MetaM α) (successK : Expr → MetaM α) : MetaM α := let majorIdx := rec.getMajorIdx; if h : majorIdx < recArgs.size then do let major := recArgs.get ⟨majorIdx, h⟩; major ← whnf major; major ← if !rec.k then pure major else do { newMajor ← toCtorWhenK rec major; pure (newMajor.getD major) }; let major := toCtorIfLit major; match getRecRuleFor rec major with | some rule => let majorArgs := major.getAppArgs; if recLvls.length != rec.lparams.length then failK () else let rhs := rule.rhs.instantiateLevelParams rec.lparams recLvls; -- Apply parameters, motives and minor premises from recursor application. let rhs := mkAppRange rhs 0 (rec.nparams+rec.nmotives+rec.nminors) 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 () @[specialize] private def isRecStuck? (isStuck? : Expr → MetaM (Option MVarId)) (rec : RecursorVal) (recLvls : List Level) (recArgs : Array Expr) : MetaM (Option MVarId) := if rec.k then -- TODO: improve this case pure none else do let majorIdx := rec.getMajorIdx; if h : majorIdx < recArgs.size then do let major := recArgs.get ⟨majorIdx, h⟩; major ← whnf major; isStuck? major else pure none /- =========================== Helper functions for reducing Quot.lift and Quot.ind =========================== -/ /-- Auxiliary function for reducing `Quot.lift` and `Quot.ind` applications. -/ private def reduceQuotRec {α} (rec : QuotVal) (recLvls : List Level) (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.get ⟨majorPos, h⟩; major ← whnf major; match major with | Expr.app (Expr.app (Expr.app (Expr.const majorFn _ _) _ _) _ _) majorArg _ => do some (ConstantInfo.quotInfo { kind := QuotKind.ctor, .. }) ← getConstNoEx? majorFn | failK (); let f := recArgs.get! argPos; let r := mkApp f majorArg; let recArity := majorPos + 1; successK $ mkAppRange r recArity recArgs.size recArgs | _ => failK () else failK (); match rec.kind with | QuotKind.lift => process 5 3 | QuotKind.ind => process 4 3 | _ => failK () @[specialize] private def isQuotRecStuck? (isStuck? : Expr → MetaM (Option MVarId)) (rec : QuotVal) (recLvls : List Level) (recArgs : Array Expr) : MetaM (Option MVarId) := let process? (majorPos : Nat) : MetaM (Option MVarId) := if h : majorPos < recArgs.size then do let major := recArgs.get ⟨majorPos, h⟩; major ← whnf major; isStuck? major else pure none; match rec.kind with | QuotKind.lift => process? 5 | QuotKind.ind => process? 4 | _ => pure none /- =========================== Helper function for extracting "stuck term" =========================== -/ /-- Return `some (Expr.mvar mvarId)` if metavariable `mvarId` is blocking reduction. -/ private partial def getStuckMVarImp? : Expr → MetaM (Option MVarId) | Expr.mdata _ e _ => getStuckMVarImp? e | Expr.proj _ _ e _ => do e ← whnf e; getStuckMVarImp? e | e@(Expr.mvar mvarId _) => pure (some mvarId) | e@(Expr.app f _ _) => let f := f.getAppFn; match f with | Expr.mvar mvarId _ => pure (some mvarId) | Expr.const fName fLvls _ => do cinfo? ← getConstNoEx? fName; match cinfo? with | some $ ConstantInfo.recInfo rec => isRecStuck? getStuckMVarImp? rec fLvls e.getAppArgs | some $ ConstantInfo.quotInfo rec => isQuotRecStuck? getStuckMVarImp? rec fLvls e.getAppArgs | _ => pure none | _ => pure none | _ => pure none @[inline] def getStuckMVar? (e : Expr) : m (Option MVarId) := liftM $ getStuckMVarImp? e /- =========================== 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] private partial def whnfEasyCases : Expr → (Expr → MetaM Expr) → MetaM Expr | e@(Expr.forallE _ _ _ _), _ => pure e | e@(Expr.lam _ _ _ _), _ => pure e | e@(Expr.sort _ _), _ => pure e | e@(Expr.lit _ _), _ => pure e | e@(Expr.bvar _ _), _ => unreachable! | Expr.mdata _ e _, k => whnfEasyCases e k | e@(Expr.letE _ _ _ _ _), k => k e | e@(Expr.fvar fvarId _), k => do decl ← getLocalDecl fvarId; match decl with | LocalDecl.cdecl _ _ _ _ _ => pure e | LocalDecl.ldecl _ _ _ _ v nonDep => do cfg ← getConfig; if nonDep && !cfg.zetaNonDep then pure e else do when cfg.trackZeta $ modify fun s => { s with zetaFVarIds := s.zetaFVarIds.insert fvarId }; whnfEasyCases v k | e@(Expr.mvar mvarId _), k => do v? ← getExprMVarAssignment? mvarId; match v? with | some v => whnfEasyCases v k | none => pure e | e@(Expr.const _ _ _), k => k e | e@(Expr.app _ _ _), k => k e | e@(Expr.proj _ _ _ _), k => k e | Expr.localE _ _ _ _, _ => unreachable! /-- Return true iff term is of the form `idRhs ...` -/ private def isIdRhsApp (e : Expr) : Bool := e.isAppOf `idRhs /-- (@idRhs T f a_1 ... a_n) ==> (f a_1 ... a_n) -/ private def extractIdRhs (e : Expr) : Expr := if !isIdRhsApp e then e else let args := e.getAppArgs; if args.size < 2 then e else mkAppRange (args.get! 1) 2 args.size args @[specialize] private def deltaDefinition {α} (c : ConstantInfo) (lvls : List Level) (failK : Unit → α) (successK : Expr → α) : α := if c.lparams.length != lvls.length then failK () else let val := c.instantiateValueLevelParams lvls; successK (extractIdRhs val) @[specialize] private def deltaBetaDefinition {α} (c : ConstantInfo) (lvls : List Level) (revArgs : Array Expr) (failK : Unit → α) (successK : Expr → α) : α := if c.lparams.length != lvls.length then failK () else let val := c.instantiateValueLevelParams lvls; let val := val.betaRev revArgs; successK (extractIdRhs val) /-- Apply beta-reduction, zeta-reduction (i.e., unfold let local-decls), iota-reduction, expand let-expressions, expand assigned meta-variables. -/ private partial def whnfCoreImp : Expr → MetaM Expr | e => whnfEasyCases e $ fun e => match e with | e@(Expr.const _ _ _) => pure e | e@(Expr.letE _ _ v b _) => whnfCoreImp $ b.instantiate1 v | e@(Expr.app f _ _) => do let f := f.getAppFn; f' ← whnfCoreImp f; if f'.isLambda then let revArgs := e.getAppRevArgs; whnfCoreImp $ f'.betaRev revArgs else do let done : Unit → MetaM Expr := fun _ => if f == f' then pure e else pure $ e.updateFn f'; matchConstAux f' done $ fun cinfo lvls => match cinfo with | ConstantInfo.recInfo rec => reduceRec rec lvls e.getAppArgs done whnfCoreImp | ConstantInfo.quotInfo rec => reduceQuotRec rec lvls e.getAppArgs done whnfCoreImp | c@(ConstantInfo.defnInfo _) => do unfold? ← isAuxDef? c.name; if unfold? then deltaBetaDefinition c lvls e.getAppRevArgs done whnfCoreImp else done () | _ => done () | e@(Expr.proj _ i c _) => do c ← whnf c; matchConstAux c.getAppFn (fun _ => pure e) $ fun cinfo lvls => match cinfo with | ConstantInfo.ctorInfo ctorVal => pure $ c.getArgD (ctorVal.nparams + i) e | _ => pure e | _ => unreachable! @[inline] def whnfCore (e : Expr) : m Expr := liftMetaM $ whnfCoreImp e /-- Similar to `whnfCore`, but uses `synthesizePending` to (try to) synthesize metavariables that are blocking reduction. -/ private partial def whnfCoreUnstuck : Expr → MetaM Expr | e => do e ← whnfCore e; (some mvarId) ← getStuckMVar? e | pure e; succeeded ← Meta.synthPending mvarId; if succeeded then whnfCoreUnstuck e else pure e /-- Unfold definition using "smart unfolding" if possible. -/ private def unfoldDefinitionImp? (e : Expr) : MetaM (Option Expr) := match e with | Expr.app f _ _ => matchConstAux f.getAppFn (fun _ => pure none) $ fun fInfo fLvls => if fInfo.lparams.length != fLvls.length then pure none else do fAuxInfo? ← getConstNoEx? (mkSmartUnfoldingNameFor fInfo.name); match fAuxInfo? with | some $ fAuxInfo@(ConstantInfo.defnInfo _) => deltaBetaDefinition fAuxInfo fLvls e.getAppRevArgs (fun _ => pure none) $ fun e₁ => do e₂ ← whnfCoreUnstuck e₁; if isIdRhsApp e₂ then pure (some (extractIdRhs e₂)) else pure none | _ => if fInfo.hasValue then deltaBetaDefinition fInfo fLvls e.getAppRevArgs (fun _ => pure none) (fun e => pure (some e)) else pure none | Expr.const name lvls _ => do (some (cinfo@(ConstantInfo.defnInfo _))) ← getConstNoEx? name | pure none; deltaDefinition cinfo lvls (fun _ => pure none) (fun e => pure (some e)) | _ => pure none @[inline] def unfoldDefinition? (e : Expr) : m (Option Expr) := liftMetaM $ unfoldDefinitionImp? e unsafe def reduceNativeConst (α : Type) (typeName : Name) (constName : Name) : MetaM α := do env ← getEnv; match env.evalConstCheck α typeName constName with | Except.error ex => throwError ex | Except.ok v => pure v unsafe def reduceBoolNativeUnsafe (constName : Name) : MetaM Bool := reduceNativeConst Bool `Bool constName unsafe def reduceNatNativeUnsafe (constName : Name) : MetaM Nat := reduceNativeConst Nat `Nat constName @[implementedBy reduceBoolNativeUnsafe] constant reduceBoolNative (constName : Name) : MetaM Bool := arbitrary _ @[implementedBy reduceNatNativeUnsafe] constant reduceNatNative (constName : Name) : MetaM Nat := arbitrary _ def reduceNative? (e : Expr) : MetaM (Option Expr) := match e with | Expr.app (Expr.const fName _ _) (Expr.const argName _ _) _ => if fName == `Lean.reduceBool then do b ← reduceBoolNative argName; pure $ toExpr b else if fName == `Lean.reduceNat then do n ← reduceNatNative argName; pure $ toExpr n else pure none | _ => pure none @[inline] def withNatValue {α} (a : Expr) (k : Nat → MetaM (Option α)) : MetaM (Option α) := do a ← whnf a; match a with | Expr.const `Nat.zero _ _ => k 0 | Expr.lit (Literal.natVal v) _ => k v | _ => pure none def reduceUnaryNatOp (f : Nat → Nat) (a : Expr) : MetaM (Option Expr) := withNatValue a $ fun a => pure $ mkNatLit $ 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 $ fun _ => toString a ++ " op " ++ toString b; pure $ mkNatLit $ f a b def reduceBinNatPred (f : Nat → Nat → Bool) (a b : Expr) : MetaM (Option Expr) := do withNatValue a $ fun a => withNatValue b $ fun b => pure $ toExpr $ f a b def reduceNat? (e : Expr) : MetaM (Option Expr) := if e.hasFVar || e.hasMVar then pure none else match e with | Expr.app (Expr.const fn _ _) a _ => if fn == `Nat.succ then reduceUnaryNatOp Nat.succ a else pure none | Expr.app (Expr.app (Expr.const fn _ _) a1 _) a2 _ => if fn == `Nat.add then reduceBinNatOp Nat.add a1 a2 else if fn == `Nat.sub then reduceBinNatOp Nat.sub a1 a2 else if fn == `Nat.mul then reduceBinNatOp Nat.mul a1 a2 else if fn == `Nat.div then reduceBinNatOp Nat.div a1 a2 else if fn == `Nat.mod then reduceBinNatOp Nat.mod a1 a2 else if fn == `Nat.beq then reduceBinNatPred Nat.beq a1 a2 else if fn == `Nat.ble then reduceBinNatPred Nat.ble a1 a2 else pure none | _ => pure none @[inline] private def useWHNFCache (e : Expr) : MetaM Bool := do -- We cache only closed terms if e.hasFVar then pure false else do ctx ← read; pure $ ctx.config.transparency != TransparencyMode.reducible @[inline] private def cached? (useCache : Bool) (e : Expr) : MetaM (Option Expr) := do if useCache then do ctx ← read; s ← get; match ctx.config.transparency with | TransparencyMode.default => pure $ s.cache.whnfDefault.find? e | TransparencyMode.all => pure $ s.cache.whnfAll.find? e | _ => unreachable! else pure none private def cache (useCache : Bool) (e r : Expr) : MetaM Expr := do ctx ← read; when useCache $ match ctx.config.transparency with | TransparencyMode.default => modify $ fun s => { s with cache := { s.cache with whnfDefault := s.cache.whnfDefault.insert e r } } | TransparencyMode.all => modify $ fun s => { s with cache := { s.cache with whnfAll := s.cache.whnfAll.insert e r } } | _ => unreachable!; pure r partial def whnfImp : Expr → MetaM Expr | e => whnfEasyCases e $ fun e => do useCache ← useWHNFCache e; e? ← cached? useCache e; match e? with | some e' => pure e' | none => do e' ← whnfCore e; v? ← reduceNat? e'; match v? with | some v => cache useCache e v | none => do v? ← reduceNative? e'; match v? with | some v => cache useCache e v | none => do e? ← unfoldDefinition? e'; match e? with | some e => whnfImp e | none => cache useCache e e' @[init] def setWHNFRef : IO Unit := whnfRef.set whnfImp /- Given an expression `e`, compute its WHNF and if the result is a constructor, return field #i. -/ def reduceProj? (e : Expr) (i : Nat) : MetaM (Option Expr) := do e ← whnf e; matchConstCtor e.getAppFn (fun _ => pure none) fun ctorVal _ => let numArgs := e.getAppNumArgs; let idx := ctorVal.nparams + i; if idx < numArgs then pure (some (e.getArg! idx)) else pure none @[specialize] partial def whnfHeadPredAux (pred : Expr → MetaM Bool) : Expr → MetaM Expr | e => whnfEasyCases e $ fun e => do e ← whnfCore e; condM (pred e) (do e? ← unfoldDefinition? e; match e? with | some e => whnfHeadPredAux e | none => pure e) (pure e) @[inline] def whnfHeadPred (e : Expr) (pred : Expr → MetaM Bool) : m Expr := liftMetaM $ whnfHeadPredAux pred e def whnfUntil (e : Expr) (declName : Name) : m (Option Expr) := liftMetaM do e ← whnfHeadPredAux (fun e => pure $ !e.isAppOf declName) e; if e.isAppOf declName then pure e else pure none end Meta end Lean