Skip to content

Commit b064eec

Browse files
authored
feat: add do← for effect forwarding (#13931)
This PR introduces the `do← body` marker (ASCII `do<- body`), which lets ordinary continuation-taking wrappers like `withReader` or `Meta.withLocalDecl` participate in the surrounding `do` block's control flow. When `do← body` appears as the last argument of an application inside a `do` block, the body's `return`, `break`, `continue`, and `mut`-variable reassignments are forwarded out through the wrapper to the enclosing block. ```lean example : ReaderT Ctx IO Nat := do let mut x := 0 withReader (·.bump) (do← if cond then return 0 -- early-returns from the outer `do` x := x + 1) -- mutates the outer `x` return x ``` The syntax is reminiscent of a nested action `(← body)`, but unlike a nested action, `body` is not run eagerly in the `do` block context before the wrapping function is called. The wrapping function decides when to run `body`, and code is inserted to forward `body`'s effects to the outer `do` block. Internally, `elabDoExpr` and the `ControlInfo` inferer recognise the marker and route the body through the same `EffectForwarder` framework that already powers `try`/`catch`. The framework now lets each caller decide whether the surrounding continuation is dead: `try`/`catch` propagates `info.noFallthrough`, while `do←` always treats the continuation as live (an opaque wrapper might not invoke the body at all). A new `DoElemCont.withDeadCodeFromInfo` helper captures the shared pattern. A wrapper validator rejects shapes that don't fit `(… → m α) → m α` with a polymorphic `α` (e.g. `withConst`, `foldlM`) up front with a dedicated diagnostic, rather than letting a downstream type mismatch surface. `dropParens` moves from `Lean.Elab.Term` to `Lean.Parser.Term` since it is a generic term-syntax utility.
1 parent 9e0092d commit b064eec

13 files changed

Lines changed: 449 additions & 30 deletions

File tree

src/Lean/Elab/BuiltinDo.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ public import Lean.Elab.BuiltinDo.Jump
1515
public import Lean.Elab.BuiltinDo.Misc
1616
public import Lean.Elab.BuiltinDo.For
1717
public import Lean.Elab.BuiltinDo.TryCatch
18+
public import Lean.Elab.BuiltinDo.Forward
1819
public import Lean.Elab.BuiltinDo.Repeat
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/-
2+
Copyright (c) 2026 Lean FRO LLC. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Sebastian Graf
5+
-/
6+
module
7+
8+
prelude
9+
public import Lean.Elab.Do.Basic
10+
meta import Lean.Parser.Do
11+
import Lean.Elab.Do.Control
12+
import Lean.Elab.Do.InferControlInfo
13+
import Lean.Elab.Binders
14+
15+
namespace Lean.Elab.Do
16+
17+
open Lean.Parser.Term
18+
open Lean.Meta
19+
20+
@[builtin_term_elab Lean.Parser.Term.doForward]
21+
public def elabDoForward : Term.TermElab := fun _ _ =>
22+
throwError "\
23+
`do←` may only appear as the last argument of a function application \
24+
inside an enclosing `do` block, optionally inside a `fun` binder"
25+
26+
private def forwardHint (headApp : Term) (reason : MessageData) : MessageData :=
27+
m!"\
28+
`{headApp}` is not a valid `do←` wrapper: {reason}. The wrapper must have type \
29+
`(… → m α) → m α` for some `α` that is universally quantified in the wrapper's signature and \
30+
does not appear elsewhere."
31+
32+
/--
33+
Check that `probeExpr` (the elaboration of `headApp ?forwarded`) has the shape required for
34+
`do←`: its result type is `m α` for an unassigned `α`, the `forwarded` slot is `… → m α` with
35+
the same `α` (and `α` not occurring in the slot's input types), and `α` does not occur in any
36+
*other* explicit argument of the elaborated probe.
37+
-/
38+
private def validateForwarder (headApp : Term) (forwarded probeExpr : Expr) : MetaM Unit := do
39+
let reject {α} (reason : MessageData) : MetaM α := throwError forwardHint headApp reason
40+
let probeType ← whnfD (← instantiateMVars (← inferType probeExpr))
41+
let .app _ alphaRet := probeType
42+
| reject m!"its return type `{probeType}` is not of the form `m α`"
43+
unless alphaRet.isMVar do
44+
reject m!"its return type pins `α` to a concrete type"
45+
let argMentionsAlpha (arg : Expr) : MetaM (Option Expr) := do
46+
let ty ← inferType arg
47+
if (← instantiateMVars ty).findMVar? (· == alphaRet.mvarId!) |>.isSome then
48+
return some ty
49+
else return none
50+
forallTelescopeReducing (← inferType forwarded) fun args body => do
51+
unless ← isDefEq probeType (← whnfD (← instantiateMVars body)) do
52+
reject m!"the forwarded body's `α` differs from the wrapper's return `α`"
53+
for arg in args do
54+
if let some ty ← argMentionsAlpha arg then
55+
reject m!"`α` appears in the forwarded body's input type `{ty}`"
56+
forallTelescopeReducing (← inferType probeExpr.getAppFn) fun fvars _ => do
57+
for (fvar, appArg) in fvars.zip probeExpr.getAppArgs do
58+
unless (← fvar.fvarId!.getDecl).binderInfo matches .default do continue
59+
let appArgInst ← instantiateMVars appArg
60+
if appArgInst == forwarded then continue
61+
if let some ty ← argMentionsAlpha appArgInst then
62+
reject m!"`α` appears in an applied explicit argument of type `{ty}`"
63+
64+
/--
65+
Elaborate `e` as an application whose last argument is a `do←` body, performing the effect
66+
forwarding. Returns `none` if `e` doesn't have that shape.
67+
68+
Strategy: build `headApp ?forwarded` with a fresh metavariable in the body slot and elaborate
69+
it. Lean's elaborator handles named args, defaults, and dot notation. We then check the
70+
wrapper's shape against the probe; on success, the lifted body is assigned into `?forwarded`
71+
and the probe expression itself is the result.
72+
-/
73+
public def tryElabForwardApp? (e : Term) (dec : DoElemCont) : DoElabM (Option Expr) := do
74+
let some (headApp, arg) := Forward.matchApp? e | return none
75+
let (forwarded, probeExpr) ← controlAtTermElabM fun _ => withFreshMacroScope do
76+
let forwardedStx ← `(?forwarded)
77+
let forwarded ← Term.elabTerm forwardedStx none
78+
let probeExpr ← Term.elabTerm (← `($headApp $forwardedStx)) none
79+
pure (forwarded, probeExpr)
80+
validateForwarder headApp forwarded probeExpr
81+
let bodyInfo ← InferControlInfo.ofSeq arg.body
82+
let forwarder ← EffectForwarder.ofCont bodyInfo dec
83+
let liftedBody ← controlAtTermElabM fun runInBase => do
84+
Term.elabFunBinders (arg.binders.map (·.raw)) none fun bsExpr _ => runInBase do
85+
mkLambdaFVars bsExpr (← forwarder.lift (elabDoSeq arg.body))
86+
unless ← forwarded.mvarId!.checkedAssign liftedBody do
87+
throwError "the lifted body's type does not match the wrapper's body slot type"
88+
-- NB: No `withDeadCode` wrap on `restoreCont`'s result, because it is unclear whether `body` is
89+
-- even called.
90+
return some (← (← forwarder.restoreCont).mkBindUnlessPure probeExpr)
91+
92+
end Lean.Elab.Do

src/Lean/Elab/BuiltinDo/Jump.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ open Lean.Meta
1919
@[builtin_doElem_elab Lean.Parser.Term.doReturn] def elabDoReturn : DoElab := fun stx dec => do
2020
let `(doReturn| return $[$e?]?) := stx | throwUnsupportedSyntax
2121
let returnCont ← getReturnCont
22-
-- When using the ControlLifter framework, `returnCont.resultType` can be different than the
22+
-- When using the EffectForwarder framework, `returnCont.resultType` can be different than the
2323
-- result type of the `do` block. That's why we track it separately.
2424
-- trace[Elab.do] "return e: {e} with type {returnCont.resultType}"
2525
let e ← match e? with

src/Lean/Elab/BuiltinDo/Misc.lean

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module
77

88
prelude
99
public import Lean.Elab.Do.Basic
10+
public import Lean.Elab.BuiltinDo.Forward
1011
meta import Lean.Parser.Do
1112

1213
public section
@@ -24,6 +25,8 @@ open InternalSyntax in
2425

2526
@[builtin_doElem_elab Lean.Parser.Term.doExpr] def elabDoExpr : DoElab := fun stx dec => do
2627
let `(doExpr| $e:term) := stx | throwUnsupportedSyntax
28+
if let some r ← tryElabForwardApp? e dec then
29+
return r
2730
let mα ← mkMonadApp dec.resultType
2831
let e ← Term.elabTermEnsuringType e mα
2932
dec.mkBindUnlessPure e

src/Lean/Elab/BuiltinDo/TryCatch.lean

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ namespace Lean.Elab.Do
1717
open Lean.Parser.Term
1818
open Lean.Meta
1919

20-
private def elabDoCatch (lifter : ControlLifter) (body : Expr) (catch_ : TSyntax ``doCatch) : DoElabM Expr := do
20+
private def elabDoCatch (lifter : EffectForwarder) (body : Expr) (catch_ : TSyntax ``doCatch) : DoElabM Expr := do
2121
let mi := (← read).monadInfo
2222
let `(doCatch| catch $x $[: $eType?]? => $catchSeq) := catch_ | throwUnsupportedSyntax
2323
let x := Term.mkExplicitBinder x <| match eType? with
@@ -56,7 +56,8 @@ private def elabDoCatch (lifter : ControlLifter) (body : Expr) (catch_ : TSyntax
5656
-- So we need to pack up our effects and unpack them after the `try`.
5757
-- We could optimize for the terminal action case by omitting the state tuple ... in the future.
5858
let mi := (← read).monadInfo
59-
let lifter ← ControlLifter.ofCont (← inferControlInfoElem stx) dec
59+
let info ← inferControlInfoElem stx
60+
let lifter ← EffectForwarder.ofCont info dec
6061
let body ← do
6162
let body ← lifter.lift (elabDoSeq trySeq)
6263
let body ← catches.foldlM (init := body) fun body catch_ => do
@@ -75,4 +76,6 @@ private def elabDoCatch (lifter : ControlLifter) (body : Expr) (catch_ : TSyntax
7576
let instFunctor ← Term.mkInstMVar <| mkApp (mkConst ``Functor [mi.u, mi.v]) mi.m
7677
pure <| mkApp7 (mkConst ``tryFinally [mi.u, mi.v])
7778
mi.m lifter.liftedDoBlockResultType β instMonadFinally instFunctor body fin
78-
(← lifter.restoreCont).mkBindUnlessPure body
79+
-- `info` already combines try and catch arms via `ControlInfo.alternative`,
80+
-- so its `noFallthrough` is the right liveness signal for the continuation.
81+
((← lifter.restoreCont).withDeadCodeFromInfo info).mkBindUnlessPure body

src/Lean/Elab/Do/Basic.lean

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,10 @@ def DoElemCont.elabAsSyntacticallyDeadCode (dec : DoElemCont) : DoElabM Unit :=
542542
s.restore
543543
Core.setMessageLog (log ++ warnings)
544544

545+
/-- Wrap `dec.k` so it elaborates as dead iff `info.noFallthrough`. -/
546+
def DoElemCont.withDeadCodeFromInfo (dec : DoElemCont) (info : ControlInfo) : DoElemCont :=
547+
{ dec with k := withDeadCode (if info.noFallthrough then .deadSemantically else .alive) dec.k }
548+
545549
/--
546550
Given a list of mut vars `vars` and an FVar `tupleVar` binding a tuple, bind the mut vars to the
547551
fields of the tuple and call `k` in the resulting local context.
@@ -646,8 +650,7 @@ def DoElemCont.withDuplicableCont (nondupDec : DoElemCont) (callerInfo : Control
646650
withLocalDeclD nondupDec.resultName nondupDec.resultType fun r => do
647651
withLocalDeclsDND (mutDecls.map fun (d : LocalDecl) => (d.userName, d.type)) fun muts => do
648652
for (x, newX) in mutVars.zip muts do Term.addTermInfo' x newX
649-
withDeadCode (if callerInfo.noFallthrough then .deadSemantically else .alive) do
650-
let e ← nondupDec.k
653+
let e ← (nondupDec.withDeadCodeFromInfo callerInfo).k
651654
mkLambdaFVars (#[r] ++ muts) e
652655
unless ← joinRhsMVar.mvarId!.checkedAssign joinRhs do
653656
joinRhsMVar.mvarId!.withContext do

src/Lean/Elab/Do/Control.lean

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -186,20 +186,27 @@ def ControlStack.mkPure (base : ControlStack) (resultName : Name) : DoElabM Expr
186186
let r ← getFVarFromUserName resultName
187187
base.runInBase <| mkApp4 (mkConst ``Pure.pure [mi.u, mi.v]) mi.m instPure (← inferType r) r
188188

189-
structure ControlLifter where
189+
/-- Plan for embedding a non-tail-resumptive body inside `origCont` via a monad-transformer stack. -/
190+
structure EffectForwarder where
191+
/-- Continuation of the surrounding `do` block; restored after the body. -/
190192
origCont : DoElemCont
193+
/-- Substack at which the early-return handler was installed, if any. -/
191194
returnBase? : Option ControlStack
195+
/-- Substack at which the `break` handler was installed, if any. -/
192196
breakBase? : Option ControlStack
197+
/-- Substack at which the `continue` handler was installed, if any. -/
193198
continueBase? : Option ControlStack
194-
pureBase : ControlStack
195-
pureDeadCode : CodeLiveness
199+
/-- The full transformer stack over the base monad. -/
200+
liftedStack : ControlStack
201+
/-- The body's elaboration type, `stM dec.resultType`. -/
196202
liftedDoBlockResultType : Expr
197203

198204
-- abbrev M := List
199205
-- #reduce (types := true) M (Except Nat (Option (Option Bool) × String))
200206
-- #reduce (types := true) OptionT (OptionT (StateT String (ExceptT Nat M))) Bool
201207

202-
def ControlLifter.ofCont (info : ControlInfo) (dec : DoElemCont) : DoElabM ControlLifter := do
208+
/-- Build the lifter plan for a body whose effects are summarised by `info`. -/
209+
def EffectForwarder.ofCont (info : ControlInfo) (dec : DoElemCont) : DoElabM EffectForwarder := do
203210
let mi := (← read).monadInfo
204211
let reassignedMutVars := (← read).mutVars |>.filter (info.reassigns.contains ·.getId)
205212
let reassignedMutVarNames := reassignedMutVars.map (·.getId)
@@ -231,21 +238,20 @@ def ControlLifter.ofCont (info : ControlInfo) (dec : DoElemCont) : DoElabM Contr
231238
returnBase?,
232239
breakBase?,
233240
continueBase?,
234-
pureBase := controlStack,
235-
-- The success continuation `origCont` is dead code iff the `ControlInfo` says so semantically.
236-
pureDeadCode := if info.noFallthrough then .deadSemantically else .alive,
241+
liftedStack := controlStack,
237242
liftedDoBlockResultType := (← controlStack.stM dec.resultType),
238243
}
239244

240245
/--
241-
This function is like `MonadControl.liftWith fun runInBase => elabElem (runInBase pure)`.
242-
All continuations should be thought of as wrapped in `runInBase`, so that their effects are embedded
243-
in the terminal `stM m (t m)` result type. This wrapping will be realized by
244-
`ControlStack.synthesizeConts`, after we know what the transformer stack `t` looks like.
245-
What `t` looks like depends on whether reassignments, early `return`, `break` and `continue` are
246-
used, considering *all* the use sites of `ControlLifter.lift`.
246+
Elaborate `elabElem` in the lifted stack: install `break`/`continue`/`return`
247+
handlers funnelling into `liftedStack` and set the doBlock result type to
248+
`liftedDoBlockResultType`. Semantically
249+
`MonadControl.liftWith fun runInBase => elabElem (runInBase pure)`. Conts
250+
passed to `elabElem` are implicitly wrapped in `runInBase`, realised later by
251+
`ControlStack.synthesizeConts` once the transformer stack `t` is fixed by
252+
aggregating effects across *all* `lift` sites for this lifter.
247253
-/
248-
def ControlLifter.lift (l : ControlLifter) (elabElem : DoElemCont → DoElabM Expr) : DoElabM Expr := do
254+
def EffectForwarder.lift (l : EffectForwarder) (elabElem : DoElemCont → DoElabM Expr) : DoElabM Expr := do
249255
let oldBreakCont ← getBreakCont
250256
let oldContinueCont ← getContinueCont
251257
let oldReturnCont ← getReturnCont
@@ -262,9 +268,10 @@ def ControlLifter.lift (l : ControlLifter) (elabElem : DoElemCont → DoElabM Ex
262268
| some returnBase => { oldReturnCont with k := returnBase.mkReturn }
263269
| _ => oldReturnCont
264270
let contInfo := ContInfo.toContInfoRef { breakCont, continueCont, returnCont }
265-
let pureCont := { l.origCont with k := l.pureBase.mkPure l.origCont.resultName, kind := .duplicable }
271+
let pureCont := { l.origCont with k := l.liftedStack.mkPure l.origCont.resultName, kind := .duplicable }
266272
withReader (fun ctx => { ctx with contInfo, doBlockResultType := l.liftedDoBlockResultType }) do
267273
elabElem pureCont
268274

269-
def ControlLifter.restoreCont (l : ControlLifter) : DoElabM DoElemCont := do
270-
l.pureBase.restoreCont { l.origCont with k := withDeadCode l.pureDeadCode l.origCont.k }
275+
/-- Build a `DoElemCont` that unpacks the lifted body's result and resumes `origCont.k`. -/
276+
def EffectForwarder.restoreCont (l : EffectForwarder) : DoElabM DoElemCont :=
277+
l.liftedStack.restoreCont l.origCont
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/-
2+
Copyright (c) 2026 Lean FRO LLC. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Sebastian Graf
5+
-/
6+
module
7+
8+
prelude
9+
public import Lean.Parser.Term
10+
import Lean.Elab.Term.TermElabM
11+
meta import Lean.Parser.Do
12+
13+
namespace Lean.Elab.Do
14+
15+
open Lean.Parser.Term
16+
17+
/--
18+
A body that effect-forwards into the enclosing `do` block via a `do←` marker, captured from
19+
syntax of the form `do← body` or `fun b₁ … bₖ => do← body`. `binders` is empty in the bare
20+
form.
21+
-/
22+
public structure ForwardArg where
23+
binders : Array (TSyntax ``funBinder)
24+
body : TSyntax ``doSeq
25+
26+
/--
27+
If `e` is a function application whose last argument effect-forwards into the enclosing `do`
28+
block (its body bears a `do←` marker, possibly under `fun` binders), return the application
29+
head sans the forwarding argument together with the `ForwardArg` description.
30+
-/
31+
public def Forward.matchApp? (e : Term) : Option (Term × ForwardArg) := do
32+
let `($head $args*) := (⟨dropParens e.raw⟩ : Term) | none
33+
let last : Term := ⟨dropParens (← args.back?).raw⟩
34+
let arg : ForwardArg ←
35+
if last.raw.isOfKind ``doForward then
36+
some ⟨#[], ⟨last.raw[2]⟩⟩
37+
else if let `(fun $bs:funBinder* => $rest) := last then
38+
let rest := dropParens rest.raw
39+
if rest.isOfKind ``doForward then some ⟨bs, ⟨rest[2]⟩⟩ else none
40+
else none
41+
let preArgs := args.pop
42+
let head' : Term :=
43+
if preArgs.isEmpty then head
44+
else ⟨mkNode ``Lean.Parser.Term.app #[head, mkNullNode preArgs]⟩
45+
some (head', arg)
46+
47+
end Lean.Elab.Do

src/Lean/Elab/Do/InferControlInfo.lean

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module
77

88
prelude
99
public import Lean.Elab.Term
10+
public import Lean.Elab.Do.ForwardSyntax
1011
meta import Lean.Parser.Do
1112
import Lean.Elab.Do.PatternVar
1213

@@ -56,6 +57,7 @@ structure ControlInfo where
5657
reassigns : NameSet := {}
5758
deriving Inhabited
5859

60+
/-- A `ControlInfo` for an element that always falls through normally with a single exit. -/
5961
def ControlInfo.pure : ControlInfo := {}
6062

6163
/--
@@ -64,6 +66,7 @@ at all (so no regular exits and the next element is trivially unreachable).
6466
-/
6567
def ControlInfo.empty : ControlInfo := { numRegularExits := 0, noFallthrough := true }
6668

69+
/-- Combine info for `a; b`: union effect flags, exits/dead follow `b`/either. -/
6770
def ControlInfo.sequence (a b : ControlInfo) : ControlInfo := {
6871
-- Syntactic fields aggregate unconditionally; the elaborator keeps visiting `b` unless `a` is
6972
-- a syntactically-terminal element (only top-level `return`/`break`/`continue` are, via
@@ -77,6 +80,7 @@ def ControlInfo.sequence (a b : ControlInfo) : ControlInfo := {
7780
noFallthrough := a.noFallthrough || b.noFallthrough,
7881
}
7982

83+
/-- Combine info for branches `a | b`: union flags, sum exits, dead iff both dead. -/
8084
def ControlInfo.alternative (a b : ControlInfo) : ControlInfo := {
8185
breaks := a.breaks || b.breaks,
8286
continues := a.continues || b.continues,
@@ -127,7 +131,14 @@ partial def ofElem (stx : TSyntax `doElem) : TermElabM ControlInfo := do
127131
| `(doElem| break) => return { breaks := true, numRegularExits := 0, noFallthrough := true }
128132
| `(doElem| continue) => return { continues := true, numRegularExits := 0, noFallthrough := true }
129133
| `(doElem| return $[$_]?) => return { returnsEarly := true, numRegularExits := 0, noFallthrough := true }
130-
| `(doExpr| $_:term) => return { numRegularExits := 1 }
134+
| `(doExpr| $e:term) =>
135+
if let some (_, arg) := Forward.matchApp? e then
136+
-- The last arg is a `do + resume $body` block. Depending on the function, it might or might
137+
-- not be called. We may definitely fall through and the control lifting mechanism prevents
138+
-- creation of a join point.
139+
return { (← ofSeq arg.body) with noFallthrough := false, numRegularExits := 1 }
140+
else
141+
return { numRegularExits := 1 }
131142
| `(doElem| do $doSeq) => ofSeq doSeq
132143
-- Let
133144
| `(doElem| let $[mut]? $_:letConfig $_:letDecl) => return .pure
@@ -262,8 +273,10 @@ end
262273

263274
end InferControlInfo
264275

276+
/-- Infer the `ControlInfo` of `doSeq`. -/
265277
def inferControlInfoSeq (doSeq : TSyntax ``doSeq) : TermElabM ControlInfo :=
266278
InferControlInfo.ofSeq doSeq
267279

280+
/-- Infer the `ControlInfo` of a single doElem. -/
268281
def inferControlInfoElem (doElem : TSyntax `doElem) : TermElabM ControlInfo :=
269282
InferControlInfo.ofElem doElem

