The previous implementation was using the following heuristic
```lean
      -- heuristic: use non-dependent arrows only if possible for whole group to avoid
      -- noisy mix like `(α : Type) → Type → (γ : Type) → ...`.
      let dependent := curNames.any fun n => hasIdent n.getId stxBody
```
The result produced by this heuristic was **not** producing an
accidental name capture, but I agree
it was confusing to have `∀ (a : True), ∃ a, a = a : Prop` instead of
`True → ∃ a, a = a : Prop` since there is no dependency.
AFAICT, all examples affected by this commit have a better output now.

cc @digma0 @kha
This commit is contained in:
Leonardo de Moura 2022-09-15 11:02:46 -07:00
parent 4f1f20bc97
commit 10a56bf4a1
10 changed files with 42 additions and 17 deletions

View file

@ -550,9 +550,29 @@ def delabLam : Delab :=
| `(fun $binderGroups* => $stxBody) => `(fun $group $binderGroups* => $stxBody)
| _ => `(fun $group => $stxBody)
/--
Similar to `delabBinders`, but tracking whether `forallE` is dependent or not.
See issue #1571
-/
private partial def delabForallBinders (delabGroup : Array Syntax → Bool → Syntax → Delab) (curNames : Array Syntax := #[]) (curDep := false) : Delab := do
let dep := !(← getExpr).isArrow
if !curNames.isEmpty && dep != curDep then
-- don't group
delabGroup curNames curDep (← delab)
else
let curDep := dep
if ← shouldGroupWithNext then
-- group with nested binder => recurse immediately
withBindingBodyUnusedName fun stxN => delabForallBinders delabGroup (curNames.push stxN) curDep
else
-- don't group => delab body and prepend current binder group
let (stx, stxN) ← withBindingBodyUnusedName fun stxN => return (← delab, stxN)
delabGroup (curNames.push stxN) curDep stx
@[builtinDelab forallE]
def delabForall : Delab :=
delabBinders fun curNames stxBody => do
def delabForall : Delab := do
delabForallBinders fun curNames dependent stxBody => do
let e ← getExpr
let prop ← try isProp e catch _ => pure false
let stxT ← withBindingDomain delab
@ -562,9 +582,6 @@ def delabForall : Delab :=
-- here `curNames.size == 1`
| BinderInfo.instImplicit => `(bracketedBinderF|[$curNames.back : $stxT])
| _ =>
-- heuristic: use non-dependent arrows only if possible for whole group to avoid
-- noisy mix like `(α : Type) → Type → (γ : Type) → ...`.
let dependent := curNames.any fun n => hasIdent n.getId stxBody
-- NOTE: non-dependent arrows are available only for the default binder info
if dependent then
if prop && !(← getPPOption getPPPiBinderTypes) then

5
tests/lean/1571.lean Normal file
View file

@ -0,0 +1,5 @@
#check True → (∃ a : Nat, a = a)
-- True → ∃ a, a = a : Prop
#check True → (∃ b : Nat, b = b)
-- True → ∃ b, b = b : Prop

View file

@ -0,0 +1,2 @@
True → ∃ a, a = a : Prop
True → ∃ b, b = b : Prop

View file

@ -141,7 +141,7 @@
IsSmooth fun g => g
Transformer
fun redf a => redf
[Meta.synthInstance.resume] propagating ∀ (a : α),
[Meta.synthInstance.resume] propagating α
IsSmooth fun a => a to subgoal α → IsSmooth fun g => g of ∀ (a : α), IsSmooth fun g => g a
[Meta.synthInstance.resume] size: 2
[Meta.synthInstance.resume] propagating ∀ (a : α),
@ -155,10 +155,10 @@
IsSmooth fun g => g
Transformer
fun redf a => redf
[Meta.synthInstance.resume] propagating ∀ (a : α),
[Meta.synthInstance.resume] propagating α
IsSmooth fun b a => b a to subgoal α → IsSmooth fun g => g of ∀ (a : α), IsSmooth fun g => g a
[Meta.synthInstance.resume] size: 8
[Meta.synthInstance.resume] propagating ∀ (a : α),
[Meta.synthInstance.resume] propagating α
IsSmooth fun a => a to subgoal α → IsSmooth fun g => g of ∀ (a : α), IsSmooth fun g => g a
[Meta.synthInstance.resume] size: 5
[Meta.synthInstance.resume] propagating ∀ (a : α),

View file

@ -30,7 +30,8 @@ fun {α}
a =>
@toString α inst a
(α : Type) → α
(α β : Type) → α
(α : Type) →
Type → α
Type → Type → Type
(α : Type) → αα
{α : Type} → α

View file

@ -5,7 +5,7 @@ this : z.x = 1
z : A
this : z.x = 1
⊢ z.x = 1
@A.rec : {motive : A → Sort u_1} → ((x : Nat) → x = 1 → motive (A.mk x)) → (t : A) → motive t
@A.rec : {motive : A → Sort u_1} → ((x : Nat) → (a' : x = 1) → motive (A.mk x)) → (t : A) → motive t
z : A
x : Nat
a' : x = 1

View file

@ -1,8 +1,8 @@
∀ (p p_1 : Prop),
p = p_1 →
∀ {inst : Decidable p} {inst_1 : Decidable p_1} (a a_1 : Nat),
a = a_1 → ∀ (h : a > 0), g p a h = g p_1 a_1 (_ : a_1 > 0)
∀ {inst : Decidable p} {inst_1 : Decidable p_1} (a a_1 : Nat) (e_3 : a = a_1) (h : a > 0),
g p a h = g p_1 a_1 (_ : a_1 > 0)
∀ (p p_1 : Prop),
p = p_1 →
∀ {inst : Decidable p} [inst_1 : Decidable p_1] (a a_1 : Nat),
a = a_1 → ∀ (h : a > 0), g p a h = g p_1 a_1 (_ : a_1 > 0)
∀ {inst : Decidable p} [inst_1 : Decidable p_1] (a a_1 : Nat) (e_3 : a = a_1) (h : a > 0),
g p a h = g p_1 a_1 (_ : a_1 > 0)

View file

@ -4,7 +4,7 @@ cap : Nat
backing : Fin cap → Option α
size : Nat
h_size : size ≤ cap
h_full : ∀ (i : Nat), i < size → Option.isSome (backing { val := i, isLt := (_ : i < cap) }) = true
h_full : ∀ (i : Nat) (h : i < size), Option.isSome (backing { val := i, isLt := (_ : i < cap) }) = true
i : Nat
h : i < size
⊢ Option.isSome

View file

@ -11,7 +11,7 @@ protected def Nat.add : Nat → Nat → Nat :=
fun x x_1 =>
Nat.brecOn (motive := fun x => Nat → Nat) x_1
(fun x f x_2 =>
(match (motive := (x x : Nat) → Nat.below (motive := fun x => Nat → Nat) x → Nat) x_2, x with
(match (motive := Nat → (x : Nat) → Nat.below (motive := fun x => Nat → Nat) x → Nat) x_2, x with
| a, Nat.zero => fun x => a
| a, Nat.succ b => fun x => Nat.succ (PProd.fst x.fst a))
f)

View file

@ -25,6 +25,6 @@ p ∧ ¬q : Prop
¬p = q : Prop
¬p = q : Prop
id ¬p : Prop
∀ (a a : Nat), a = a : Prop
Nat → ∀ (a : Nat), a = a : Prop
id : ?m → ?m
precissues.lean:41:10: error: expected command