Skip to content

Commit bb8a6fa

Browse files
committed
feat: use nondep flag in Expr.letE and LocalContext.ldecl
This PR implements first-class support for nondependent let expressions in the elaborator; recall that a let expression `let x := v; b` is called *nondependent* if `fun x => 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 fully supported throughout the metaprogramming interface and the elaborator. Note that nondependence of lets is not checked by the kernel. Follows up from leanprover#8751, which made sure the nondep flag was preserved in the C++ interface.
1 parent 2ebc001 commit bb8a6fa

38 files changed

+472
-291
lines changed

src/Lean/Elab/Binders.lean

Lines changed: 24 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -785,54 +785,34 @@ 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+
let pf ← mkEqRefl x
803+
withLetDecl h.getId hTy pf (nondep := true) fun h' => do
804+
addLocalVarInfo h h'
802805
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)
806+
if config.zeta then
807+
pure <| (← body.abstractM #[x, h']).instantiateRev #[val, pf]
808+
else if config.nondep then
809+
-- TODO(kmill): Think more about how to encode this case.
810+
-- Currently we produce `(fun (x : α) (h : x = val) => b) val rfl`.
811+
-- N.B. the nondep lets become lambdas here.
812+
let f ← mkLambdaFVars #[x, h'] body
813+
return mkApp2 f val pf
833814
else
834-
let body ← elabBody
835-
mkLetFun x val body
815+
mkLetFVars #[x, h'] body (usedLetOnly := config.usedOnly) (generalizeNondepLet := false)
836816
if config.postponeValue then
837817
forallBoundedTelescope type binders.size fun xs type => do
838818
-- 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: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -869,10 +869,10 @@ 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+
if nondep || !zetaDeltaFVarIds.contains fvarId then
875+
/- Nondependent let-decl. See comment at src/Lean/Meta/Closure.lean -/
876876
let toProcess ← pushLocalDecl toProcess fvarId userName type .default k
877877
mkClosureForAux toProcess
878878
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: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,11 @@ 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+
-- Turn `have`s into `let`s if they are not propositions.
138+
let nondep ← pure nondep <&&> not <$> Meta.isProp type
139+
mapLetDecl n (← loop below type) (← loop below val) (nondep := nondep) (usedLetOnly := false) fun x => do
140+
loop below (body.instantiate1 x)
139141
| Expr.mdata d b =>
140142
if let some stx := getRecAppSyntax? e then
141143
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: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,12 @@ 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+
-- Convert `have`s to `let`s if they aren't propositions so that the values
89+
-- of local declarations can be used within decreasing-by proofs.
90+
let nondep ← pure nondep <&&> Meta.isProp type
91+
mapLetDecl n (← loop F type) (← loop F val) (nondep := nondep) (usedLetOnly := false) fun x => do
92+
loop F (body.instantiate1 x)
9093
| Expr.mdata d b =>
9194
if let some stx := getRecAppSyntax? e then
9295
withRef stx <| loop F b

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,10 +241,12 @@ 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+
-- Convert `have`s to `let`s if they aren't propositions so that the values of local declarations can be used.
248+
let nondep ← pure nondep <&&> Meta.isProp type
249+
withLetDecl n type val (nondep := nondep) fun x => do
248250
loop param (body.instantiate1 x)
249251
| Expr.mdata _d b =>
250252
if let some stx := getRecAppSyntax? e then

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

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -110,14 +110,16 @@ 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+
/-- `let x := (wfParam e); body[x] ==> let x := e; body[wfParam y]`
114+
Also accepts non-Prop `have`s as well. -/
114115
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'
116+
let .letE _ t v b _ := e | return .continue
117+
let some v := isWfParam? v | return .continue
118+
if ← Meta.isProp t then return .continue <| e.updateLetE! t v b
119+
let u ← getLevel t
120+
let body' := b.instantiate1 <|
121+
mkApp2 (.const ``wfParam [u]) t (.bvar 0)
122+
return .continue <| e.updateLetE! t v body'
121123

122124
def preprocess (e : Expr) : MetaM Simp.Result := do
123125
unless wf.preprocess.get (← getOptions) do

0 commit comments

Comments
 (0)