src/Lean/Elab/Term/TermElabM.lean

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1660,11 +1660,6 @@ private def isLambdaWithImplicit (stx : Syntax) : Bool :=
16601660
| `(fun $binders* => $_) => binders.raw.any fun b => b.isOfKind ``Lean.Parser.Term.implicitBinder || b.isOfKind `Lean.Parser.Term.instBinder
16611661
| _ => false
16621662

1663-
private partial def dropTermParens : Syntax → Syntax := fun stx =>
1664-
match stx with
1665-
| `(($stx)) => dropTermParens stx
1666-
| _ => stx
1667-
16681663
private def isHole (stx : Syntax) : Bool :=
16691664
stx.isOfKind ``Lean.Parser.Term.hole || stx.isOfKind ``Lean.Parser.Term.syntheticHole
16701665

@@ -1692,7 +1687,7 @@ def mkNoImplicitLambdaAnnotation (type : Expr) : Expr :=
16921687

16931688
/-- Block usage of implicit lambdas if `stx` is `@f` or `@f arg1 ...` or `fun` with an implicit binder annotation. -/
16941689
def blockImplicitLambda (stx : Syntax) : Bool :=
1695-
let stx := dropTermParens stx
1690+
let stx := Parser.Term.dropParens stx
16961691
-- TODO: make it extensible
16971692
isExplicit stx || isExplicitApp stx || isLambdaWithImplicit stx || isHole stx || isTacticBlock stx ||
16981693
isNoImplicitLambda stx || isTypeAscription stx

0 commit comments

Comments
 (0)