985 lines
46 KiB
Text
985 lines
46 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.Util.FindMVar
|
||
import Lean.Parser.Term
|
||
import Lean.Elab.Term
|
||
import Lean.Elab.Binders
|
||
import Lean.Elab.SyntheticMVars
|
||
import Lean.Elab.Arg
|
||
import Lean.Elab.RecAppSyntax
|
||
|
||
namespace Lean.Elab.Term
|
||
open Meta
|
||
|
||
builtin_initialize elabWithoutExpectedTypeAttr : TagAttribute ←
|
||
registerTagAttribute `elabWithoutExpectedType "mark that applications of the given declaration should be elaborated without the expected type"
|
||
|
||
def hasElabWithoutExpectedType (env : Environment) (declName : Name) : Bool :=
|
||
elabWithoutExpectedTypeAttr.hasTag env declName
|
||
|
||
instance : ToString Arg := ⟨fun
|
||
| Arg.stx val => toString val
|
||
| Arg.expr val => toString val⟩
|
||
|
||
instance : ToString NamedArg where
|
||
toString s := "(" ++ toString s.name ++ " := " ++ toString s.val ++ ")"
|
||
|
||
def throwInvalidNamedArg {α} (namedArg : NamedArg) (fn? : Option Name) : TermElabM α :=
|
||
withRef namedArg.ref <| match fn? with
|
||
| some fn => throwError "invalid argument name '{namedArg.name}' for function '{fn}'"
|
||
| none => throwError "invalid argument name '{namedArg.name}' for function"
|
||
|
||
private def ensureArgType (f : Expr) (arg : Expr) (expectedType : Expr) : TermElabM Expr := do
|
||
let argType ← inferType arg
|
||
ensureHasTypeAux expectedType argType arg f
|
||
|
||
private def mkProjAndCheck (structName : Name) (idx : Nat) (e : Expr) : MetaM Expr := do
|
||
let r := mkProj structName idx e
|
||
let eType ← inferType e
|
||
if (← isProp eType) then
|
||
let rType ← inferType r
|
||
if !(← isProp rType) then
|
||
throwError "invalid projection, the expression{indentExpr e}\nis a proposition and has type{indentExpr eType}\nbut the projected value is not, it has type{indentExpr rType}"
|
||
return r
|
||
|
||
/-
|
||
Relevant definitions:
|
||
```
|
||
class CoeFun (α : Sort u) (γ : α → outParam (Sort v))
|
||
```
|
||
-/
|
||
private def tryCoeFun? (α : Expr) (a : Expr) : TermElabM (Option Expr) := do
|
||
let v ← mkFreshLevelMVar
|
||
let type ← mkArrow α (mkSort v)
|
||
let γ ← mkFreshExprMVar type
|
||
let u ← getLevel α
|
||
let coeFunInstType := mkAppN (Lean.mkConst ``CoeFun [u, v]) #[α, γ]
|
||
let mvar ← mkFreshExprMVar coeFunInstType MetavarKind.synthetic
|
||
let mvarId := mvar.mvarId!
|
||
try
|
||
if (← synthesizeCoeInstMVarCore mvarId) then
|
||
expandCoe <| mkAppN (Lean.mkConst ``CoeFun.coe [u, v]) #[α, γ, mvar, a]
|
||
else
|
||
return none
|
||
catch _ =>
|
||
return none
|
||
|
||
def synthesizeAppInstMVars (instMVars : Array MVarId) (app : Expr) : TermElabM Unit :=
|
||
for mvarId in instMVars do
|
||
unless (← synthesizeInstMVarCore mvarId) do
|
||
registerSyntheticMVarWithCurrRef mvarId SyntheticMVarKind.typeClass
|
||
registerMVarErrorImplicitArgInfo mvarId (← getRef) app
|
||
|
||
namespace ElabAppArgs
|
||
|
||
/- Auxiliary structure for elaborating the application `f args namedArgs`. -/
|
||
structure State where
|
||
explicit : Bool -- true if `@` modifier was used
|
||
f : Expr
|
||
fType : Expr
|
||
args : List Arg -- remaining regular arguments
|
||
namedArgs : List NamedArg -- remaining named arguments to be processed
|
||
ellipsis : Bool := false
|
||
expectedType? : Option Expr
|
||
etaArgs : Array Expr := #[]
|
||
toSetErrorCtx : Array MVarId := #[] -- metavariables that we need the set the error context using the application being built
|
||
instMVars : Array MVarId := #[] -- metavariables for the instance implicit arguments that have already been processed
|
||
-- The following field is used to implement the `propagateExpectedType` heuristic.
|
||
propagateExpected : Bool -- true when expectedType has not been propagated yet
|
||
|
||
abbrev M := StateRefT State TermElabM
|
||
|
||
/- Add the given metavariable to the collection of metavariables associated with instance-implicit arguments. -/
|
||
private def addInstMVar (mvarId : MVarId) : M Unit :=
|
||
modify fun s => { s with instMVars := s.instMVars.push mvarId }
|
||
|
||
/-
|
||
Try to synthesize metavariables are `instMVars` using type class resolution.
|
||
The ones that cannot be synthesized yet are registered.
|
||
Remark: we use this method before trying to apply coercions to function. -/
|
||
def synthesizeAppInstMVars : M Unit := do
|
||
let s ← get
|
||
let instMVars := s.instMVars
|
||
modify fun s => { s with instMVars := #[] }
|
||
Lean.Elab.Term.synthesizeAppInstMVars instMVars s.f
|
||
|
||
/- fType may become a forallE after we synthesize pending metavariables. -/
|
||
private def synthesizePendingAndNormalizeFunType : M Unit := do
|
||
synthesizeAppInstMVars
|
||
synthesizeSyntheticMVars
|
||
let s ← get
|
||
let fType ← whnfForall s.fType
|
||
if fType.isForall then
|
||
modify fun s => { s with fType := fType }
|
||
else
|
||
match (← tryCoeFun? fType s.f) with
|
||
| some f =>
|
||
let fType ← inferType f
|
||
modify fun s => { s with f := f, fType := fType }
|
||
| none =>
|
||
for namedArg in s.namedArgs do
|
||
let f := s.f.getAppFn
|
||
if f.isConst then
|
||
throwInvalidNamedArg namedArg f.constName!
|
||
else
|
||
throwInvalidNamedArg namedArg none
|
||
throwError "function expected at{indentExpr s.f}\nterm has type{indentExpr fType}"
|
||
|
||
/- Normalize and return the function type. -/
|
||
private def normalizeFunType : M Expr := do
|
||
let s ← get
|
||
let fType ← whnfForall s.fType
|
||
modify fun s => { s with fType := fType }
|
||
pure fType
|
||
|
||
/- Return the binder name at `fType`. This method assumes `fType` is a function type. -/
|
||
private def getBindingName : M Name := return (← get).fType.bindingName!
|
||
|
||
/- Return the next argument expected type. This method assumes `fType` is a function type. -/
|
||
private def getArgExpectedType : M Expr := return (← get).fType.bindingDomain!
|
||
|
||
def eraseNamedArgCore (namedArgs : List NamedArg) (binderName : Name) : List NamedArg :=
|
||
namedArgs.filter (·.name != binderName)
|
||
|
||
/- Remove named argument with name `binderName` from `namedArgs`. -/
|
||
def eraseNamedArg (binderName : Name) : M Unit :=
|
||
modify fun s => { s with namedArgs := eraseNamedArgCore s.namedArgs binderName }
|
||
|
||
/-
|
||
Add a new argument to the result. That is, `f := f arg`, update `fType`.
|
||
This method assumes `fType` is a function type. -/
|
||
private def addNewArg (argName : Name) (arg : Expr) : M Unit := do
|
||
modify fun s => { s with f := mkApp s.f arg, fType := s.fType.bindingBody!.instantiate1 arg }
|
||
if arg.isMVar then
|
||
let mvarId := arg.mvarId!
|
||
if let some mvarErrorInfo ← getMVarErrorInfo? mvarId then
|
||
registerMVarErrorInfo { mvarErrorInfo with argName? := argName }
|
||
|
||
/-
|
||
Elaborate the given `Arg` and add it to the result. See `addNewArg`.
|
||
Recall that, `Arg` may be wrapping an already elaborated `Expr`. -/
|
||
private def elabAndAddNewArg (argName : Name) (arg : Arg) : M Unit := do
|
||
let s ← get
|
||
let expectedType := (← getArgExpectedType).consumeTypeAnnotations
|
||
match arg with
|
||
| Arg.expr val =>
|
||
let arg ← ensureArgType s.f val expectedType
|
||
addNewArg argName arg
|
||
| Arg.stx val =>
|
||
let val ← elabTerm val expectedType
|
||
let arg ← ensureArgType s.f val expectedType
|
||
addNewArg argName arg
|
||
|
||
/- Return true if the given type contains `OptParam` or `AutoParams` -/
|
||
private def hasOptAutoParams (type : Expr) : M Bool := do
|
||
forallTelescopeReducing type fun xs type =>
|
||
xs.anyM fun x => do
|
||
let xType ← inferType x
|
||
return xType.getOptParamDefault?.isSome || xType.getAutoParamTactic?.isSome
|
||
|
||
/- Return true if `fType` contains `OptParam` or `AutoParams` -/
|
||
private def fTypeHasOptAutoParams : M Bool := do
|
||
hasOptAutoParams (← get).fType
|
||
|
||
/- Auxiliary function for retrieving the resulting type of a function application.
|
||
See `propagateExpectedType`.
|
||
|
||
Remark: `(explicit : Bool) == true` when `@` modifier is used. -/
|
||
private partial def getForallBody (explicit : Bool) : Nat → List NamedArg → Expr → Option Expr
|
||
| i, namedArgs, type@(Expr.forallE n d b c) =>
|
||
match namedArgs.find? fun (namedArg : NamedArg) => namedArg.name == n with
|
||
| some _ => getForallBody explicit i (eraseNamedArgCore namedArgs n) b
|
||
| none =>
|
||
if !explicit && !c.binderInfo.isExplicit then
|
||
getForallBody explicit i namedArgs b
|
||
else if i > 0 then
|
||
getForallBody explicit (i-1) namedArgs b
|
||
else if d.isAutoParam || d.isOptParam then
|
||
getForallBody explicit i namedArgs b
|
||
else
|
||
some type
|
||
| 0, [], type => some type
|
||
| _, _, _ => none
|
||
|
||
private def shouldPropagateExpectedTypeFor (nextArg : Arg) : Bool :=
|
||
match nextArg with
|
||
| Arg.expr _ => false -- it has already been elaborated
|
||
| Arg.stx stx =>
|
||
-- TODO: make this configurable?
|
||
stx.getKind != ``Lean.Parser.Term.hole &&
|
||
stx.getKind != ``Lean.Parser.Term.syntheticHole &&
|
||
stx.getKind != ``Lean.Parser.Term.byTactic
|
||
|
||
/-
|
||
Auxiliary method for propagating the expected type. We call it as soon as we find the first explict
|
||
argument. The goal is to propagate the expected type in applications of functions such as
|
||
```lean
|
||
Add.add {α : Type u} : α → α → α
|
||
List.cons {α : Type u} : α → List α → List α
|
||
```
|
||
This is particularly useful when there applicable coercions. For example,
|
||
assume we have a coercion from `Nat` to `Int`, and we have
|
||
`(x : Nat)` and the expected type is `List Int`. Then, if we don't use this function,
|
||
the elaborator will fail to elaborate
|
||
```
|
||
List.cons x []
|
||
```
|
||
First, the elaborator creates a new metavariable `?α` for the implicit argument `{α : Type u}`.
|
||
Then, when it processes `x`, it assigns `?α := Nat`, and then obtain the
|
||
resultant type `List Nat` which is **not** definitionally equal to `List Int`.
|
||
We solve the problem by executing this method before we elaborate the first explicit argument (`x` in this example).
|
||
This method infers that the resultant type is `List ?α` and unifies it with `List Int`.
|
||
Then, when we elaborate `x`, the elaborate realizes the coercion from `Nat` to `Int` must be used, and the
|
||
term
|
||
```
|
||
@List.cons Int (coe x) (@List.nil Int)
|
||
```
|
||
is produced.
|
||
|
||
The method will do nothing if
|
||
1- The resultant type depends on the remaining arguments (i.e., `!eTypeBody.hasLooseBVars`).
|
||
2- The resultant type contains optional/auto params.
|
||
|
||
We have considered adding the following extra conditions
|
||
a) The resultant type does not contain any type metavariable.
|
||
b) The resultant type contains a nontype metavariable.
|
||
|
||
These two conditions would restrict the method to simple functions that are "morally" in
|
||
the Hindley&Milner fragment.
|
||
If users need to disable expected type propagation, we can add an attribute `[elabWithoutExpectedType]`.
|
||
-/
|
||
private def propagateExpectedType (arg : Arg) : M Unit := do
|
||
if shouldPropagateExpectedTypeFor arg then
|
||
let s ← get
|
||
-- TODO: handle s.etaArgs.size > 0
|
||
unless !s.etaArgs.isEmpty || !s.propagateExpected do
|
||
match s.expectedType? with
|
||
| none => pure ()
|
||
| some expectedType =>
|
||
/- We don't propagate `Prop` because we often use `Prop` as a more general "Bool" (e.g., `if-then-else`).
|
||
If we propagate `expectedType == Prop` in the following examples, the elaborator would fail
|
||
```
|
||
def f1 (s : Nat × Bool) : Bool := if s.2 then false else true
|
||
|
||
def f2 (s : List Bool) : Bool := if s.head! then false else true
|
||
|
||
def f3 (s : List Bool) : Bool := if List.head! (s.map not) then false else true
|
||
```
|
||
They would all fail for the same reason. So, let's focus on the first one.
|
||
We would elaborate `s.2` with `expectedType == Prop`.
|
||
Before we elaborate `s`, this method would be invoked, and `s.fType` is `?α × ?β → ?β` and after
|
||
propagation we would have `?α × Prop → Prop`. Then, when we would try to elaborate `s`, and
|
||
get a type error because `?α × Prop` cannot be unified with `Nat × Bool`
|
||
Most users would have a hard time trying to understand why these examples failed.
|
||
|
||
Here is a possible alternative workarounds. We give up the idea of using `Prop` at `if-then-else`.
|
||
Drawback: users use `if-then-else` with conditions that are not Decidable.
|
||
So, users would have to embrace `propDecidable` and `choice`.
|
||
This may not be that bad since the developers and users don't seem to care about constructivism.
|
||
|
||
We currently use a different workaround, we just don't propagate the expected type when it is `Prop`. -/
|
||
if expectedType.isProp then
|
||
modify fun s => { s with propagateExpected := false }
|
||
else
|
||
let numRemainingArgs := s.args.length
|
||
trace[Elab.app.propagateExpectedType] "etaArgs.size: {s.etaArgs.size}, numRemainingArgs: {numRemainingArgs}, fType: {s.fType}"
|
||
match getForallBody s.explicit numRemainingArgs s.namedArgs s.fType with
|
||
| none => pure ()
|
||
| some fTypeBody =>
|
||
unless fTypeBody.hasLooseBVars do
|
||
unless (← hasOptAutoParams fTypeBody) do
|
||
trace[Elab.app.propagateExpectedType] "{expectedType} =?= {fTypeBody}"
|
||
if (← isDefEq expectedType fTypeBody) then
|
||
/- Note that we only set `propagateExpected := false` when propagation has succeeded. -/
|
||
modify fun s => { s with propagateExpected := false }
|
||
|
||
|
||
/- This method execute after all application arguments have been processed. -/
|
||
private def finalize : M Expr := do
|
||
let s ← get
|
||
let mut e := s.f
|
||
-- all user explicit arguments have been consumed
|
||
trace[Elab.app.finalize] e
|
||
let ref ← getRef
|
||
-- Register the error context of implicits
|
||
for mvarId in s.toSetErrorCtx do
|
||
registerMVarErrorImplicitArgInfo mvarId ref e
|
||
if !s.etaArgs.isEmpty then
|
||
e ← mkLambdaFVars s.etaArgs e
|
||
/-
|
||
Remark: we should not use `s.fType` as `eType` even when
|
||
`s.etaArgs.isEmpty`. Reason: it may have been unfolded.
|
||
-/
|
||
let eType ← inferType e
|
||
trace[Elab.app.finalize] "after etaArgs, {e} : {eType}"
|
||
match s.expectedType? with
|
||
| none => pure ()
|
||
| some expectedType =>
|
||
trace[Elab.app.finalize] "expected type: {expectedType}"
|
||
-- Try to propagate expected type. Ignore if types are not definitionally equal, caller must handle it.
|
||
discard <| isDefEq expectedType eType
|
||
synthesizeAppInstMVars
|
||
pure e
|
||
|
||
/- Return true if there is a named argument that depends on the next argument. -/
|
||
private def anyNamedArgDependsOnCurrent : M Bool := do
|
||
let s ← get
|
||
if s.namedArgs.isEmpty then
|
||
return false
|
||
else
|
||
forallTelescopeReducing s.fType fun xs _ => do
|
||
let curr := xs[0]
|
||
for i in [1:xs.size] do
|
||
let xDecl ← getLocalDecl xs[i].fvarId!
|
||
if s.namedArgs.any fun arg => arg.name == xDecl.userName then
|
||
if (← getMCtx).localDeclDependsOn xDecl curr.fvarId! then
|
||
return true
|
||
return false
|
||
|
||
|
||
/- Return true if there are regular or named arguments to be processed. -/
|
||
private def hasArgsToProcess : M Bool := do
|
||
let s ← get
|
||
return !s.args.isEmpty || !s.namedArgs.isEmpty
|
||
|
||
/- Return true if the next argument at `args` is of the form `_` -/
|
||
private def isNextArgHole : M Bool := do
|
||
match (← get).args with
|
||
| Arg.stx (Syntax.node _ ``Lean.Parser.Term.hole _) :: _ => pure true
|
||
| _ => pure false
|
||
|
||
|
||
mutual
|
||
/-
|
||
Create a fresh local variable with the current binder name and argument type, add it to `etaArgs` and `f`,
|
||
and then execute the main loop.-/
|
||
private partial def addEtaArg (argName : Name) : M Expr := do
|
||
let n ← getBindingName
|
||
let type ← getArgExpectedType
|
||
withLocalDeclD n type fun x => do
|
||
modify fun s => { s with etaArgs := s.etaArgs.push x }
|
||
addNewArg argName x
|
||
main
|
||
|
||
private partial def addImplicitArg (argName : Name) : M Expr := do
|
||
let argType ← getArgExpectedType
|
||
let arg ← mkFreshExprMVar argType
|
||
modify fun s => { s with toSetErrorCtx := s.toSetErrorCtx.push arg.mvarId! }
|
||
addNewArg argName arg
|
||
main
|
||
|
||
/-
|
||
Process a `fType` of the form `(x : A) → B x`.
|
||
This method assume `fType` is a function type -/
|
||
private partial def processExplictArg (argName : Name) : M Expr := do
|
||
let s ← get
|
||
match s.args with
|
||
| arg::args =>
|
||
propagateExpectedType arg
|
||
modify fun s => { s with args := args }
|
||
elabAndAddNewArg argName arg
|
||
main
|
||
| _ =>
|
||
let argType ← getArgExpectedType
|
||
match s.explicit, argType.getOptParamDefault?, argType.getAutoParamTactic? with
|
||
| false, some defVal, _ => addNewArg argName defVal; main
|
||
| false, _, some (Expr.const tacticDecl _ _) =>
|
||
let env ← getEnv
|
||
let opts ← getOptions
|
||
match evalSyntaxConstant env opts tacticDecl with
|
||
| Except.error err => throwError err
|
||
| Except.ok tacticSyntax =>
|
||
-- TODO(Leo): does this work correctly for tactic sequences?
|
||
let tacticBlock ← `(by $tacticSyntax)
|
||
let argNew := Arg.stx tacticBlock
|
||
propagateExpectedType argNew
|
||
elabAndAddNewArg argName argNew
|
||
main
|
||
| false, _, some _ =>
|
||
throwError "invalid autoParam, argument must be a constant"
|
||
| _, _, _ =>
|
||
if !s.namedArgs.isEmpty then
|
||
if (← anyNamedArgDependsOnCurrent) then
|
||
addImplicitArg argName
|
||
else
|
||
addEtaArg argName
|
||
else if !s.explicit then
|
||
if (← fTypeHasOptAutoParams) then
|
||
addEtaArg argName
|
||
else if (← get).ellipsis then
|
||
addImplicitArg argName
|
||
else
|
||
finalize
|
||
else
|
||
finalize
|
||
|
||
/-
|
||
Process a `fType` of the form `{x : A} → B x`.
|
||
This method assume `fType` is a function type -/
|
||
private partial def processImplicitArg (argName : Name) : M Expr := do
|
||
if (← get).explicit then
|
||
processExplictArg argName
|
||
else
|
||
addImplicitArg argName
|
||
|
||
/-
|
||
Process a `fType` of the form `{{x : A}} → B x`.
|
||
This method assume `fType` is a function type -/
|
||
private partial def processStrictImplicitArg (argName : Name) : M Expr := do
|
||
if (← get).explicit then
|
||
processExplictArg argName
|
||
else if (← hasArgsToProcess) then
|
||
addImplicitArg argName
|
||
else
|
||
finalize
|
||
|
||
/-
|
||
Process a `fType` of the form `[x : A] → B x`.
|
||
This method assume `fType` is a function type -/
|
||
private partial def processInstImplicitArg (argName : Name) : M Expr := do
|
||
if (← get).explicit then
|
||
if (← isNextArgHole) then
|
||
/- Recall that if '@' has been used, and the argument is '_', then we still use type class resolution -/
|
||
let arg ← mkFreshExprMVar (← getArgExpectedType) MetavarKind.synthetic
|
||
modify fun s => { s with args := s.args.tail! }
|
||
addInstMVar arg.mvarId!
|
||
addNewArg argName arg
|
||
main
|
||
else
|
||
processExplictArg argName
|
||
else
|
||
let arg ← mkFreshExprMVar (← getArgExpectedType) MetavarKind.synthetic
|
||
addInstMVar arg.mvarId!
|
||
addNewArg argName arg
|
||
main
|
||
|
||
/- Elaborate function application arguments. -/
|
||
partial def main : M Expr := do
|
||
let s ← get
|
||
let fType ← normalizeFunType
|
||
if fType.isForall then
|
||
let binderName := fType.bindingName!
|
||
let binfo := fType.bindingInfo!
|
||
let s ← get
|
||
match s.namedArgs.find? fun (namedArg : NamedArg) => namedArg.name == binderName with
|
||
| some namedArg =>
|
||
propagateExpectedType namedArg.val
|
||
eraseNamedArg binderName
|
||
elabAndAddNewArg binderName namedArg.val
|
||
main
|
||
| none =>
|
||
match binfo with
|
||
| BinderInfo.implicit => processImplicitArg binderName
|
||
| BinderInfo.instImplicit => processInstImplicitArg binderName
|
||
| BinderInfo.strictImplicit => processStrictImplicitArg binderName
|
||
| _ => processExplictArg binderName
|
||
else if (← hasArgsToProcess) then
|
||
synthesizePendingAndNormalizeFunType
|
||
main
|
||
else
|
||
finalize
|
||
|
||
end
|
||
|
||
end ElabAppArgs
|
||
|
||
private def propagateExpectedTypeFor (f : Expr) : TermElabM Bool :=
|
||
match f.getAppFn.constName? with
|
||
| some declName => return !hasElabWithoutExpectedType (← getEnv) declName
|
||
| _ => return true
|
||
|
||
def elabAppArgs (f : Expr) (namedArgs : Array NamedArg) (args : Array Arg)
|
||
(expectedType? : Option Expr) (explicit ellipsis : Bool) : TermElabM Expr := do
|
||
let fType ← inferType f
|
||
let fType ← instantiateMVars fType
|
||
trace[Elab.app.args] "explicit: {explicit}, {f} : {fType}"
|
||
unless namedArgs.isEmpty && args.isEmpty do
|
||
tryPostponeIfMVar fType
|
||
ElabAppArgs.main.run' {
|
||
args := args.toList,
|
||
expectedType? := expectedType?,
|
||
explicit := explicit,
|
||
ellipsis := ellipsis,
|
||
namedArgs := namedArgs.toList,
|
||
f := f,
|
||
fType := fType
|
||
propagateExpected := (← propagateExpectedTypeFor f)
|
||
}
|
||
|
||
/-- Auxiliary inductive datatype that represents the resolution of an `LVal`. -/
|
||
inductive LValResolution where
|
||
| projFn (baseStructName : Name) (structName : Name) (fieldName : Name)
|
||
| projIdx (structName : Name) (idx : Nat)
|
||
| const (baseStructName : Name) (structName : Name) (constName : Name)
|
||
| localRec (baseName : Name) (fullName : Name) (fvar : Expr)
|
||
| getOp (fullName : Name) (idx : Syntax)
|
||
|
||
private def throwLValError {α} (e : Expr) (eType : Expr) (msg : MessageData) : TermElabM α :=
|
||
throwError "{msg}{indentExpr e}\nhas type{indentExpr eType}"
|
||
|
||
/-- `findMethod? env S fName`.
|
||
1- If `env` contains `S ++ fName`, return `(S, S++fName)`
|
||
2- Otherwise if `env` contains private name `prv` for `S ++ fName`, return `(S, prv)`, o
|
||
3- Otherwise for each parent structure `S'` of `S`, we try `findMethod? env S' fname` -/
|
||
private partial def findMethod? (env : Environment) (structName fieldName : Name) : Option (Name × Name) :=
|
||
let fullName := structName ++ fieldName
|
||
match env.find? fullName with
|
||
| some _ => some (structName, fullName)
|
||
| none =>
|
||
let fullNamePrv := mkPrivateName env fullName
|
||
match env.find? fullNamePrv with
|
||
| some _ => some (structName, fullNamePrv)
|
||
| none =>
|
||
if isStructure env structName then
|
||
(getParentStructures env structName).findSome? fun parentStructName => findMethod? env parentStructName fieldName
|
||
else
|
||
none
|
||
|
||
private def resolveLValAux (e : Expr) (eType : Expr) (lval : LVal) : TermElabM LValResolution := do
|
||
match eType.getAppFn.constName?, lval with
|
||
| some structName, LVal.fieldIdx _ idx =>
|
||
if idx == 0 then
|
||
throwError "invalid projection, index must be greater than 0"
|
||
let env ← getEnv
|
||
unless isStructureLike env structName do
|
||
throwLValError e eType "invalid projection, structure expected"
|
||
let numFields := getStructureLikeNumFields env structName
|
||
if idx - 1 < numFields then
|
||
if isStructure env structName then
|
||
let fieldNames := getStructureFields env structName
|
||
return LValResolution.projFn structName structName fieldNames[idx - 1]
|
||
else
|
||
/- `structName` was declared using `inductive` command.
|
||
So, we don't projection functions for it. Thus, we use `Expr.proj` -/
|
||
return LValResolution.projIdx structName (idx - 1)
|
||
else
|
||
throwLValError e eType m!"invalid projection, structure has only {numFields} field(s)"
|
||
| some structName, LVal.fieldName _ fieldName _ _ =>
|
||
let env ← getEnv
|
||
let searchEnv : Unit → TermElabM LValResolution := fun _ => do
|
||
match findMethod? env structName (Name.mkSimple fieldName) with
|
||
| some (baseStructName, fullName) => pure $ LValResolution.const baseStructName structName fullName
|
||
| none =>
|
||
throwLValError e eType
|
||
m!"invalid field '{fieldName}', the environment does not contain '{Name.mkStr structName fieldName}'"
|
||
-- search local context first, then environment
|
||
let searchCtx : Unit → TermElabM LValResolution := fun _ => do
|
||
let fullName := Name.mkStr structName fieldName
|
||
let currNamespace ← getCurrNamespace
|
||
let localName := fullName.replacePrefix currNamespace Name.anonymous
|
||
let lctx ← getLCtx
|
||
match lctx.findFromUserName? localName with
|
||
| some localDecl =>
|
||
if localDecl.binderInfo == BinderInfo.auxDecl then
|
||
/- LVal notation is being used to make a "local" recursive call. -/
|
||
pure $ LValResolution.localRec structName fullName localDecl.toExpr
|
||
else
|
||
searchEnv ()
|
||
| none => searchEnv ()
|
||
if isStructure env structName then
|
||
match findField? env structName (Name.mkSimple fieldName) with
|
||
| some baseStructName => pure $ LValResolution.projFn baseStructName structName (Name.mkSimple fieldName)
|
||
| none => searchCtx ()
|
||
else
|
||
searchCtx ()
|
||
| some structName, LVal.getOp _ idx =>
|
||
let env ← getEnv
|
||
let fullName := Name.mkStr structName "getOp"
|
||
match env.find? fullName with
|
||
| some _ => pure $ LValResolution.getOp fullName idx
|
||
| none => throwLValError e eType m!"invalid [..] notation because environment does not contain '{fullName}'"
|
||
| none, LVal.fieldName _ _ (some suffix) _ =>
|
||
if e.isConst then
|
||
throwUnknownConstant (e.constName! ++ suffix)
|
||
else
|
||
throwLValError e eType "invalid field notation, type is not of the form (C ...) where C is a constant"
|
||
| _, LVal.getOp _ idx =>
|
||
throwLValError e eType "invalid [..] notation, type is not of the form (C ...) where C is a constant"
|
||
| _, _ =>
|
||
throwLValError e eType "invalid field notation, type is not of the form (C ...) where C is a constant"
|
||
|
||
/- whnfCore + implicit consumption.
|
||
Example: given `e` with `eType := {α : Type} → (fun β => List β) α `, it produces `(e ?m, List ?m)` where `?m` is fresh metavariable. -/
|
||
private partial def consumeImplicits (stx : Syntax) (e eType : Expr) (hasArgs : Bool) : TermElabM (Expr × Expr) := do
|
||
let eType ← whnfCore eType
|
||
match eType with
|
||
| Expr.forallE n d b c =>
|
||
if c.binderInfo.isImplicit || (hasArgs && c.binderInfo.isStrictImplicit) then
|
||
let mvar ← mkFreshExprMVar d
|
||
registerMVarErrorHoleInfo mvar.mvarId! stx
|
||
consumeImplicits stx (mkApp e mvar) (b.instantiate1 mvar) hasArgs
|
||
else if c.binderInfo.isInstImplicit then
|
||
let mvar ← mkInstMVar d
|
||
let r := mkApp e mvar
|
||
registerMVarErrorImplicitArgInfo mvar.mvarId! stx r
|
||
consumeImplicits stx r (b.instantiate1 mvar) hasArgs
|
||
else match d.getOptParamDefault? with
|
||
| some defVal => consumeImplicits stx (mkApp e defVal) (b.instantiate1 defVal) hasArgs
|
||
-- TODO: we do not handle autoParams here.
|
||
| _ => pure (e, eType)
|
||
| _ => pure (e, eType)
|
||
|
||
private partial def resolveLValLoop (lval : LVal) (e eType : Expr) (previousExceptions : Array Exception) (hasArgs : Bool) : TermElabM (Expr × LValResolution) := do
|
||
let (e, eType) ← consumeImplicits lval.getRef e eType hasArgs
|
||
tryPostponeIfMVar eType
|
||
try
|
||
let lvalRes ← resolveLValAux e eType lval
|
||
pure (e, lvalRes)
|
||
catch
|
||
| ex@(Exception.error _ _) =>
|
||
let eType? ← unfoldDefinition? eType
|
||
match eType? with
|
||
| some eType => resolveLValLoop lval e eType (previousExceptions.push ex) hasArgs
|
||
| none =>
|
||
previousExceptions.forM fun ex => logException ex
|
||
throw ex
|
||
| ex@(Exception.internal _ _) => throw ex
|
||
|
||
private def resolveLVal (e : Expr) (lval : LVal) (hasArgs : Bool) : TermElabM (Expr × LValResolution) := do
|
||
let eType ← inferType e
|
||
resolveLValLoop lval e eType #[] hasArgs
|
||
|
||
private partial def mkBaseProjections (baseStructName : Name) (structName : Name) (e : Expr) : TermElabM Expr := do
|
||
let env ← getEnv
|
||
match getPathToBaseStructure? env baseStructName structName with
|
||
| none => throwError "failed to access field in parent structure"
|
||
| some path =>
|
||
let mut e := e
|
||
for projFunName in path do
|
||
let projFn ← mkConst projFunName
|
||
e ← elabAppArgs projFn #[{ name := `self, val := Arg.expr e }] (args := #[]) (expectedType? := none) (explicit := false) (ellipsis := false)
|
||
return e
|
||
|
||
/- Auxiliary method for field notation. It tries to add `e` as a new argument to `args` or `namedArgs`.
|
||
This method first finds the parameter with a type of the form `(baseName ...)`.
|
||
When the parameter is found, if it an explicit one and `args` is big enough, we add `e` to `args`.
|
||
Otherwise, if there isn't another parameter with the same name, we add `e` to `namedArgs`.
|
||
|
||
Remark: `fullName` is the name of the resolved "field" access function. It is used for reporting errors -/
|
||
private def addLValArg (baseName : Name) (fullName : Name) (e : Expr) (args : Array Arg) (namedArgs : Array NamedArg) (fType : Expr)
|
||
: TermElabM (Array Arg × Array NamedArg) :=
|
||
forallTelescopeReducing fType fun xs _ => do
|
||
let mut argIdx := 0 -- position of the next explicit argument
|
||
let mut remainingNamedArgs := namedArgs
|
||
for i in [:xs.size] do
|
||
let x := xs[i]
|
||
let xDecl ← getLocalDecl x.fvarId!
|
||
/- If there is named argument with name `xDecl.userName`, then we skip it. -/
|
||
match remainingNamedArgs.findIdx? (fun namedArg => namedArg.name == xDecl.userName) with
|
||
| some idx =>
|
||
remainingNamedArgs := remainingNamedArgs.eraseIdx idx
|
||
| none =>
|
||
let mut foundIt := false
|
||
let type := xDecl.type
|
||
if type.consumeMData.isAppOf baseName then
|
||
foundIt := true
|
||
if !foundIt then
|
||
/- Normalize type and try again -/
|
||
let type ← withReducible $ whnf type
|
||
if type.consumeMData.isAppOf baseName then
|
||
foundIt := true
|
||
if foundIt then
|
||
/- We found a type of the form (baseName ...).
|
||
First, we check if the current argument is an explicit one,
|
||
and the current explicit position "fits" at `args` (i.e., it must be ≤ arg.size) -/
|
||
if argIdx ≤ args.size && xDecl.binderInfo.isExplicit then
|
||
/- We insert `e` as an explicit argument -/
|
||
return (args.insertAt argIdx (Arg.expr e), namedArgs)
|
||
/- If we can't add `e` to `args`, we try to add it using a named argument, but this is only possible
|
||
if there isn't an argument with the same name occurring before it. -/
|
||
for j in [:i] do
|
||
let prev := xs[j]
|
||
let prevDecl ← getLocalDecl prev.fvarId!
|
||
if prevDecl.userName == xDecl.userName then
|
||
throwError "invalid field notation, function '{fullName}' has argument with the expected type{indentExpr type}\nbut it cannot be used"
|
||
return (args, namedArgs.push { name := xDecl.userName, val := Arg.expr e })
|
||
if xDecl.binderInfo.isExplicit then
|
||
-- advance explicit argument position
|
||
argIdx := argIdx + 1
|
||
throwError "invalid field notation, function '{fullName}' does not have argument with type ({baseName} ...) that can be used, it must be explicit or implicit with an unique name"
|
||
|
||
private def elabAppLValsAux (namedArgs : Array NamedArg) (args : Array Arg) (expectedType? : Option Expr) (explicit ellipsis : Bool)
|
||
(f : Expr) (lvals : List LVal) : TermElabM Expr :=
|
||
let rec loop : Expr → List LVal → TermElabM Expr
|
||
| f, [] => elabAppArgs f namedArgs args expectedType? explicit ellipsis
|
||
| f, lval::lvals => do
|
||
if let LVal.fieldName (ref := fieldStx) (targetStx := targetStx) .. := lval then
|
||
addDotCompletionInfo targetStx f expectedType? fieldStx
|
||
let hasArgs := !namedArgs.isEmpty || !args.isEmpty
|
||
let (f, lvalRes) ← resolveLVal f lval hasArgs
|
||
match lvalRes with
|
||
| LValResolution.projIdx structName idx =>
|
||
let f ← mkProjAndCheck structName idx f
|
||
addTermInfo lval.getRef f
|
||
loop f lvals
|
||
| LValResolution.projFn baseStructName structName fieldName =>
|
||
let f ← mkBaseProjections baseStructName structName f
|
||
if let some info := getFieldInfo? (← getEnv) baseStructName fieldName then
|
||
if isPrivateNameFromImportedModule (← getEnv) info.projFn then
|
||
throwError "field '{fieldName}' from structure '{structName}' is private"
|
||
let projFn ← mkConst info.projFn
|
||
addTermInfo lval.getRef projFn
|
||
if lvals.isEmpty then
|
||
let namedArgs ← addNamedArg namedArgs { name := `self, val := Arg.expr f }
|
||
elabAppArgs projFn namedArgs args expectedType? explicit ellipsis
|
||
else
|
||
let f ← elabAppArgs projFn #[{ name := `self, val := Arg.expr f }] #[] (expectedType? := none) (explicit := false) (ellipsis := false)
|
||
loop f lvals
|
||
else
|
||
unreachable!
|
||
| LValResolution.const baseStructName structName constName =>
|
||
let f ← if baseStructName != structName then mkBaseProjections baseStructName structName f else pure f
|
||
let projFn ← mkConst constName
|
||
addTermInfo lval.getRef projFn
|
||
if lvals.isEmpty then
|
||
let projFnType ← inferType projFn
|
||
let (args, namedArgs) ← addLValArg baseStructName constName f args namedArgs projFnType
|
||
elabAppArgs projFn namedArgs args expectedType? explicit ellipsis
|
||
else
|
||
let f ← elabAppArgs projFn #[] #[Arg.expr f] (expectedType? := none) (explicit := false) (ellipsis := false)
|
||
loop f lvals
|
||
| LValResolution.localRec baseName fullName fvar =>
|
||
addTermInfo lval.getRef fvar
|
||
if lvals.isEmpty then
|
||
let fvarType ← inferType fvar
|
||
let (args, namedArgs) ← addLValArg baseName fullName f args namedArgs fvarType
|
||
elabAppArgs fvar namedArgs args expectedType? explicit ellipsis
|
||
else
|
||
let f ← elabAppArgs fvar #[] #[Arg.expr f] (expectedType? := none) (explicit := false) (ellipsis := false)
|
||
loop f lvals
|
||
| LValResolution.getOp fullName idx =>
|
||
let getOpFn ← mkConst fullName
|
||
addTermInfo lval.getRef getOpFn
|
||
if lvals.isEmpty then
|
||
let namedArgs ← addNamedArg namedArgs { name := `self, val := Arg.expr f }
|
||
let namedArgs ← addNamedArg namedArgs { name := `idx, val := Arg.stx idx }
|
||
elabAppArgs getOpFn namedArgs args expectedType? explicit ellipsis
|
||
else
|
||
let f ← elabAppArgs getOpFn #[{ name := `self, val := Arg.expr f }, { name := `idx, val := Arg.stx idx }]
|
||
#[] (expectedType? := none) (explicit := false) (ellipsis := false)
|
||
loop f lvals
|
||
loop f lvals
|
||
|
||
private def elabAppLVals (f : Expr) (lvals : List LVal) (namedArgs : Array NamedArg) (args : Array Arg)
|
||
(expectedType? : Option Expr) (explicit ellipsis : Bool) : TermElabM Expr := do
|
||
if !lvals.isEmpty && explicit then
|
||
throwError "invalid use of field notation with `@` modifier"
|
||
elabAppLValsAux namedArgs args expectedType? explicit ellipsis f lvals
|
||
|
||
def elabExplicitUnivs (lvls : Array Syntax) : TermElabM (List Level) := do
|
||
lvls.foldrM (fun stx lvls => do pure ((← elabLevel stx)::lvls)) []
|
||
|
||
/-
|
||
Interaction between `errToSorry` and `observing`.
|
||
|
||
- The method `elabTerm` catches exceptions, log them, and returns a synthetic sorry (IF `ctx.errToSorry` == true).
|
||
|
||
- When we elaborate choice nodes (and overloaded identifiers), we track multiple results using the `observing x` combinator.
|
||
The `observing x` executes `x` and returns a `TermElabResult`.
|
||
|
||
`observing `x does not check for synthetic sorry's, just an exception. Thus, it may think `x` worked when it didn't
|
||
if a synthetic sorry was introduced. We decided that checking for synthetic sorrys at `observing` is not a good solution
|
||
because it would not be clear to decide what the "main" error message for the alternative is. When the result contains
|
||
a synthetic `sorry`, it is not clear which error message corresponds to the `sorry`. Moreover, while executing `x`, many
|
||
error messages may have been logged. Recall that we need an error per alternative at `mergeFailures`.
|
||
|
||
Thus, we decided to set `errToSorry` to `false` whenever processing choice nodes and overloaded symbols.
|
||
|
||
Important: we rely on the property that after `errToSorry` is set to
|
||
false, no elaboration function executed by `x` will reset it to
|
||
`true`.
|
||
-/
|
||
|
||
private partial def elabAppFnId (fIdent : Syntax) (fExplicitUnivs : List Level) (lvals : List LVal)
|
||
(namedArgs : Array NamedArg) (args : Array Arg) (expectedType? : Option Expr) (explicit ellipsis overloaded : Bool) (acc : Array (TermElabResult Expr))
|
||
: TermElabM (Array (TermElabResult Expr)) := do
|
||
let funLVals ← withRef fIdent <| resolveName' fIdent fExplicitUnivs expectedType?
|
||
let overloaded := overloaded || funLVals.length > 1
|
||
-- Set `errToSorry` to `false` if `funLVals` > 1. See comment above about the interaction between `errToSorry` and `observing`.
|
||
withReader (fun ctx => { ctx with errToSorry := funLVals.length == 1 && ctx.errToSorry }) do
|
||
funLVals.foldlM (init := acc) fun acc (f, fIdent, fields) => do
|
||
let lvals' := toLVals fields (first := true)
|
||
let s ← observing do
|
||
addTermInfo fIdent f expectedType?
|
||
let e ← elabAppLVals f (lvals' ++ lvals) namedArgs args expectedType? explicit ellipsis
|
||
if overloaded then ensureHasType expectedType? e else pure e
|
||
return acc.push s
|
||
where
|
||
toName (fields : List Syntax) : Name :=
|
||
let rec go
|
||
| [] => Name.anonymous
|
||
| field :: fields => Name.mkStr (go fields) field.getId.toString
|
||
go fields.reverse
|
||
|
||
toLVals : List Syntax → (first : Bool) → List LVal
|
||
| [], _ => []
|
||
| field::fields, true => LVal.fieldName field field.getId.toString (toName (field::fields)) fIdent :: toLVals fields false
|
||
| field::fields, false => LVal.fieldName field field.getId.toString none fIdent :: toLVals fields false
|
||
|
||
private partial def elabAppFn (f : Syntax) (lvals : List LVal) (namedArgs : Array NamedArg) (args : Array Arg)
|
||
(expectedType? : Option Expr) (explicit ellipsis overloaded : Bool) (acc : Array (TermElabResult Expr)) : TermElabM (Array (TermElabResult Expr)) :=
|
||
if f.getKind == choiceKind then
|
||
-- Set `errToSorry` to `false` when processing choice nodes. See comment above about the interaction between `errToSorry` and `observing`.
|
||
withReader (fun ctx => { ctx with errToSorry := false }) do
|
||
f.getArgs.foldlM (init := acc) fun acc f => elabAppFn f lvals namedArgs args expectedType? explicit ellipsis true acc
|
||
else
|
||
let elabFieldName (e field : Syntax) := do
|
||
let newLVals := field.identComponents.map fun comp =>
|
||
-- We use `none` in `suffix?` since `field` can't be part of a composite name
|
||
LVal.fieldName comp (toString comp.getId) none e
|
||
elabAppFn e (newLVals ++ lvals) namedArgs args expectedType? explicit ellipsis overloaded acc
|
||
let elabFieldIdx (e idxStx : Syntax) := do
|
||
let idx := idxStx.isFieldIdx?.get!
|
||
elabAppFn e (LVal.fieldIdx idxStx idx :: lvals) namedArgs args expectedType? explicit ellipsis overloaded acc
|
||
match f with
|
||
| `($(e).$idx:fieldIdx) => elabFieldIdx e idx
|
||
| `($e |>.$idx:fieldIdx) => elabFieldIdx e idx
|
||
| `($(e).$field:ident) => elabFieldName e field
|
||
| `($e |>.$field:ident) => elabFieldName e field
|
||
| `($e[%$bracket $idx]) => elabAppFn e (LVal.getOp bracket idx :: lvals) namedArgs args expectedType? explicit ellipsis overloaded acc
|
||
| `($id:ident@$t:term) =>
|
||
throwError "unexpected occurrence of named pattern"
|
||
| `($id:ident) => do
|
||
elabAppFnId id [] lvals namedArgs args expectedType? explicit ellipsis overloaded acc
|
||
| `($id:ident.{$us,*}) => do
|
||
let us ← elabExplicitUnivs us
|
||
elabAppFnId id us lvals namedArgs args expectedType? explicit ellipsis overloaded acc
|
||
| `(@$id:ident) =>
|
||
elabAppFn id lvals namedArgs args expectedType? (explicit := true) ellipsis overloaded acc
|
||
| `(@$id:ident.{$us,*}) =>
|
||
elabAppFn (f.getArg 1) lvals namedArgs args expectedType? (explicit := true) ellipsis overloaded acc
|
||
| `(@$t) => throwUnsupportedSyntax -- invalid occurrence of `@`
|
||
| `(_) => throwError "placeholders '_' cannot be used where a function is expected"
|
||
| _ => do
|
||
let catchPostpone := !overloaded
|
||
/- If we are processing a choice node, then we should use `catchPostpone == false` when elaborating terms.
|
||
Recall that `observing` does not catch `postponeExceptionId`. -/
|
||
if lvals.isEmpty && namedArgs.isEmpty && args.isEmpty then
|
||
/- Recall that elabAppFn is used for elaborating atomics terms **and** choice nodes that may contain
|
||
arbitrary terms. If they are not being used as a function, we should elaborate using the expectedType. -/
|
||
let s ← observing do
|
||
if overloaded then
|
||
elabTermEnsuringType f expectedType? catchPostpone
|
||
else
|
||
elabTerm f expectedType?
|
||
return acc.push s
|
||
else
|
||
let s ← observing do
|
||
let f ← elabTerm f none catchPostpone
|
||
let e ← elabAppLVals f lvals namedArgs args expectedType? explicit ellipsis
|
||
if overloaded then ensureHasType expectedType? e else pure e
|
||
return acc.push s
|
||
|
||
private def getSuccesses (candidates : Array (TermElabResult Expr)) : TermElabM (Array (TermElabResult Expr)) := do
|
||
let r₁ := candidates.filter fun | EStateM.Result.ok .. => true | _ => false
|
||
if r₁.size ≤ 1 then return r₁
|
||
let r₂ ← candidates.filterM fun
|
||
| EStateM.Result.ok e s => do
|
||
if e.isMVar then
|
||
/- Make sure `e` is not a delayed coercion.
|
||
Recall that coercion insertion may be delayed when the type and expected type contains
|
||
metavariables that block TC resolution.
|
||
When processing overloaded notation, we disallow delayed coercions at `e`. -/
|
||
try
|
||
s.restore
|
||
synthesizeSyntheticMVars -- Tries to process pending coercions (and elaboration tasks)
|
||
let e ← instantiateMVars e
|
||
if e.isMVar then
|
||
/- If `e` is still a metavariable, and its `SyntheticMVarDecl` is a coercion, we discard this solution -/
|
||
if let some synDecl ← getSyntheticMVarDecl? e.mvarId! then
|
||
if synDecl.kind matches SyntheticMVarKind.coe .. then
|
||
return false
|
||
catch _ =>
|
||
-- If `synthesizeSyntheticMVars` failed, we just eliminate the candidate.
|
||
return false
|
||
return true
|
||
| _ => return false
|
||
if r₂.size == 0 then return r₁ else return r₂
|
||
|
||
private def toMessageData (ex : Exception) : TermElabM MessageData := do
|
||
let pos ← getRefPos
|
||
match ex.getRef.getPos? with
|
||
| none => return ex.toMessageData
|
||
| some exPos =>
|
||
if pos == exPos then
|
||
return ex.toMessageData
|
||
else
|
||
let exPosition := (← getFileMap).toPosition exPos
|
||
return m!"{exPosition.line}:{exPosition.column} {ex.toMessageData}"
|
||
|
||
private def toMessageList (msgs : Array MessageData) : MessageData :=
|
||
indentD (MessageData.joinSep msgs.toList m!"\n\n")
|
||
|
||
private def mergeFailures {α} (failures : Array (TermElabResult Expr)) : TermElabM α := do
|
||
let msgs ← failures.mapM fun failure =>
|
||
match failure with
|
||
| EStateM.Result.ok _ _ => unreachable!
|
||
| EStateM.Result.error ex _ => toMessageData ex
|
||
throwError "overloaded, errors {toMessageList msgs}"
|
||
|
||
private def elabAppAux (f : Syntax) (namedArgs : Array NamedArg) (args : Array Arg) (ellipsis : Bool) (expectedType? : Option Expr) : TermElabM Expr := do
|
||
let candidates ← elabAppFn f [] namedArgs args expectedType? (explicit := false) (ellipsis := ellipsis) (overloaded := false) #[]
|
||
if candidates.size == 1 then
|
||
applyResult candidates[0]
|
||
else
|
||
let successes ← getSuccesses candidates
|
||
if successes.size == 1 then
|
||
applyResult successes[0]
|
||
else if successes.size > 1 then
|
||
let msgs : Array MessageData ← successes.mapM fun success => do
|
||
match success with
|
||
| EStateM.Result.ok e s => withMCtx s.meta.meta.mctx <| withEnv s.meta.core.env do addMessageContext m!"{e} : {← inferType e}"
|
||
| _ => unreachable!
|
||
throwErrorAt f "ambiguous, possible interpretations {toMessageList msgs}"
|
||
else
|
||
withRef f <| mergeFailures candidates
|
||
|
||
@[builtinTermElab app] def elabApp : TermElab := fun stx expectedType? =>
|
||
withoutPostponingUniverseConstraints do
|
||
let (f, namedArgs, args, ellipsis) ← expandApp stx
|
||
let result ← elabAppAux f namedArgs args (ellipsis := ellipsis) expectedType?
|
||
let resultFn := result.getAppFn
|
||
if resultFn.isFVar then
|
||
let localDecl ← getLocalDecl resultFn.fvarId!
|
||
if localDecl.isAuxDecl then
|
||
return mkRecAppWithSyntax result stx
|
||
return result
|
||
|
||
private def elabAtom : TermElab := fun stx expectedType? =>
|
||
elabAppAux stx #[] #[] (ellipsis := false) expectedType?
|
||
|
||
@[builtinTermElab ident] def elabIdent : TermElab := elabAtom
|
||
/-- `x@e` matches the pattern `e` and binds its value to the identifier `x`. -/
|
||
@[builtinTermElab namedPattern] def elabNamedPattern : TermElab := elabAtom
|
||
/-- `x.{u, ...}` explicitly specifies the universes `u, ...` of the constant `x`. -/
|
||
@[builtinTermElab explicitUniv] def elabExplicitUniv : TermElab := elabAtom
|
||
/-- `e |>.x` is a shorthand for `(e).x`. It is especially useful for avoiding parentheses with repeated applications. -/
|
||
@[builtinTermElab pipeProj] def elabPipeProj : TermElab
|
||
| `($e |>.$f $args*), expectedType? =>
|
||
withoutPostponingUniverseConstraints do
|
||
let (namedArgs, args, ellipsis) ← expandArgs args
|
||
elabAppAux (← `($e |>.$f)) namedArgs args (ellipsis := ellipsis) expectedType?
|
||
| _, _ => throwUnsupportedSyntax
|
||
|
||
/--
|
||
`@x` disables automatic insertion of implicit parameters of the constant `x`.
|
||
`@e` for any term `e` also disables the insertion of implicit lambdas at this position. -/
|
||
@[builtinTermElab explicit] def elabExplicit : TermElab := fun stx expectedType? =>
|
||
match stx with
|
||
| `(@$id:ident) => elabAtom stx expectedType? -- Recall that `elabApp` also has support for `@`
|
||
| `(@$id:ident.{$us,*}) => elabAtom stx expectedType?
|
||
| `(@($t)) => elabTerm t expectedType? (implicitLambda := false) -- `@` is being used just to disable implicit lambdas
|
||
| `(@$t) => elabTerm t expectedType? (implicitLambda := false) -- `@` is being used just to disable implicit lambdas
|
||
| _ => throwUnsupportedSyntax
|
||
|
||
@[builtinTermElab choice] def elabChoice : TermElab := elabAtom
|
||
@[builtinTermElab proj] def elabProj : TermElab := elabAtom
|
||
@[builtinTermElab arrayRef] def elabArrayRef : TermElab := elabAtom
|
||
|
||
builtin_initialize
|
||
registerTraceClass `Elab.app
|
||
|
||
end Lean.Elab.Term
|