This PR fixes a bug where the monad lift coercion elaborator would partially unify expressions even if they were not monads. This could be taken advantage of to propagate information that could help elaboration make progress, for example the first `change` worked because the monad lift coercion elaborator was unifying `@Eq _ _` with `@Eq (Nat × Nat) p`: ```lean example (p : Nat × Nat) : p = p := by change _ = ⟨_, _⟩ -- used to work (yielding `p = (p.fst, p.snd)`), now it doesn't change ⟨_, _⟩ = _ -- never worked ``` As such, this is a breaking change; you may need to adjust expressions to include additional implicit arguments.
214 lines
9.4 KiB
Text
214 lines
9.4 KiB
Text
/-
|
||
Copyright (c) 2021 Microsoft Corporation. All rights reserved.
|
||
Released under Apache 2.0 license as described in the file LICENSE.
|
||
Authors: Leonardo de Moura
|
||
-/
|
||
prelude
|
||
import Lean.Meta.Transform
|
||
import Lean.Meta.SynthInstance
|
||
import Lean.Meta.AppBuilder
|
||
|
||
namespace Lean.Meta
|
||
|
||
builtin_initialize coeDeclAttr : TagAttribute ←
|
||
registerTagAttribute `coe_decl "auxiliary definition used to implement coercion (unfolded during elaboration)"
|
||
|
||
/--
|
||
Return true iff `declName` is one of the auxiliary definitions/projections
|
||
used to implement coercions.
|
||
-/
|
||
def isCoeDecl (env : Environment) (declName : Name) : Bool :=
|
||
coeDeclAttr.hasTag env declName
|
||
|
||
/-- Expand coercions occurring in `e` -/
|
||
partial def expandCoe (e : Expr) : MetaM Expr :=
|
||
withReducibleAndInstances do
|
||
transform e fun e => do
|
||
let f := e.getAppFn
|
||
if f.isConst then
|
||
let declName := f.constName!
|
||
if isCoeDecl (← getEnv) declName then
|
||
if let some e ← unfoldDefinition? e then
|
||
return .visit e.headBeta
|
||
return .continue
|
||
|
||
register_builtin_option autoLift : Bool := {
|
||
defValue := true
|
||
descr := "insert monadic lifts (i.e., `liftM` and coercions) when needed"
|
||
}
|
||
|
||
/-- Coerces `expr` to `expectedType` using `CoeT`. -/
|
||
def coerceSimple? (expr expectedType : Expr) : MetaM (LOption Expr) := do
|
||
let eType ← inferType expr
|
||
let u ← getLevel eType
|
||
let v ← getLevel expectedType
|
||
let coeTInstType := mkAppN (mkConst ``CoeT [u, v]) #[eType, expr, expectedType]
|
||
match ← trySynthInstance coeTInstType with
|
||
| .some inst =>
|
||
let result ← expandCoe (mkAppN (mkConst ``CoeT.coe [u, v]) #[eType, expr, expectedType, inst])
|
||
unless ← isDefEq (← inferType result) expectedType do
|
||
throwError "could not coerce{indentExpr expr}\nto{indentExpr expectedType}\ncoerced expression has wrong type:{indentExpr result}"
|
||
return .some result
|
||
| .undef => return .undef
|
||
| .none => return .none
|
||
|
||
/-- Coerces `expr` to a function type. -/
|
||
def coerceToFunction? (expr : Expr) : MetaM (Option Expr) := do
|
||
-- constructing expression manually because mkAppM wouldn't assign universe mvars
|
||
let α ← inferType expr
|
||
let u ← getLevel α
|
||
let v ← mkFreshLevelMVar
|
||
let γ ← mkFreshExprMVar (← mkArrow α (mkSort v))
|
||
let .some inst ← trySynthInstance (mkApp2 (.const ``CoeFun [u,v]) α γ) | return none
|
||
let expanded ← expandCoe (mkApp4 (.const ``CoeFun.coe [u,v]) α γ inst expr)
|
||
unless (← whnf (← inferType expanded)).isForall do
|
||
throwError "failed to coerce{indentExpr expr}\nto a function, after applying `CoeFun.coe`, result is still not a function{indentExpr expanded}\nthis is often due to incorrect `CoeFun` instances, the synthesized instance was{indentExpr inst}"
|
||
return expanded
|
||
|
||
/-- Coerces `expr` to a type. -/
|
||
def coerceToSort? (expr : Expr) : MetaM (Option Expr) := do
|
||
-- constructing expression manually because mkAppM wouldn't assign universe mvars
|
||
let α ← inferType expr
|
||
let u ← getLevel α
|
||
let v ← mkFreshLevelMVar
|
||
let β ← mkFreshExprMVar (mkSort v)
|
||
let .some inst ← trySynthInstance (mkApp2 (.const ``CoeSort [u,v]) α β) | return none
|
||
let expanded ← expandCoe (mkApp4 (.const ``CoeSort.coe [u,v]) α β inst expr)
|
||
unless (← whnf (← inferType expanded)).isSort do
|
||
throwError "failed to coerce{indentExpr expr}\nto a type, after applying `CoeSort.coe`, result is still not a type{indentExpr expanded}\nthis is often due to incorrect `CoeSort` instances, the synthesized instance was{indentExpr inst}"
|
||
return expanded
|
||
|
||
/-- Return `some (m, α)` if `type` can be reduced to an application of the form `m α` using `[reducible]` transparency. -/
|
||
def isTypeApp? (type : Expr) : MetaM (Option (Expr × Expr)) := do
|
||
let type ← withReducible <| whnf type
|
||
match type with
|
||
| .app m α => return some ((← instantiateMVars m), (← instantiateMVars α))
|
||
| _ => return none
|
||
|
||
/--
|
||
Return `true` if `type` is of the form `m α` where `m` is a `Monad`.
|
||
Note that we reduce `type` using transparency `[reducible]`.
|
||
-/
|
||
def isMonadApp (type : Expr) : MetaM Bool := do
|
||
let some (m, _) ← isTypeApp? type | return false
|
||
return (← isMonad? m).isSome
|
||
|
||
/--
|
||
Try coercions and monad lifts to make sure `e` has type `expectedType`.
|
||
|
||
If `expectedType` is of the form `n β`, we try monad lifts and other extensions.
|
||
|
||
Extensions for monads.
|
||
|
||
1. Try to unify `n` and `m`. If it succeeds, then we use
|
||
```
|
||
coeM {m : Type u → Type v} {α β : Type u} [∀ a, CoeT α a β] [Monad m] (x : m α) : m β
|
||
```
|
||
`n` must be a `Monad` to use this one.
|
||
|
||
2. If there is monad lift from `m` to `n` and we can unify `α` and `β`, we use
|
||
```
|
||
liftM : ∀ {m : Type u_1 → Type u_2} {n : Type u_1 → Type u_3} [self : MonadLiftT m n] {α : Type u_1}, m α → n α
|
||
```
|
||
Note that `n` may not be a `Monad` in this case. This happens quite a bit in code such as
|
||
```
|
||
def g (x : Nat) : IO Nat := do
|
||
IO.println x
|
||
pure x
|
||
|
||
def f {m} [MonadLiftT IO m] : m Nat :=
|
||
g 10
|
||
|
||
```
|
||
|
||
3. If there is a monad lift from `m` to `n` and a coercion from `α` to `β`, we use
|
||
```
|
||
liftCoeM {m : Type u → Type v} {n : Type u → Type w} {α β : Type u} [MonadLiftT m n] [∀ a, CoeT α a β] [Monad n] (x : m α) : n β
|
||
```
|
||
|
||
Note that approach 3 does not subsume 1 because it is only applicable if there is a coercion from `α` to `β` for all values in `α`.
|
||
This is not the case for example for `pure $ x > 0` when the expected type is `IO Bool`. The given type is `IO Prop`, and
|
||
we only have a coercion from decidable propositions. Approach 1 works because it constructs the coercion `CoeT (m Prop) (pure $ x > 0) (m Bool)`
|
||
using the instance `pureCoeDepProp`.
|
||
|
||
Note that, approach 2 is more powerful than `tryCoe`.
|
||
Recall that type class resolution never assigns metavariables created by other modules.
|
||
Now, consider the following scenario
|
||
```lean
|
||
def g (x : Nat) : IO Nat := ...
|
||
deg h (x : Nat) : StateT Nat IO Nat := do
|
||
v ← g x;
|
||
IO.Println v;
|
||
...
|
||
```
|
||
Let's assume there is no other occurrence of `v` in `h`.
|
||
Thus, we have that the expected of `g x` is `StateT Nat IO ?α`,
|
||
and the given type is `IO Nat`. So, even if we add a coercion.
|
||
```
|
||
instance {α m n} [MonadLiftT m n] {α} : Coe (m α) (n α) := ...
|
||
```
|
||
It is not applicable because TC would have to assign `?α := Nat`.
|
||
On the other hand, TC can easily solve `[MonadLiftT IO (StateT Nat IO)]`
|
||
since this goal does not contain any metavariables. And then, we
|
||
convert `g x` into `liftM $ g x`.
|
||
-/
|
||
def coerceMonadLift? (e expectedType : Expr) : MetaM (Option Expr) := do
|
||
let expectedType ← instantiateMVars expectedType
|
||
let eType ← instantiateMVars (← inferType e)
|
||
let some (n, β) ← isTypeApp? expectedType | return none
|
||
let some (m, α) ← isTypeApp? eType | return none
|
||
-- Need to save and restore the state in case `m` and `n` are defeq but not monads to prevent this procedure from having side effects.
|
||
let saved ← saveState
|
||
if (← isDefEq m n) then
|
||
let some monadInst ← isMonad? n | restoreState saved; return none
|
||
try expandCoe (← mkAppOptM ``Lean.Internal.coeM #[m, α, β, none, monadInst, e]) catch _ => restoreState saved; return none
|
||
else if autoLift.get (← getOptions) then
|
||
try
|
||
-- Construct lift from `m` to `n`
|
||
-- Note: we cannot use mkAppM here because mkAppM does not assign universe metavariables,
|
||
-- but we need to make sure that the domains of `m` and `n` have the same level.
|
||
let .forallE _ (.sort um₁) (.sort um₂) _ ← whnf (← inferType m) | return none
|
||
let .forallE _ (.sort un₁) (.sort un₂) _ ← whnf (← inferType n) | return none
|
||
let u ← decLevel um₁
|
||
let .true ← isLevelDefEq u (← decLevel un₁) | return none
|
||
let v ← decLevel um₂
|
||
let w ← decLevel un₂
|
||
let monadLiftType := mkAppN (.const ``MonadLiftT [u, v, w]) #[m, n]
|
||
let .some monadLiftVal ← trySynthInstance monadLiftType | return none
|
||
let u_1 ← getDecLevel α
|
||
let u_2 ← getDecLevel eType
|
||
let u_3 ← getDecLevel expectedType
|
||
let eNew := mkAppN (Lean.mkConst ``liftM [u_1, u_2, u_3]) #[m, n, monadLiftVal, α, e]
|
||
let eNewType ← inferType eNew
|
||
if (← isDefEq expectedType eNewType) then
|
||
return some eNew -- approach 2 worked
|
||
else
|
||
let some monadInst ← isMonad? n | return none
|
||
let u ← getLevel α
|
||
let v ← getLevel β
|
||
let coeTInstType := Lean.mkForall `a BinderInfo.default α <| mkAppN (mkConst ``CoeT [u, v]) #[α, mkBVar 0, β]
|
||
let .some coeTInstVal ← trySynthInstance coeTInstType | return none
|
||
let eNew ← expandCoe (mkAppN (Lean.mkConst ``Lean.Internal.liftCoeM [u_1, u_2, u_3]) #[m, n, α, β, monadLiftVal, coeTInstVal, monadInst, e])
|
||
let eNewType ← inferType eNew
|
||
unless (← isDefEq expectedType eNewType) do return none
|
||
return some eNew -- approach 3 worked
|
||
catch _ =>
|
||
/- If `m` is not a monad, then we try to use `tryCoe?`. -/
|
||
return none
|
||
else
|
||
return none
|
||
|
||
/-- Coerces `expr` to the type `expectedType`.
|
||
Returns `.some coerced` on successful coercion,
|
||
`.none` if the expression cannot by coerced to that type,
|
||
or `.undef` if we need more metavariable assignments. -/
|
||
def coerce? (expr expectedType : Expr) : MetaM (LOption Expr) := do
|
||
if let some lifted ← coerceMonadLift? expr expectedType then
|
||
return .some lifted
|
||
if (← whnfR expectedType).isForall then
|
||
if let some fn ← coerceToFunction? expr then
|
||
if ← isDefEq (← inferType fn) expectedType then
|
||
return .some fn
|
||
coerceSimple? expr expectedType
|
||
|
||
end Lean.Meta
|