Skip to content

Commit 192e182

Browse files
sgraf812claude
andcommitted
fix: fresh result type in ControlLifter.lift for monad resolution
The pure continuation in `ControlLifter.lift` now uses a fresh result type `?α` instead of `l.origCont.resultType`, mirroring the legacy elaborator's `DoResultPR` pattern. This allows monad resolution to succeed independently of the expected result type. For example, `evalConstCheck Nat ``Nat name` in a `CoreM (Option Nat)` try/catch context: with `Option Nat` as expected type, Lean cannot decompose `?m Nat =?= CoreM (Option Nat)`, leaving `?m` stuck. With a fresh `?α`, Lean decomposes `?m Nat =?= CoreM ?α` successfully. The coercion from `?α` to the original result type is inserted by `mkPure` via `ensureHasType`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 1d1c5c6 commit 192e182

File tree

2 files changed

+36
-3
lines changed

2 files changed

+36
-3
lines changed

src/Lean/Elab/Do/Control.lean

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,13 +178,15 @@ def ControlStack.mkReturn (base : ControlStack) (r : Expr) : DoElabM Expr := do
178178
synthUsingDefEq "early return result type" mγ mγ'
179179
base.runInBase <| mkApp5 (mkConst ``EarlyReturnT.return [mi.u, mi.v]) ρ mi.m δ instMonad r
180180

181-
def ControlStack.mkPure (base : ControlStack) (resultName : Name) : DoElabM Expr := do
181+
def ControlStack.mkPure (base : ControlStack) (resultName : Name)
182+
(expectedResultType : Expr) : DoElabM Expr := do
182183
let mi := { (← read).monadInfo with m := (← base.m) }
183184
let instMonad ← mkInstMonad mi
184185
let instPure := instMonad |> mkApp2 (mkConst ``Monad.toApplicative [mi.u, mi.v]) mi.m
185186
|> mkApp2 (mkConst ``Applicative.toPure [mi.u, mi.v]) mi.m
186187
let r ← getFVarFromUserName resultName
187-
base.runInBase <| mkApp4 (mkConst ``Pure.pure [mi.u, mi.v]) mi.m instPure (← inferType r) r
188+
let r ← Term.ensureHasType expectedResultType r
189+
base.runInBase <| mkApp4 (mkConst ``Pure.pure [mi.u, mi.v]) mi.m instPure expectedResultType r
188190

189191
structure ControlLifter where
190192
origCont : DoElemCont
@@ -263,7 +265,17 @@ def ControlLifter.lift (l : ControlLifter) (elabElem : DoElemCont → DoElabM Ex
263265
| some returnBase => { oldReturnCont with k := returnBase.mkReturn }
264266
| _ => oldReturnCont
265267
let contInfo := ContInfo.toContInfoRef { breakCont, continueCont, returnCont }
266-
let pureCont := { l.origCont with k := l.pureBase.mkPure l.origCont.resultName, kind := .duplicable }
268+
-- Use a fresh result type for the pure continuation, mirroring the legacy elaborator's
269+
-- `DoResultPR` pattern. This allows monad resolution to succeed independently of the
270+
-- expected result type. For example, `evalConstCheck Nat ``Nat name : ?m Nat` in a
271+
-- `CoreM (Option Nat)` context: with `dec.resultType = Option Nat`, Lean cannot decompose
272+
-- `?m Nat =?= CoreM (Option Nat)` (since `Nat ≠ Option Nat`), leaving `?m` stuck.
273+
-- With a fresh `?α`, Lean decomposes `?m Nat =?= CoreM ?α` successfully.
274+
-- The coercion from `?α` to the original result type is inserted by `mkPure`.
275+
let pureCont := { l.origCont with
276+
resultType := (← mkFreshResultType `α),
277+
k := l.pureBase.mkPure l.origCont.resultName l.origCont.resultType,
278+
kind := .duplicable }
267279
withReader (fun ctx => { ctx with contInfo, doBlockResultType := l.liftedDoBlockResultType }) do
268280
elabElem pureCont
269281

tests/elab/doTryCatchCoerce.lean

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import Lean
2+
3+
/-!
4+
Test that the new do elaborator correctly handles monad-polymorphic actions in
5+
`try`/`catch` blocks when the action's result type differs from the continuation's
6+
expected result type but is coercible.
7+
8+
Without the fix, `elabDoExpr` passes `m dec.resultType` (e.g., `CoreM (Option Nat)`)
9+
as the expected type. `evalConstCheck Nat ``Nat name : ?m Nat` cannot decompose
10+
`?m Nat =?= CoreM (Option Nat)` (since `Nat ≠ Option Nat`), leaving `?m` stuck.
11+
12+
The fix gives the pure continuation a fresh result type `?α`, so Lean decomposes
13+
`?m Nat =?= CoreM ?α` successfully, then coerces `Nat → Option Nat` inside `mkPure`.
14+
-/
15+
16+
open Lean in
17+
def evalStatsReport? (name : Name) : CoreM (Option Nat) := do
18+
try
19+
unsafe evalConstCheck Nat ``Nat name
20+
catch _ =>
21+
return none

0 commit comments

Comments
 (0)