640 lines
26 KiB
Text
640 lines
26 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
|
||
-/
|
||
import Lean.Elab.Quotation.Precheck
|
||
import Lean.Elab.Term
|
||
import Lean.Elab.BindersUtil
|
||
import Lean.Parser.Term
|
||
|
||
namespace Lean.Elab.Term
|
||
open Meta
|
||
open Lean.Parser.Term
|
||
|
||
/--
|
||
Given syntax of the forms
|
||
a) (`:` term)?
|
||
b) `:` term
|
||
return `term` if it is present, or a hole if not. -/
|
||
private def expandBinderType (ref : Syntax) (stx : Syntax) : Syntax :=
|
||
if stx.getNumArgs == 0 then
|
||
mkHole ref
|
||
else
|
||
stx[1]
|
||
|
||
/-- Given syntax of the form `ident <|> hole`, return `ident`. If `hole`, then we create a new anonymous name. -/
|
||
private def expandBinderIdent (stx : Syntax) : TermElabM Syntax :=
|
||
match stx with
|
||
| `(_) => mkFreshIdent stx
|
||
| _ => pure stx
|
||
|
||
/-- Given syntax of the form `(ident >> " : ")?`, return `ident`, or a new instance name. -/
|
||
private def expandOptIdent (stx : Syntax) : TermElabM Syntax := do
|
||
if stx.isNone then
|
||
let id ← withFreshMacroScope <| MonadQuotation.addMacroScope `inst
|
||
return mkIdentFrom stx id
|
||
else
|
||
return stx[0]
|
||
|
||
structure BinderView where
|
||
id : Syntax
|
||
type : Syntax
|
||
bi : BinderInfo
|
||
|
||
partial def quoteAutoTactic : Syntax → TermElabM Syntax
|
||
| stx@(Syntax.ident _ _ _ _) => throwErrorAt stx "invalid auto tactic, identifier is not allowed"
|
||
| stx@(Syntax.node _ k args) => do
|
||
if stx.isAntiquot then
|
||
throwErrorAt stx "invalid auto tactic, antiquotation is not allowed"
|
||
else
|
||
let mut quotedArgs ← `(Array.empty)
|
||
for arg in args do
|
||
if k == nullKind && (arg.isAntiquotSuffixSplice || arg.isAntiquotSplice) then
|
||
throwErrorAt arg "invalid auto tactic, antiquotation is not allowed"
|
||
else
|
||
let quotedArg ← quoteAutoTactic arg
|
||
quotedArgs ← `(Array.push $quotedArgs $quotedArg)
|
||
`(Syntax.node SourceInfo.none $(quote k) $quotedArgs)
|
||
| Syntax.atom info val => `(mkAtom $(quote val))
|
||
| Syntax.missing => throwError "invalid auto tactic, tactic is missing"
|
||
|
||
def declareTacticSyntax (tactic : Syntax) : TermElabM Name :=
|
||
withFreshMacroScope do
|
||
let name ← MonadQuotation.addMacroScope `_auto
|
||
let type := Lean.mkConst `Lean.Syntax
|
||
let tactic ← quoteAutoTactic tactic
|
||
let val ← elabTerm tactic type
|
||
let val ← instantiateMVars val
|
||
trace[Elab.autoParam] val
|
||
let decl := Declaration.defnDecl { name := name, levelParams := [], type := type, value := val, hints := ReducibilityHints.opaque,
|
||
safety := DefinitionSafety.safe }
|
||
addDecl decl
|
||
compileDecl decl
|
||
return name
|
||
|
||
/-
|
||
Expand `optional (binderTactic <|> binderDefault)`
|
||
def binderTactic := leading_parser " := " >> " by " >> tacticParser
|
||
def binderDefault := leading_parser " := " >> termParser
|
||
-/
|
||
private def expandBinderModifier (type : Syntax) (optBinderModifier : Syntax) : TermElabM Syntax := do
|
||
if optBinderModifier.isNone then
|
||
return type
|
||
else
|
||
let modifier := optBinderModifier[0]
|
||
let kind := modifier.getKind
|
||
if kind == `Lean.Parser.Term.binderDefault then
|
||
let defaultVal := modifier[1]
|
||
`(optParam $type $defaultVal)
|
||
else if kind == `Lean.Parser.Term.binderTactic then
|
||
let tac := modifier[2]
|
||
let name ← declareTacticSyntax tac
|
||
`(autoParam $type $(mkIdentFrom tac name))
|
||
else
|
||
throwUnsupportedSyntax
|
||
|
||
private def getBinderIds (ids : Syntax) : TermElabM (Array Syntax) :=
|
||
ids.getArgs.mapM fun id =>
|
||
let k := id.getKind
|
||
if k == identKind || k == `Lean.Parser.Term.hole then
|
||
return id
|
||
else
|
||
throwErrorAt id "identifier or `_` expected"
|
||
|
||
private def matchBinder (stx : Syntax) : TermElabM (Array BinderView) := do
|
||
let k := stx.getKind
|
||
if k == ``Lean.Parser.Term.simpleBinder then
|
||
-- binderIdent+ >> optType
|
||
let ids ← getBinderIds stx[0]
|
||
let optType := stx[1]
|
||
ids.mapM fun id => do pure { id := (← expandBinderIdent id), type := expandOptType id optType, bi := BinderInfo.default }
|
||
else if k == ``Lean.Parser.Term.explicitBinder then
|
||
-- `(` binderIdent+ binderType (binderDefault <|> binderTactic)? `)`
|
||
let ids ← getBinderIds stx[1]
|
||
let type := stx[2]
|
||
let optModifier := stx[3]
|
||
ids.mapM fun id => do pure { id := (← expandBinderIdent id), type := (← expandBinderModifier (expandBinderType id type) optModifier), bi := BinderInfo.default }
|
||
else if k == ``Lean.Parser.Term.implicitBinder then
|
||
-- `{` binderIdent+ binderType `}`
|
||
let ids ← getBinderIds stx[1]
|
||
let type := stx[2]
|
||
ids.mapM fun id => do pure { id := (← expandBinderIdent id), type := expandBinderType id type, bi := BinderInfo.implicit }
|
||
else if k == ``Lean.Parser.Term.strictImplicitBinder then
|
||
-- `⦃` binderIdent+ binderType `⦄`
|
||
let ids ← getBinderIds stx[1]
|
||
let type := stx[2]
|
||
ids.mapM fun id => do pure { id := (← expandBinderIdent id), type := expandBinderType id type, bi := BinderInfo.strictImplicit }
|
||
else if k == ``Lean.Parser.Term.instBinder then
|
||
-- `[` optIdent type `]`
|
||
let id ← expandOptIdent stx[1]
|
||
let type := stx[2]
|
||
pure #[ { id := id, type := type, bi := BinderInfo.instImplicit } ]
|
||
else
|
||
throwUnsupportedSyntax
|
||
|
||
private def registerFailedToInferBinderTypeInfo (type : Expr) (ref : Syntax) : TermElabM Unit :=
|
||
registerCustomErrorIfMVar type ref "failed to infer binder type"
|
||
|
||
private def addLocalVarInfo (stx : Syntax) (fvar : Expr) : TermElabM Unit := do
|
||
addTermInfo (isBinder := true) stx fvar
|
||
|
||
private def ensureAtomicBinderName (binderView : BinderView) : TermElabM Unit :=
|
||
let n := binderView.id.getId.eraseMacroScopes
|
||
unless n.isAtomic do
|
||
throwErrorAt binderView.id "invalid binder name '{n}', it must be atomic"
|
||
|
||
register_builtin_option checkBinderAnnotations : Bool := {
|
||
defValue := true
|
||
descr := "check whether type is a class instance whenever the binder annotation `[...]` is used"
|
||
}
|
||
|
||
private partial def elabBinderViews {α} (binderViews : Array BinderView) (fvars : Array Expr) (k : Array Expr → TermElabM α)
|
||
: TermElabM α :=
|
||
let rec loop (i : Nat) (fvars : Array Expr) : TermElabM α := do
|
||
if h : i < binderViews.size then
|
||
let binderView := binderViews.get ⟨i, h⟩
|
||
ensureAtomicBinderName binderView
|
||
let type ← elabType binderView.type
|
||
registerFailedToInferBinderTypeInfo type binderView.type
|
||
if binderView.bi.isInstImplicit && checkBinderAnnotations.get (← getOptions) then
|
||
unless (← isClass? type).isSome do
|
||
throwErrorAt binderView.type "invalid binder annotation, type is not a class instance{indentExpr type}\nuse the command `set_option checkBinderAnnotations false` to disable the check"
|
||
withLocalDecl binderView.id.getId binderView.bi type fun fvar => do
|
||
addLocalVarInfo binderView.id fvar
|
||
loop (i+1) (fvars.push fvar)
|
||
else
|
||
k fvars
|
||
loop 0 fvars
|
||
|
||
private partial def elabBindersAux {α} (binders : Array Syntax) (k : Array Expr → TermElabM α) : TermElabM α :=
|
||
let rec loop (i : Nat) (fvars : Array Expr) : TermElabM α := do
|
||
if h : i < binders.size then
|
||
let binderViews ← matchBinder (binders.get ⟨i, h⟩)
|
||
elabBinderViews binderViews fvars <| loop (i+1)
|
||
else
|
||
k fvars
|
||
loop 0 #[]
|
||
|
||
/--
|
||
Elaborate the given binders (i.e., `Syntax` objects for `simpleBinder <|> bracketedBinder`),
|
||
update the local context, set of local instances, reset instance chache (if needed), and then
|
||
execute `x` with the updated context. -/
|
||
def elabBinders {α} (binders : Array Syntax) (k : Array Expr → TermElabM α) : TermElabM α :=
|
||
withoutPostponingUniverseConstraints do
|
||
if binders.isEmpty then
|
||
k #[]
|
||
else
|
||
elabBindersAux binders k
|
||
|
||
def elabBinder {α} (binder : Syntax) (x : Expr → TermElabM α) : TermElabM α :=
|
||
elabBinders #[binder] fun fvars => x fvars[0]
|
||
|
||
@[builtinTermElab «forall»] def elabForall : TermElab := fun stx _ =>
|
||
match stx with
|
||
| `(forall $binders*, $term) =>
|
||
elabBinders binders fun xs => do
|
||
let e ← elabType term
|
||
mkForallFVars xs e
|
||
| _ => throwUnsupportedSyntax
|
||
|
||
@[builtinTermElab arrow] def elabArrow : TermElab := fun stx _ =>
|
||
match stx with
|
||
| `($dom:term -> $rng) => do
|
||
-- elaborate independently from each other
|
||
let dom ← elabType dom
|
||
let rng ← elabType rng
|
||
mkForall (← MonadQuotation.addMacroScope `a) BinderInfo.default dom rng
|
||
| _ => throwUnsupportedSyntax
|
||
|
||
@[builtinTermElab depArrow] def elabDepArrow : TermElab := fun stx _ =>
|
||
-- bracketedBinder `->` term
|
||
let binder := stx[0]
|
||
let term := stx[2]
|
||
elabBinders #[binder] fun xs => do
|
||
mkForallFVars xs (← elabType term)
|
||
|
||
/--
|
||
Auxiliary functions for converting `id_1 ... id_n` application into `#[id_1, ..., id_m]`
|
||
It is used at `expandFunBinders`. -/
|
||
private partial def getFunBinderIds? (stx : Syntax) : OptionT MacroM (Array Syntax) :=
|
||
let convertElem (stx : Syntax) : OptionT MacroM Syntax :=
|
||
match stx with
|
||
| `(_) => do let ident ← mkFreshIdent stx; pure ident
|
||
| `($id:ident) => return id
|
||
| _ => failure
|
||
match stx with
|
||
| `($f $args*) => do
|
||
let mut acc := #[].push (← convertElem f)
|
||
for arg in args do
|
||
acc := acc.push (← convertElem arg)
|
||
return acc
|
||
| _ =>
|
||
return #[].push (← convertElem stx)
|
||
|
||
/--
|
||
Auxiliary function for expanding `fun` notation binders. Recall that `fun` parser is defined as
|
||
```
|
||
def funBinder : Parser := implicitBinder <|> instBinder <|> termParser maxPrec
|
||
leading_parser unicodeSymbol "λ" "fun" >> many1 funBinder >> "=>" >> termParser
|
||
```
|
||
to allow notation such as `fun (a, b) => a + b`, where `(a, b)` should be treated as a pattern.
|
||
The result is a pair `(explicitBinders, newBody)`, where `explicitBinders` is syntax of the form
|
||
```
|
||
`(` ident `:` term `)`
|
||
```
|
||
which can be elaborated using `elabBinders`, and `newBody` is the updated `body` syntax.
|
||
We update the `body` syntax when expanding the pattern notation.
|
||
Example: `fun (a, b) => a + b` expands into `fun _a_1 => match _a_1 with | (a, b) => a + b`.
|
||
See local function `processAsPattern` at `expandFunBindersAux`.
|
||
|
||
The resulting `Bool` is true if a pattern was found. We use it "mark" a macro expansion. -/
|
||
partial def expandFunBinders (binders : Array Syntax) (body : Syntax) : MacroM (Array Syntax × Syntax × Bool) :=
|
||
let rec loop (body : Syntax) (i : Nat) (newBinders : Array Syntax) := do
|
||
if h : i < binders.size then
|
||
let binder := binders.get ⟨i, h⟩
|
||
let processAsPattern : Unit → MacroM (Array Syntax × Syntax × Bool) := fun _ => do
|
||
let pattern := binder
|
||
let major ← mkFreshIdent binder
|
||
let (binders, newBody, _) ← loop body (i+1) (newBinders.push $ mkExplicitBinder major (mkHole binder))
|
||
let newBody ← `(match $major:ident with | $pattern => $newBody)
|
||
pure (binders, newBody, true)
|
||
match binder with
|
||
| Syntax.node _ ``Lean.Parser.Term.implicitBinder _ => loop body (i+1) (newBinders.push binder)
|
||
| Syntax.node _ ``Lean.Parser.Term.strictImplicitBinder _ => loop body (i+1) (newBinders.push binder)
|
||
| Syntax.node _ ``Lean.Parser.Term.instBinder _ => loop body (i+1) (newBinders.push binder)
|
||
| Syntax.node _ ``Lean.Parser.Term.explicitBinder _ => loop body (i+1) (newBinders.push binder)
|
||
| Syntax.node _ ``Lean.Parser.Term.simpleBinder _ => loop body (i+1) (newBinders.push binder)
|
||
| Syntax.node _ ``Lean.Parser.Term.hole _ =>
|
||
let ident ← mkFreshIdent binder
|
||
let type := binder
|
||
loop body (i+1) (newBinders.push <| mkExplicitBinder ident type)
|
||
| Syntax.node _ ``Lean.Parser.Term.paren args =>
|
||
-- `(` (termParser >> parenSpecial)? `)`
|
||
-- parenSpecial := (tupleTail <|> typeAscription)?
|
||
let binderBody := binder[1]
|
||
if binderBody.isNone then
|
||
processAsPattern ()
|
||
else
|
||
let idents := binderBody[0]
|
||
let special := binderBody[1]
|
||
if special.isNone then
|
||
processAsPattern ()
|
||
else if special[0].getKind != `Lean.Parser.Term.typeAscription then
|
||
processAsPattern ()
|
||
else
|
||
-- typeAscription := `:` term
|
||
let type := special[0][1]
|
||
match (← getFunBinderIds? idents) with
|
||
| some idents => loop body (i+1) (newBinders ++ idents.map (fun ident => mkExplicitBinder ident type))
|
||
| none => processAsPattern ()
|
||
| Syntax.ident .. =>
|
||
let type := mkHole binder
|
||
loop body (i+1) (newBinders.push <| mkExplicitBinder binder type)
|
||
| _ => processAsPattern ()
|
||
else
|
||
pure (newBinders, body, false)
|
||
loop body 0 #[]
|
||
|
||
namespace FunBinders
|
||
|
||
structure State where
|
||
fvars : Array Expr := #[]
|
||
lctx : LocalContext
|
||
localInsts : LocalInstances
|
||
expectedType? : Option Expr := none
|
||
|
||
private def propagateExpectedType (fvar : Expr) (fvarType : Expr) (s : State) : TermElabM State := do
|
||
match s.expectedType? with
|
||
| none => pure s
|
||
| some expectedType =>
|
||
let expectedType ← whnfForall expectedType
|
||
match expectedType with
|
||
| Expr.forallE _ d b _ =>
|
||
discard <| isDefEq fvarType d
|
||
let b := b.instantiate1 fvar
|
||
pure { s with expectedType? := some b }
|
||
| _ =>
|
||
pure { s with expectedType? := none }
|
||
|
||
private partial def elabFunBinderViews (binderViews : Array BinderView) (i : Nat) (s : State) : TermElabM State := do
|
||
if h : i < binderViews.size then
|
||
let binderView := binderViews.get ⟨i, h⟩
|
||
ensureAtomicBinderName binderView
|
||
withRef binderView.type <| withLCtx s.lctx s.localInsts do
|
||
let type ← elabType binderView.type
|
||
registerFailedToInferBinderTypeInfo type binderView.type
|
||
let fvarId ← mkFreshFVarId
|
||
let fvar := mkFVar fvarId
|
||
let s := { s with fvars := s.fvars.push fvar }
|
||
-- dbgTrace (toString binderView.id.getId ++ " : " ++ toString type)
|
||
/-
|
||
We do **not** want to support default and auto arguments in lambda abstractions.
|
||
Example: `fun (x : Nat := 10) => x+1`.
|
||
We do not believe this is an useful feature, and it would complicate the logic here.
|
||
-/
|
||
let lctx := s.lctx.mkLocalDecl fvarId binderView.id.getId type binderView.bi
|
||
addTermInfo (lctx? := some lctx) (isBinder := true) binderView.id fvar
|
||
let s ← withRef binderView.id <| propagateExpectedType fvar type s
|
||
let s := { s with lctx := lctx }
|
||
match (← isClass? type) with
|
||
| none => elabFunBinderViews binderViews (i+1) s
|
||
| some className =>
|
||
resettingSynthInstanceCache do
|
||
let localInsts := s.localInsts.push { className := className, fvar := mkFVar fvarId }
|
||
elabFunBinderViews binderViews (i+1) { s with localInsts := localInsts }
|
||
else
|
||
pure s
|
||
|
||
partial def elabFunBindersAux (binders : Array Syntax) (i : Nat) (s : State) : TermElabM State := do
|
||
if h : i < binders.size then
|
||
let binderViews ← matchBinder (binders.get ⟨i, h⟩)
|
||
let s ← elabFunBinderViews binderViews 0 s
|
||
elabFunBindersAux binders (i+1) s
|
||
else
|
||
pure s
|
||
|
||
end FunBinders
|
||
|
||
def elabFunBinders {α} (binders : Array Syntax) (expectedType? : Option Expr) (x : Array Expr → Option Expr → TermElabM α) : TermElabM α :=
|
||
if binders.isEmpty then
|
||
x #[] expectedType?
|
||
else do
|
||
let lctx ← getLCtx
|
||
let localInsts ← getLocalInstances
|
||
let s ← FunBinders.elabFunBindersAux binders 0 { lctx := lctx, localInsts := localInsts, expectedType? := expectedType? }
|
||
resettingSynthInstanceCacheWhen (s.localInsts.size > localInsts.size) <| withLCtx s.lctx s.localInsts <|
|
||
x s.fvars s.expectedType?
|
||
|
||
/- Helper function for `expandEqnsIntoMatch` -/
|
||
private def getMatchAltsNumPatterns (matchAlts : Syntax) : Nat :=
|
||
let alt0 := matchAlts[0][0]
|
||
let pats := alt0[1].getSepArgs
|
||
pats.size
|
||
|
||
def expandWhereDecls (whereDecls : Syntax) (body : Syntax) : MacroM Syntax :=
|
||
match whereDecls with
|
||
| `(whereDecls|where $[$decls:letRecDecl $[;]?]*) => `(let rec $decls:letRecDecl,*; $body)
|
||
| _ => Macro.throwUnsupported
|
||
|
||
def expandWhereDeclsOpt (whereDeclsOpt : Syntax) (body : Syntax) : MacroM Syntax :=
|
||
if whereDeclsOpt.isNone then
|
||
body
|
||
else
|
||
expandWhereDecls whereDeclsOpt[0] body
|
||
|
||
/- Helper function for `expandMatchAltsIntoMatch` -/
|
||
private def expandMatchAltsIntoMatchAux (matchAlts : Syntax) (matchTactic : Bool) : Nat → Array Syntax → MacroM Syntax
|
||
| 0, discrs => do
|
||
if matchTactic then
|
||
`(tactic|match $[$discrs:term],* with $matchAlts:matchAlts)
|
||
else
|
||
`(match $[$discrs:term],* with $matchAlts:matchAlts)
|
||
| n+1, discrs => withFreshMacroScope do
|
||
let x ← `(x)
|
||
let d ← `(@$x:ident) -- See comment below
|
||
let body ← expandMatchAltsIntoMatchAux matchAlts matchTactic n (discrs.push d)
|
||
if matchTactic then
|
||
`(tactic| intro $x:term; $body:tactic)
|
||
else
|
||
`(@fun $x => $body)
|
||
|
||
/--
|
||
Expand `matchAlts` syntax into a full `match`-expression.
|
||
Example
|
||
```
|
||
| 0, true => alt_1
|
||
| i, _ => alt_2
|
||
```
|
||
expands into (for tactic == false)
|
||
```
|
||
fun x_1 x_2 =>
|
||
match @x_1, @x_2 with
|
||
| 0, true => alt_1
|
||
| i, _ => alt_2
|
||
```
|
||
and (for tactic == true)
|
||
```
|
||
intro x_1; intro x_2;
|
||
match @x_1, @x_2 with
|
||
| 0, true => alt_1
|
||
| i, _ => alt_2
|
||
```
|
||
|
||
Remark: we add `@` to make sure we don't consume implicit arguments, and to make the behavior consistent with `fun`.
|
||
Example:
|
||
```
|
||
inductive T : Type 1 :=
|
||
| mkT : (forall {a : Type}, a -> a) -> T
|
||
|
||
def makeT (f : forall {a : Type}, a -> a) : T :=
|
||
mkT f
|
||
|
||
def makeT' : (forall {a : Type}, a -> a) -> T
|
||
| f => mkT f
|
||
```
|
||
The two definitions should be elaborated without errors and be equivalent.
|
||
-/
|
||
def expandMatchAltsIntoMatch (ref : Syntax) (matchAlts : Syntax) (tactic := false) : MacroM Syntax :=
|
||
withRef ref <| expandMatchAltsIntoMatchAux matchAlts tactic (getMatchAltsNumPatterns matchAlts) #[]
|
||
|
||
def expandMatchAltsIntoMatchTactic (ref : Syntax) (matchAlts : Syntax) : MacroM Syntax :=
|
||
withRef ref <| expandMatchAltsIntoMatchAux matchAlts true (getMatchAltsNumPatterns matchAlts) #[]
|
||
|
||
/--
|
||
Similar to `expandMatchAltsIntoMatch`, but supports an optional `where` clause.
|
||
|
||
Expand `matchAltsWhereDecls` into `let rec` + `match`-expression.
|
||
Example
|
||
```
|
||
| 0, true => ... f 0 ...
|
||
| i, _ => ... f i + g i ...
|
||
where
|
||
f x := g x + 1
|
||
|
||
g : Nat → Nat
|
||
| 0 => 1
|
||
| x+1 => f x
|
||
```
|
||
expands into
|
||
```
|
||
fux x_1 x_2 =>
|
||
let rec
|
||
f x := g x + 1,
|
||
g : Nat → Nat
|
||
| 0 => 1
|
||
| x+1 => f x
|
||
match x_1, x_2 with
|
||
| 0, true => ... f 0 ...
|
||
| i, _ => ... f i + g i ...
|
||
```
|
||
-/
|
||
def expandMatchAltsWhereDecls (matchAltsWhereDecls : Syntax) : MacroM Syntax :=
|
||
let matchAlts := matchAltsWhereDecls[0]
|
||
let whereDeclsOpt := matchAltsWhereDecls[1]
|
||
let rec loop (i : Nat) (discrs : Array Syntax) : MacroM Syntax :=
|
||
match i with
|
||
| 0 => do
|
||
let matchStx ← `(match $[$discrs:term],* with $matchAlts:matchAlts)
|
||
if whereDeclsOpt.isNone then
|
||
return matchStx
|
||
else
|
||
expandWhereDeclsOpt whereDeclsOpt matchStx
|
||
| n+1 => withFreshMacroScope do
|
||
let d ← `(@x) -- See comment at `expandMatchAltsIntoMatch`
|
||
let body ← loop n (discrs.push d)
|
||
`(@fun x => $body)
|
||
loop (getMatchAltsNumPatterns matchAlts) #[]
|
||
|
||
@[builtinMacro Lean.Parser.Term.fun] partial def expandFun : Macro
|
||
| `(fun $binders* => $body) => do
|
||
let (binders, body, expandedPattern) ← expandFunBinders binders body
|
||
if expandedPattern then
|
||
`(fun $binders* => $body)
|
||
else
|
||
Macro.throwUnsupported
|
||
| stx@`(fun $m:matchAlts) => expandMatchAltsIntoMatch stx m
|
||
| _ => Macro.throwUnsupported
|
||
|
||
open Lean.Elab.Term.Quotation in
|
||
@[builtinQuotPrecheck Lean.Parser.Term.fun] def precheckFun : Precheck
|
||
| `(fun $binders* => $body) => do
|
||
let (binders, body, expandedPattern) ← liftMacroM <| expandFunBinders binders body
|
||
let mut ids := #[]
|
||
for b in binders do
|
||
for v in ← matchBinder b do
|
||
Quotation.withNewLocals ids <| precheck v.type
|
||
ids := ids.push v.id.getId
|
||
Quotation.withNewLocals ids <| precheck body
|
||
| _ => throwUnsupportedSyntax
|
||
|
||
@[builtinTermElab «fun»] partial def elabFun : TermElab := fun stx expectedType? =>
|
||
match stx with
|
||
| `(fun $binders* => $body) => do
|
||
-- We can assume all `match` binders have been iteratively expanded by the above macro here, though
|
||
-- we still need to call `expandFunBinders` once to obtain `binders` in a normal form
|
||
-- expected by `elabFunBinder`.
|
||
let (binders, body, expandedPattern) ← liftMacroM <| expandFunBinders binders body
|
||
elabFunBinders binders expectedType? fun xs expectedType? => do
|
||
/- We ensure the expectedType here since it will force coercions to be applied if needed.
|
||
If we just use `elabTerm`, then we will need to a coercion `Coe (α → β) (α → δ)` whenever there is a coercion `Coe β δ`,
|
||
and another instance for the dependent version. -/
|
||
let e ← elabTermEnsuringType body expectedType?
|
||
mkLambdaFVars xs e
|
||
| _ => throwUnsupportedSyntax
|
||
|
||
/- If `useLetExpr` is true, then a kernel let-expression `let x : type := val; body` is created.
|
||
Otherwise, we create a term of the form `(fun (x : type) => body) val`
|
||
|
||
The default elaboration order is `binders`, `typeStx`, `valStx`, and `body`.
|
||
If `elabBodyFirst == true`, then we use the order `binders`, `typeStx`, `body`, and `valStx`. -/
|
||
def elabLetDeclAux (id : Syntax) (binders : Array Syntax) (typeStx : Syntax) (valStx : Syntax) (body : Syntax)
|
||
(expectedType? : Option Expr) (useLetExpr : Bool) (elabBodyFirst : Bool) (usedLetOnly : Bool) : TermElabM Expr := do
|
||
let (type, val, arity) ← elabBinders binders fun xs => do
|
||
let type ← elabType typeStx
|
||
registerCustomErrorIfMVar type typeStx "failed to infer 'let' declaration type"
|
||
if elabBodyFirst then
|
||
let type ← mkForallFVars xs type
|
||
let val ← mkFreshExprMVar type
|
||
pure (type, val, xs.size)
|
||
else
|
||
let val ← elabTermEnsuringType valStx type
|
||
let type ← mkForallFVars xs type
|
||
/- By default `mkLambdaFVars` and `mkLetFVars` create binders only for let-declarations that are actually used
|
||
in the body. This generates counterintuitive behavior in the elaborator since users will not be notified
|
||
about holes such as
|
||
```
|
||
def ex : Nat :=
|
||
let x := _
|
||
42
|
||
```
|
||
-/
|
||
let val ← mkLambdaFVars xs val (usedLetOnly := false)
|
||
pure (type, val, xs.size)
|
||
trace[Elab.let.decl] "{id.getId} : {type} := {val}"
|
||
let result ←
|
||
if useLetExpr then
|
||
withLetDecl id.getId type val fun x => do
|
||
addLocalVarInfo id x
|
||
let body ← elabTermEnsuringType body expectedType?
|
||
let body ← instantiateMVars body
|
||
mkLetFVars #[x] body (usedLetOnly := usedLetOnly)
|
||
else
|
||
let f ← withLocalDecl id.getId BinderInfo.default type fun x => do
|
||
addLocalVarInfo id x
|
||
let body ← elabTermEnsuringType body expectedType?
|
||
let body ← instantiateMVars body
|
||
mkLambdaFVars #[x] body (usedLetOnly := false)
|
||
pure <| mkLetFunAnnotation (mkApp f val)
|
||
if elabBodyFirst then
|
||
forallBoundedTelescope type arity fun xs type => do
|
||
let valResult ← elabTermEnsuringType valStx type
|
||
let valResult ← mkLambdaFVars xs valResult (usedLetOnly := false)
|
||
unless (← isDefEq val valResult) do
|
||
throwError "unexpected error when elaborating 'let'"
|
||
pure result
|
||
|
||
structure LetIdDeclView where
|
||
id : Syntax
|
||
binders : Array Syntax
|
||
type : Syntax
|
||
value : Syntax
|
||
|
||
def mkLetIdDeclView (letIdDecl : Syntax) : LetIdDeclView :=
|
||
-- `letIdDecl` is of the form `ident >> many bracketedBinder >> optType >> " := " >> termParser
|
||
let id := letIdDecl[0]
|
||
let binders := letIdDecl[1].getArgs
|
||
let optType := letIdDecl[2]
|
||
let type := expandOptType id optType
|
||
let value := letIdDecl[4]
|
||
{ id := id, binders := binders, type := type, value := value }
|
||
|
||
def expandLetEqnsDecl (letDecl : Syntax) : MacroM Syntax := do
|
||
let ref := letDecl
|
||
let matchAlts := letDecl[3]
|
||
let val ← expandMatchAltsIntoMatch ref matchAlts
|
||
return mkNode `Lean.Parser.Term.letIdDecl #[letDecl[0], letDecl[1], letDecl[2], mkAtomFrom ref " := ", val]
|
||
|
||
def elabLetDeclCore (stx : Syntax) (expectedType? : Option Expr) (useLetExpr : Bool) (elabBodyFirst : Bool) (usedLetOnly : Bool) : TermElabM Expr := do
|
||
let ref := stx
|
||
let letDecl := stx[1][0]
|
||
let body := stx[3]
|
||
if letDecl.getKind == `Lean.Parser.Term.letIdDecl then
|
||
let { id := id, binders := binders, type := type, value := val } := mkLetIdDeclView letDecl
|
||
elabLetDeclAux id binders type val body expectedType? useLetExpr elabBodyFirst usedLetOnly
|
||
else if letDecl.getKind == `Lean.Parser.Term.letPatDecl then
|
||
-- node `Lean.Parser.Term.letPatDecl $ try (termParser >> pushNone >> optType >> " := ") >> termParser
|
||
let pat := letDecl[0]
|
||
let optType := letDecl[2]
|
||
let type := expandOptType pat optType
|
||
let val := letDecl[4]
|
||
let stxNew ← `(let x : $type := $val; match x with | $pat => $body)
|
||
let stxNew := match useLetExpr, elabBodyFirst with
|
||
| true, false => stxNew
|
||
| true, true => stxNew.setKind `Lean.Parser.Term.«let_delayed»
|
||
| false, false => stxNew.setKind `Lean.Parser.Term.«let_fun»
|
||
| false, true => unreachable!
|
||
withMacroExpansion stx stxNew <| elabTerm stxNew expectedType?
|
||
else if letDecl.getKind == `Lean.Parser.Term.letEqnsDecl then
|
||
let letDeclIdNew ← liftMacroM <| expandLetEqnsDecl letDecl
|
||
let declNew := stx[1].setArg 0 letDeclIdNew
|
||
let stxNew := stx.setArg 1 declNew
|
||
withMacroExpansion stx stxNew <| elabTerm stxNew expectedType?
|
||
else
|
||
throwUnsupportedSyntax
|
||
|
||
@[builtinTermElab «let»] def elabLetDecl : TermElab :=
|
||
fun stx expectedType? => elabLetDeclCore stx expectedType? (useLetExpr := true) (elabBodyFirst := false) (usedLetOnly := false)
|
||
|
||
@[builtinTermElab «let_fun»] def elabLetFunDecl : TermElab :=
|
||
fun stx expectedType? => elabLetDeclCore stx expectedType? (useLetExpr := false) (elabBodyFirst := false) (usedLetOnly := false)
|
||
|
||
@[builtinTermElab «let_delayed»] def elabLetDelayedDecl : TermElab :=
|
||
fun stx expectedType? => elabLetDeclCore stx expectedType? (useLetExpr := true) (elabBodyFirst := true) (usedLetOnly := false)
|
||
|
||
@[builtinTermElab «let_tmp»] def elabLetTmpDecl : TermElab :=
|
||
fun stx expectedType? => elabLetDeclCore stx expectedType? (useLetExpr := true) (elabBodyFirst := false) (usedLetOnly := true)
|
||
|
||
builtin_initialize registerTraceClass `Elab.let
|
||
|
||
end Lean.Elab.Term
|