Skip to content

Commit 02c8c2f

Browse files
authored
feat: use nondep flag in Expr.letE and LocalContext.ldecl (#8804)
This PR implements first-class support for nondependent let expressions in the elaborator; recall that a let expression `let x : t := v; b` is called *nondependent* if `fun x : t => b` typechecks, and the notation for a nondependent let expression is `have x := v; b`. Previously we encoded `have` using the `letFun` function, but now we make use of the `nondep` flag in the `Expr.letE` constructor for the encoding. This has been given full support throughout the metaprogramming interface and the elaborator. Key changes to the metaprogramming interface: - Local context `ldecl`s with `nondep := true` are generally treated as `cdecl`s. This is because in the body of a `have` expression the variable is opaque. Functions like `LocalDecl.isLet` by default return `false` for nondependent `ldecl`s. In the rare case where it is needed, they take an additional optional `allowNondep : Bool` flag (defaults to `false`) if the variable is being processed in a context where the value is relevant. - Functions such as `mkLetFVars` by default generalize nondependent let variables and create lambda expressions for them. The `generalizeNondepLet` flag (default true) can be set to false if `have` expressions should be produced instead. **Breaking change:** Uses of `letLambdaTelescope`/`mkLetFVars` need to use `generalizeNondepLet := false`. See the next item. - There are now some mapping functions to make telescoping operations more convenient. See `mapLetTelescope` and `mapLambdaLetTelescope`. There is also `mapLetDecl` as a counterpart to `withLetDecl` for creating `let`/`have` expressions. - Important note about the `generalizeNondepLet` flag: it should only be used for variables in a local context that the metaprogram "owns". Since nondependent let variables are treated as constants in most cases, the `value` field might refer to variables that do not exist, if for example those variables were cleared or reverted. Using `mapLetDecl` is always fine. - The simplifier will cache its let dependence calculations in the nondep field of let expressions. - The `intro` tactic still produces *dependent* local variables. Given that the simplifier will transform lets into haves, it would be surprising if that would prevent `intro` from creating a local variable whose value cannot be used. Note that nondependence of lets is not checked by the kernel. To external checker authors: If the elaborator gets the nondep flag wrong, we consider this to be an elaborator error. Feel free to typecheck `letE n t v b true` as if it were `app (lam n t b default) v` and please report issues. This PR follows up from #8751, which made sure the nondep flag was preserved in the C++ interface.
1 parent 2ebc001 commit 02c8c2f

40 files changed

+646
-292
lines changed

src/Lean/Elab/Binders.lean

Lines changed: 23 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -785,54 +785,33 @@ def elabLetDeclAux (id : Syntax) (binders : Array Syntax) (typeStx : Syntax) (va
785785
pure (type, val, binders)
786786
let kind := kindOfBinderName id.getId
787787
trace[Elab.let.decl] "{id.getId} : {type} := {val}"
788-
let elabBody : TermElabM Expr := do
789-
let body ← elabTermEnsuringType body expectedType?
790-
instantiateMVars body
791788
let result ←
792-
if config.zeta then
793-
let elabZetaCore (x : Expr) : TermElabM Expr := do
794-
addLocalVarInfo id x
795-
if let some h := config.eq? then
796-
let hTy ← mkEq x val
797-
withLocalDeclD h.getId hTy fun h' => do
798-
addLocalVarInfo h h'
799-
let body ← elabBody
800-
pure <| (← body.abstractM #[x, h']).instantiateRev #[val, ← mkEqRefl val]
789+
withLetDecl id.getId (kind := kind) type val (nondep := config.nondep) fun x => do
790+
let elabBody : TermElabM Expr :=
791+
elabTermEnsuringType body expectedType? >>= instantiateMVars
792+
addLocalVarInfo id x
793+
match config.eq? with
794+
| none =>
795+
let body ← elabBody
796+
if config.zeta then
797+
pure <| (← body.abstractM #[x]).instantiate1 val
801798
else
799+
mkLetFVars #[x] body (usedLetOnly := config.usedOnly) (generalizeNondepLet := false)
800+
| some h =>
801+
let hTy ← mkEq x val
802+
withLetDecl h.getId hTy (← mkEqRefl x) (nondep := true) fun h' => do
803+
addLocalVarInfo h h'
802804
let body ← elabBody
803-
pure <| (← body.abstractM #[x]).instantiate1 val
804-
if !config.nondep then
805-
withLetDecl id.getId (kind := kind) type val elabZetaCore
806-
else
807-
withLocalDecl id.getId (kind := kind) .default type elabZetaCore
808-
else
809-
if !config.nondep then
810-
withLetDecl id.getId (kind := kind) type val fun x => do
811-
addLocalVarInfo id x
812-
if let some h := config.eq? then
813-
let hTy ← mkEq x val
814-
withLocalDeclD h.getId hTy fun h' => do
815-
addLocalVarInfo h h'
816-
let body ← elabBody
817-
let body := (← body.abstractM #[h']).instantiate1 (← mkEqRefl x)
818-
mkLetFVars #[x] body (usedLetOnly := config.usedOnly)
819-
else
820-
let body ← elabBody
821-
mkLetFVars #[x] body (usedLetOnly := config.usedOnly)
822-
else
823-
withLocalDecl id.getId (kind := kind) .default type fun x => do
824-
addLocalVarInfo id x
825-
if let some h := config.eq? then
826-
-- TODO(kmill): Think about how to encode this case.
827-
let hTy ← mkEq x val
828-
withLocalDeclD h.getId hTy fun h' => do
829-
addLocalVarInfo h h'
830-
let body ← elabBody
831-
let f ← mkLambdaFVars #[x, h'] body
832-
return mkApp2 f val (← mkEqRefl val)
805+
if config.zeta then
806+
pure <| (← body.abstractM #[x, h']).instantiateRev #[val, ← mkEqRefl val]
807+
else if config.nondep then
808+
-- TODO(kmill): Think more about how to encode this case.
809+
-- Currently we produce `(fun (x : α) (h : x = val) => b) val rfl`.
810+
-- N.B. the nondep lets become lambdas here.
811+
let f ← mkLambdaFVars #[x, h'] body
812+
return mkApp2 f val (← mkEqRefl val)
833813
else
834-
let body ← elabBody
835-
mkLetFun x val body
814+
mkLetFVars #[x, h'] body (usedLetOnly := config.usedOnly) (generalizeNondepLet := false)
836815
if config.postponeValue then
837816
forallBoundedTelescope type binders.size fun xs type => do
838817
-- the original `fvars` from above are gone, so add back info manually

src/Lean/Elab/Match.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -670,7 +670,7 @@ where
670670
match p with
671671
| .forallE n d b bi => withLocalDecl n bi (← go d) fun x => do mkForallFVars #[x] (← go (b.instantiate1 x))
672672
| .lam n d b bi => withLocalDecl n bi (← go d) fun x => do mkLambdaFVars #[x] (← go (b.instantiate1 x))
673-
| .letE n t v b .. => withLetDecl n (← go t) (← go v) fun x => do mkLetFVars #[x] (← go (b.instantiate1 x))
673+
| .letE n t v b nondep => mapLetDecl n (← go t) (← go v) (nondep := nondep) fun x => go (b.instantiate1 x)
674674
| .app f a => return mkApp (← go f) (← go a)
675675
| .proj _ _ b => return p.updateProj! (← go b)
676676
| .mdata k b =>

src/Lean/Elab/MutualDef.lean

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -869,10 +869,11 @@ private partial def mkClosureForAux (toProcess : Array FVarId) : StateRefT Closu
869869
| .cdecl _ _ userName type bi k =>
870870
let toProcess ← pushLocalDecl toProcess fvarId userName type bi k
871871
mkClosureForAux toProcess
872-
| .ldecl _ _ userName type val _ k =>
872+
| .ldecl _ _ userName type val nondep k =>
873873
let zetaDeltaFVarIds ← getZetaDeltaFVarIds
874-
if !zetaDeltaFVarIds.contains fvarId then
875-
/- Non-dependent let-decl. See comment at src/Lean/Meta/Closure.lean -/
874+
-- Note: If `nondep` is true then `zetaDeltaFVarIds.contains fvarId` must be false.
875+
if nondep || !zetaDeltaFVarIds.contains fvarId then
876+
/- Nondependent let-decl. See comment at src/Lean/Meta/Closure.lean -/
876877
let toProcess ← pushLocalDecl toProcess fvarId userName type .default k
877878
mkClosureForAux toProcess
878879
else

src/Lean/Elab/PreDefinition/Main.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ private partial def ensureNoUnassignedLevelMVarsAtPreDef (preDef : PreDefinition
9393
checkCache { val := e : ExprStructEq } fun _ => do
9494
match e with
9595
| .forallE n d b c | .lam n d b c => withExpr e do visit d; withLocalDecl n c d fun x => visit (b.instantiate1 x)
96-
| .letE n t v b _ => withExpr e do visit t; visit v; withLetDecl n t v fun x => visit (b.instantiate1 x)
96+
| .letE n t v b nondep => withExpr e do visit t; visit v; withLetDecl n t v (nondep := nondep) fun x => visit (b.instantiate1 x)
9797
| .mdata _ b => withExpr e do visit b
9898
| .proj _ _ b => withExpr e do visit b
9999
| .sort u => visitLevel u (← read)

src/Lean/Elab/PreDefinition/Structural/BRecOn.lean

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,9 @@ private partial def replaceRecApps (recArgInfos : Array RecArgInfo) (positions :
133133
| Expr.forallE n d b c =>
134134
withLocalDecl n c (← loop below d) fun x => do
135135
mkForallFVars #[x] (← loop below (b.instantiate1 x))
136-
| Expr.letE n type val body _ =>
137-
withLetDecl n (← loop below type) (← loop below val) fun x => do
138-
mkLetFVars #[x] (← loop below (body.instantiate1 x)) (usedLetOnly := false)
136+
| Expr.letE n type val body nondep =>
137+
mapLetDecl n (← loop below type) (← loop below val) (nondep := nondep) (usedLetOnly := false) fun x => do
138+
loop below (body.instantiate1 x)
139139
| Expr.mdata d b =>
140140
if let some stx := getRecAppSyntax? e then
141141
withRef stx <| loop below b

src/Lean/Elab/PreDefinition/Structural/IndPred.lean

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ private partial def replaceIndPredRecApps (recArgInfo : RecArgInfo) (funType : E
5050
| Expr.forallE n d b c =>
5151
withLocalDecl n c (← loop d) fun x => do
5252
mkForallFVars #[x] (← loop (b.instantiate1 x))
53-
| Expr.letE n type val body _ =>
54-
withLetDecl n (← loop type) (← loop val) fun x => do
55-
mkLetFVars #[x] (← loop (body.instantiate1 x))
53+
| Expr.letE n type val body nondep =>
54+
mapLetDecl n (← loop type) (← loop val) (nondep := nondep) fun x => do
55+
loop (body.instantiate1 x)
5656
| Expr.mdata d b => do
5757
if let some stx := getRecAppSyntax? e then
5858
withRef stx <| loop b

src/Lean/Elab/PreDefinition/Structural/SmartUnfolding.lean

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ where
3232
match e with
3333
| Expr.lam .. => lambdaTelescope e fun xs b => do mkLambdaFVars xs (← visit b)
3434
| Expr.forallE .. => forallTelescope e fun xs b => do mkForallFVars xs (← visit b)
35-
| Expr.letE n type val body _ =>
36-
withLetDecl n type (← visit val) fun x => do mkLetFVars #[x] (← visit (body.instantiate1 x))
35+
| Expr.letE n type val body nondep =>
36+
mapLetDecl n type (← visit val) (nondep := nondep) fun x => do
37+
visit (body.instantiate1 x)
3738
| Expr.mdata d b => return mkMData d (← visit b)
3839
| Expr.proj n i s => return mkProj n i (← visit s)
3940
| Expr.app .. =>

src/Lean/Elab/PreDefinition/WF/Fix.lean

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,9 @@ where
8484
| Expr.forallE n d b c =>
8585
withLocalDecl n c (← loop F d) fun x => do
8686
mkForallFVars #[x] (← loop F (b.instantiate1 x))
87-
| Expr.letE n type val body _ =>
88-
withLetDecl n (← loop F type) (← loop F val) fun x => do
89-
mkLetFVars #[x] (← loop F (body.instantiate1 x)) (usedLetOnly := false)
87+
| Expr.letE n type val body nondep =>
88+
mapLetDecl n (← loop F type) (← loop F val) (nondep := nondep) (usedLetOnly := false) fun x => do
89+
loop F (body.instantiate1 x)
9090
| Expr.mdata d b =>
9191
if let some stx := getRecAppSyntax? e then
9292
withRef stx <| loop F b

src/Lean/Elab/PreDefinition/WF/GuessLex.lean

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,10 +241,10 @@ where
241241
loop param d
242242
withLocalDecl n c d fun x => do
243243
loop param (b.instantiate1 x)
244-
| Expr.letE n type val body _ =>
244+
| Expr.letE n type val body nondep =>
245245
loop param type
246246
loop param val
247-
withLetDecl n type val fun x => do
247+
withLetDecl n type val (nondep := nondep) fun x => do
248248
loop param (body.instantiate1 x)
249249
| Expr.mdata _d b =>
250250
if let some stx := getRecAppSyntax? e then

src/Lean/Elab/PreDefinition/WF/Preprocess.lean

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,14 +110,68 @@ builtin_dsimproc paramMatcher (_) := fun e => do
110110
let matcherApp' := { matcherApp with discrs := discrs', alts := alts' }
111111
return .continue <| matcherApp'.toExpr
112112

113-
/-- `let x := (wfParam e); body[x] ==> let x := e; body[wfParam y] -/
113+
private def anyLetValueIsWfParam (e : Expr) : Bool :=
114+
match e with
115+
| .letE _ _ v b _ => (isWfParam? v).isSome || anyLetValueIsWfParam b
116+
| _ => false
117+
118+
private def numLetsWithValueNotIsWfParam (e : Expr) (acc := 0) : Nat :=
119+
match e with
120+
| .letE _ _ v b _ => if (isWfParam? v).isSome then acc else numLetsWithValueNotIsWfParam b (acc + 1)
121+
| _ => acc
122+
123+
private partial def processParamLet (e : Expr) : MetaM Expr := do
124+
if let .letE _ t v b _ := e then
125+
if let some v' := isWfParam? v then
126+
if ← Meta.isProp t then
127+
processParamLet <| e.updateLetE! t v' b
128+
else
129+
let u ← getLevel t
130+
let b' := b.instantiate1 <| mkApp2 (.const ``wfParam [u]) t (.bvar 0)
131+
processParamLet <| e.updateLetE! t v' b'
132+
else
133+
let num := numLetsWithValueNotIsWfParam e
134+
assert! num > 0
135+
letBoundedTelescope e num fun xs b => do
136+
let b' ← processParamLet b
137+
mkLetFVars (usedLetOnly := false) (generalizeNondepLet := false) xs b'
138+
else
139+
return e
140+
141+
/--
142+
`let x : T := (wfParam e); body[x] ==> let x : T := e; body[wfParam y]` if `T` is not a proposition,
143+
otherwise `... ==> let x : T := e; body[x]`. (Applies to `have`s too.)
144+
145+
Note: simprocs are provided the head of a let telescope, but not intermediate lets.
146+
-/
114147
builtin_dsimproc paramLet (_) := fun e => do
115-
unless e.isLet do return .continue
116-
let some v := isWfParam? e.letValue! | return .continue
117-
let u ← getLevel e.letType!
118-
let body' := e.letBody!.instantiate1 <|
119-
mkApp2 (.const ``wfParam [u]) e.letType! (.bvar 0)
120-
return .continue <| e.updateLetE! e.letType! v body'
148+
unless e.isLet || anyLetValueIsWfParam e do return .continue
149+
return .continue (← processParamLet e)
150+
151+
/--
152+
Transforms non-Prop `have`s to `let`s, so that the values can be used in GuessLex and decreasing-by proofs.
153+
These `have`s may have been introdued by `simp`, which converts `let`s to `have`s.
154+
-/
155+
private def nonPropHaveToLet (e : Expr) : MetaM Expr := do
156+
Meta.transform e (pre := fun e => do
157+
if (← Meta.isProof e) then
158+
return .done e
159+
else if e.isLet then
160+
-- Recall that `Meta.transform` processes entire let telescopes,
161+
-- so we need to handle the telescope all at once.
162+
let lctx ← getLCtx
163+
let e' ← letTelescope e fun xs b => do
164+
let lctx' ← xs.foldlM (init := lctx) fun lctx' x => do
165+
let decl ← x.fvarId!.getDecl
166+
-- Clear the flag if it's not a prop.
167+
let decl' := decl.setNondep <| ← pure decl.isNondep <&&> Meta.isProp decl.type
168+
pure <| lctx'.addDecl decl'
169+
withLCtx' lctx' do
170+
mkLetFVars (usedLetOnly := false) (generalizeNondepLet := false) xs b
171+
return .continue e'
172+
else
173+
return .continue
174+
)
121175

122176
def preprocess (e : Expr) : MetaM Simp.Result := do
123177
unless wf.preprocess.get (← getOptions) do
@@ -141,9 +195,13 @@ def preprocess (e : Expr) : MetaM Simp.Result := do
141195
if h : as.size ≥ 2 then
142196
return .continue (mkAppN as[1] as[2:])
143197
return .continue
198+
199+
-- Transform `have`s to `let`s for non-propositions.
200+
let e'' ← nonPropHaveToLet e''
201+
144202
let result := { result with expr := e'' }
145203

146-
trace[Elab.definition.wf] "Attach-introduction:{indentExpr e'}\nto{indentExpr result.expr}\ncleaned up as{indentExpr e''}"
204+
trace[Elab.definition.wf] "Attach-introduction:{indentExpr e'}\nto{indentExpr result.expr}"
147205
result.addLambdas xs
148206

149207
end Lean.Elab.WF

0 commit comments

Comments
 (0)