Skip to content

Commit

Permalink
feat: allow conversion of binders to instance-implicit
Browse files Browse the repository at this point in the history
  • Loading branch information
jrr6 committed Jan 14, 2025
1 parent d40d6dc commit cf16e16
Show file tree
Hide file tree
Showing 4 changed files with 217 additions and 84 deletions.
34 changes: 25 additions & 9 deletions src/Lean/Elab/BuiltinCommand.lean
Original file line number Diff line number Diff line change
Expand Up @@ -151,17 +151,24 @@ private partial def elabChoiceAux (cmds : Array Syntax) (i : Nat) : CommandElabM
open Lean.Parser.Term

private def typelessBinder? : Syntax → Option (Array (TSyntax [`ident, `Lean.Parser.Term.hole]) × BinderInfo)
| `(bracketedBinderF|($ids*)) => some (ids, .default)
| `(bracketedBinderF|{$ids*}) => some (ids, .implicit)
| `(bracketedBinderF|⦃$ids*⦄) => some (ids, .strictImplicit)
| _ => none
| `(bracketedBinderF|($ids*)) => some (ids, .default)
| `(bracketedBinderF|{$ids*}) => some (ids, .implicit)
| `(bracketedBinderF|⦃$ids*⦄) => some (ids, .strictImplicit)
| `(bracketedBinderF|[$id:ident]) => some (#[id], .instImplicit)
| _ => none

/-- If `id` is an identifier, return true if `ids` contains `id`. -/
private def containsId (ids : Array (TSyntax [`ident, ``Parser.Term.hole])) (id : TSyntax [`ident, ``Parser.Term.hole]) : Bool :=
id.raw.isIdent && ids.any fun id' => id'.raw.getId == id.raw.getId

/-- Elaborates `binders` using `withSynthesize` and `withAutoBoundImplicit`. -/
private def elabBindersSynthesizingAutoBound (binders : TSyntaxArray ``bracketedBinder) :=
runTermElabM fun _ => Term.withSynthesize <| Term.withAutoBoundImplicit <|
Term.elabBinders binders fun _ => pure ()

/--
Auxiliary method for processing binder annotation update commands: `variable (α)` and `variable {α}`.
Auxiliary method for processing binder annotation update commands:
`variable (α)`, `variable {α}`, `variable ⦃α⦄`, and `variable [α]`.
The argument `binder` is the binder of the `variable` command.
The method returns an array containing the "residue", that is, variables that do not correspond to updates.
Recall that a `bracketedBinder` can be of the form `(x y)`.
Expand Down Expand Up @@ -211,12 +218,22 @@ private def replaceBinderAnnotation (binder : TSyntax ``Parser.Term.bracketedBin
| .implicit => `(bracketedBinderF| {$id $[: $ty?]?})
| .strictImplicit => `(bracketedBinderF| {{$id $[: $ty?]?}})
| .instImplicit => do
let some ty := ty? | throwErrorAt id "instance-implicit binders must provide an explicit type"
let some ty := ty?
| throwErrorAt binder "cannot update binder annotation of variable `{id}` to instance-implicit:\n\
variable was originally declared without an explicit type"
`(bracketedBinderF| [$(⟨id⟩) : $ty])
for id in ids.reverse do
if let some idx := binderIds.findFinIdx? fun binderId => binderId.raw.isIdent && binderId.raw.getId == id.raw.getId then
binderIds := binderIds.eraseIdx idx
modifiedVarDecls := true
let newBinder ← mkBinder id binderInfo
if binderInfo.isInstImplicit then
-- We elaborate the new binder to make sure it's valid as instance-implicit
try
elabBindersSynthesizingAutoBound #[newBinder]
catch e =>
throwErrorAt binder m!"cannot update binder annotation of variable `{id}` to instance-implicit:\n\
{e.toMessageData}"
varDeclsNew := varDeclsNew.push (← mkBinder id binderInfo)
else
varDeclsNew := varDeclsNew.push (← mkBinder id binderInfo')
Expand All @@ -228,16 +245,15 @@ private def replaceBinderAnnotation (binder : TSyntax ``Parser.Term.bracketedBin
| .default => `(bracketedBinderF| ($binderId))
| .implicit => `(bracketedBinderF| {$binderId})
| .strictImplicit => `(bracketedBinderF| {{$binderId}})
| .instImplicit => throwErrorAt binderId "instance-implicit binders must provide an explicit type"
| .instImplicit => throwUnsupportedSyntax
else
return #[binder]

@[builtin_command_elab «variable»] def elabVariable : CommandElab
| `(variable $binders*) => do
let binders ← binders.flatMapM replaceBinderAnnotation
-- Try to elaborate `binders` for sanity checking
runTermElabM fun _ => Term.withSynthesize <| Term.withAutoBoundImplicit <|
Term.elabBinders binders fun _ => pure ()
elabBindersSynthesizingAutoBound binders
-- Remark: if we want to produce error messages when variables shadow existing ones, here is the place to do it.
for binder in binders do
let varUIds ← (← getBracketedBinderIds binder) |>.mapM (withFreshMacroScope ∘ MonadQuotation.addMacroScope)
Expand Down
192 changes: 192 additions & 0 deletions tests/lean/run/varBinderUpdate.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/-!
# Changing variable binder annotations
Tests the use of the `variable` command to update the binder annotations of existing variables. -/

/-! Test updating between default and implicit annotations. -/

namespace Ex1

variable {α : Type}
variable [Add α]
variable (α)
def f (a : α) := a + a
/--
info: f Nat 5 : Nat
-/
#guard_msgs in
#check f Nat 5
variable {α}
def g (b : α) := b
/--
info: g 5 : Nat
-/
#guard_msgs in
#check g 5
/--
info: Ex1.f (α : Type) [Add α] (a : α) : α
-/
#guard_msgs in
#check f
/--
info: Ex1.g {α : Type} (b : α) : α
-/
#guard_msgs in
#check g
end Ex1

namespace Ex2

variable {α β : Type}
variable (α)
def f (a : α) := a
def g (b : β) := b
/--
info: f Nat 5 : Nat
-/
#guard_msgs in
#check f Nat 5
/--
info: g 5 : Nat
-/
#guard_msgs in
#check g 5
/--
info: Ex2.f (α : Type) (a : α) : α
-/
#guard_msgs in
#check f
/--
info: Ex2.g {β : Type} (b : β) : β
-/
#guard_msgs in
#check g
/--
error: redundant binder annotation update
-/
#guard_msgs in
variable (α)
end Ex2

namespace Ex3

variable {α : Type}
variable (f : α → α)
variable (α)
def g (a : α) := f a
/--
info: Ex3.g (α : Type) (f : α → α) (a : α) : α
-/
#guard_msgs in
#check g
variable {f}
def h (a : α) := f a
/--
info: Ex3.h (α : Type) {f : α → α} (a : α) : α
-/
#guard_msgs in
#check h
end Ex3

namespace Ex4

variable {α β : Type}
variable (α γ)
def g (a : α) (b : β) (c : γ) := (a, b, c)
/--
info: g Nat Bool 10 "hello" true : Nat × String × Bool
-/
#guard_msgs in
#check g Nat Bool 10 "hello" true
end Ex4

/-! Test updating from and to instance-implicit. -/

namespace Ex5

variable [i : Add α]
variable (i)
def f (x y : α) := x + y
/--
info: Ex5.f.{u_1} {α : Type u_1} (i : Add α) (x y : α) : α
-/
#guard_msgs in
#check f
variable [i]
def g (x y : α) := x + y
/--
info: Ex5.g.{u_1} {α : Type u_1} [i : Add α] (x y : α) : α
-/
#guard_msgs in
#check g
end Ex5

/-! Test that variables with default values/tactics cannot be updated. -/

namespace Ex6

variable (a : Nat)
variable (h : a = a := rfl)
/--
error: cannot update binder annotation of variables with default values/tactics
-/
#guard_msgs in
variable {h}
end Ex6

/-! Test that variables that cannot be instance-implicit fail to be updated thereto. -/

namespace Ex7

variable (n : Nat)
/--
error: cannot update binder annotation of variable `n` to instance-implicit:
invalid binder annotation, type is not a class instance
Nat
use the command `set_option checkBinderAnnotations false` to disable the check
-/
#guard_msgs in
variable [n]
variable (x)
/--
error: cannot update binder annotation of variable `x` to instance-implicit:
variable was originally declared without an explicit type
-/
#guard_msgs in
variable [x]
end Ex7

/-! Test updating to and from strict-implicit annotations. -/

namespace Ex8

variable {α : Type} (β : Type)
variable ⦃α⦄
def f (a : α) (_ : β) := a
/--
info: Ex8.f ⦃α : Type⦄ (β : Type) (a : α) : β → α
-/
#guard_msgs in
#check f
variable (α)
variable ⦃β⦄
def g (a : α) (b : β) := (a, b)
/--
info: Ex8.g (α : Type) ⦃β : Type⦄ (a : α) (b : β) : α × β
-/
#guard_msgs in
#check g
variable {β}
def h (b : β) := b
/--
info: Ex8.h {β : Type} (b : β) : β
-/
#guard_msgs in
#check h
variable {{β}}
/--
error: redundant binder annotation update
-/
#guard_msgs in
variable ⦃β⦄
end Ex8
61 changes: 0 additions & 61 deletions tests/lean/varBinderUpdate.lean

This file was deleted.

14 changes: 0 additions & 14 deletions tests/lean/varBinderUpdate.lean.expected.out

This file was deleted.

0 comments on commit cf16e16

Please sign in to comment.