Skip to content

Commit

Permalink
feat: pp_extended_field_notation command to pretty print with dot not…
Browse files Browse the repository at this point in the history
…ation (#3811)

The projection notation delaborator that comes from core Lean has some limitations. We introduce a new projection notation delaborator that is able to collapse parent projection sequences, for example `x.toC.toB.toA.val` into `x.val`.

The other limitation of the delaborator is that it can only handle true projections that do not have any additional arguments. This commit adds a `pp_extended_field_notation` command to switch on projection notation for specific functions. This command defines app unexpanders that pretty print that function application using dot notation.

The app unexpander it produces has a small hack to completely collapse parent projection sequences. Since it is an app unexpander, we do not have access to the actual types, so we use a heuristic that, for example with `A.foo`, if we are looking at `A.foo x.toA y z ...` then we can pretty print this as `x.foo y z`. The projection delaborator is able to collapse parent projection sequences except for the vary last one, so this finishes it off. Note that this heuristic can lead to output that does not round trip if there is a `toA` function that is not a parent projection that happens to be pretty printed with dot notation.
  • Loading branch information
kmill committed May 10, 2023
1 parent bb9f362 commit c93011c
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 34 deletions.
1 change: 1 addition & 0 deletions Mathlib.lean
Expand Up @@ -1865,6 +1865,7 @@ import Mathlib.Tactic.Positivity
import Mathlib.Tactic.Positivity.Basic
import Mathlib.Tactic.Positivity.Core
import Mathlib.Tactic.PrintPrefix
import Mathlib.Tactic.ProjectionNotation
import Mathlib.Tactic.Propose
import Mathlib.Tactic.ProxyType
import Mathlib.Tactic.PushNeg
Expand Down
18 changes: 0 additions & 18 deletions Mathlib/CategoryTheory/Functor/Basic.lean
Expand Up @@ -53,24 +53,6 @@ structure Functor (C : Type u₁) [Category.{v₁} C] (D : Type u₂) [Category.
add_decl_doc Functor.toPrefunctor
#align category_theory.functor.to_prefunctor CategoryTheory.Functor.toPrefunctor

/--
This unexpander will pretty print `F.obj X` properly.
Without this, we would have `Prefunctor.obj F.toPrefunctor X`.
-/
@[app_unexpander Prefunctor.obj] def
unexpandFunctorObj : Lean.PrettyPrinter.Unexpander
| `($_ $(F).toPrefunctor $(X)*) => set_option hygiene false in `($(F).obj $(X)*)
| _ => throw ()

/--
This unexpander will pretty print `F.map f` properly.
Without this, we would have `Prefunctor.map F.toPrefunctor f`.
-/
@[app_unexpander Prefunctor.map] def
unexpandFunctorMap : Lean.PrettyPrinter.Unexpander
| `($_ $(F).toPrefunctor $(X)*) => set_option hygiene false in `($(F).map $(X)*)
| _ => throw ()

end

/-- Notation for a functor between categories. -/
Expand Down
9 changes: 1 addition & 8 deletions Mathlib/CategoryTheory/NatIso.lean
Expand Up @@ -63,14 +63,7 @@ def app {F G : C ⥤ D} (α : F ≅ G) (X : C) :
#align category_theory.iso.app_hom CategoryTheory.Iso.app_hom
#align category_theory.iso.app_inv CategoryTheory.Iso.app_inv

/--
This unexpander will pretty print `η.app X` properly.
Without this, we would have `Iso.app η X`.
-/
@[app_unexpander Iso.app] def
unexpandIsoApp : Lean.PrettyPrinter.Unexpander
| `($_ $η $(X)*) => set_option hygiene false in `($(η).app $(X)*)
| _ => throw ()
pp_extended_field_notation Iso.app

@[reassoc (attr := simp)]
theorem hom_inv_id_app {F G : C ⥤ D} (α : F ≅ G) (X : C) :
Expand Down
9 changes: 1 addition & 8 deletions Mathlib/CategoryTheory/NatTrans.lean
Expand Up @@ -59,14 +59,7 @@ structure NatTrans (F G : C ⥤ D) : Type max u₁ v₂ where
#align category_theory.nat_trans.ext_iff CategoryTheory.NatTrans.ext_iff
#align category_theory.nat_trans.ext CategoryTheory.NatTrans.ext

/--
This unexpander will pretty print `η.app X` properly.
Without this, we would have `NatTrans.app η X`.
-/
@[app_unexpander NatTrans.app] def
unexpandNatTransApp : Lean.PrettyPrinter.Unexpander
| `($_ $η $(X)*) => set_option hygiene false in `($(η).app $(X)*)
| _ => throw ()
pp_extended_field_notation NatTrans.app

-- TODO Perhaps we should just turn on `ext` in aesop?
attribute [aesop safe apply (rule_sets [CategoryTheory])] NatTrans.ext
Expand Down
6 changes: 6 additions & 0 deletions Mathlib/Combinatorics/Quiver/Basic.lean
Expand Up @@ -10,6 +10,7 @@ Ported by: Scott Morrison
! if you have ported upstream changes.
-/
import Mathlib.Data.Opposite
import Mathlib.Tactic.ProjectionNotation

/-!
# Quivers
Expand Down Expand Up @@ -65,6 +66,9 @@ structure Prefunctor (V : Type u₁) [Quiver.{v₁} V] (W : Type u₂) [Quiver.{
map : ∀ {X Y : V}, (X ⟶ Y) → (obj X ⟶ obj Y)
#align prefunctor Prefunctor

pp_extended_field_notation Prefunctor.obj
pp_extended_field_notation Prefunctor.map

namespace Prefunctor

@[ext]
Expand Down Expand Up @@ -104,6 +108,8 @@ def comp {U : Type _} [Quiver U] {V : Type _} [Quiver V] {W : Type _} [Quiver W]
#align prefunctor.comp_obj Prefunctor.comp_obj
#align prefunctor.comp_map Prefunctor.comp_map

pp_extended_field_notation Prefunctor.comp

@[simp]
theorem comp_id {U V : Type _} [Quiver U] [Quiver V] (F : Prefunctor U V) :
F.comp (id _) = F := rfl
Expand Down
1 change: 1 addition & 0 deletions Mathlib/Tactic.lean
Expand Up @@ -79,6 +79,7 @@ import Mathlib.Tactic.Positivity
import Mathlib.Tactic.Positivity.Basic
import Mathlib.Tactic.Positivity.Core
import Mathlib.Tactic.PrintPrefix
import Mathlib.Tactic.ProjectionNotation
import Mathlib.Tactic.Propose
import Mathlib.Tactic.ProxyType
import Mathlib.Tactic.PushNeg
Expand Down
145 changes: 145 additions & 0 deletions Mathlib/Tactic/ProjectionNotation.lean
@@ -0,0 +1,145 @@
/-
Copyright (c) 2023 Kyle Miller. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kyle Miller
-/

import Lean
import Lean.Elab.AuxDef

/-!
# Commands for configuring projection notation
This module contains some relatively simple commands that can be used
to configure functions to pretty print with projection
notation (i.e., like `x.f y` rather than `C.f x y`).
One of these commands is for collapsing chains of ancestor projections.
For example, to turn `x.toFoo.toBar` into `x.toBar`.
-/

namespace Mathlib.ProjectionNotation

open Lean Parser Term
open PrettyPrinter.Delaborator SubExpr
open Lean.Elab.Command

register_option pp.collapseStructureProjections : Bool := {
defValue := true
group := "pp"
descr := "(pretty printer, Mathlib extension) display structure projections using field notation"
}

def getPPCollapseStructureProjections (o : Options) : Bool :=
o.get pp.structureProjections.name (!getPPAll o)

/-- Like the projection delaborator from core Lean, but collapses projections to parent
structures into a single projection.
The only functional difference from `Lean.PrettyPrinter.Delaborator.delabProjectionApp` is
the `walkUp` function. -/
@[delab app]
partial def delabProjectionApp' : Delab := whenPPOption getPPCollapseStructureProjections $ do
let e@(Expr.app fn _) ← getExpr | failure
let .const c@(.str _ f) _ := fn.getAppFn | failure
let env ← getEnv
let some info := env.getProjectionFnInfo? c | failure
-- can't use with classes since the instance parameter is implicit
guard <| !info.fromClass
-- projection function should be fully applied (#struct params + 1 instance parameter)
-- TODO: support over-application
guard <| e.getAppNumArgs == info.numParams + 1
-- If pp.explicit is true, and the structure has parameters, we should not
-- use field notation because we will not be able to see the parameters.
let expl ← getPPOption getPPExplicit
guard <| !expl || info.numParams == 0

/- Consume projections to parent structures. -/
let rec walkUp {α} (done : DelabM α) : DelabM α := withAppArg do
let (Expr.app fn _) ← getExpr | done
let .const c@(.str _ field) _ := fn.getAppFn | done
let some structName := env.getProjectionStructureName? c | failure
let some _ := isSubobjectField? env structName field | done
walkUp done

walkUp do
let appStx ← delab
`($(appStx).$(mkIdent f):ident)

/--
Defines an `app_unexpander` for the given function to support a basic form of projection
notation. It is *only* for functions whose first explicit argument is the receiver
of the generalized field notation. That is to say, it is only meant for transforming
`C.f c x y z ...` to `c.f x y z ...` for `c : C`.
It can be used to help get projection notation to work for function-valued structure fields,
since the default projection delaborator cannot handle excess arguments.
Example for generalized field notation:
```
structure A where
n : Nat
def A.foo (a : A) (m : Nat) : Nat := a.n + m
pp_extended_field_notation A.foo
```
Now, `A.foo x m` pretty prints as `x.foo m`. It also adds a rule that
`A.foo x.toA m` pretty prints as `x.foo m`. This rule is meant to combine with
the projection collapse delaborator, so that `A.foo x.toB.toA m` also will
pretty print as `x.foo m`.
Since this last rule is a purely syntactic transformation,
it might lead to output that does not round trip, though this can only occur if
there exists an `A`-valued `toA` function that is not a parent projection that
happens to be pretty printable using dot notation.
Here is an example to illustrate the round tripping issue:
```lean
import Mathlib.Tactic.ProjectionNotation
structure A where n : Int
def A.inc (a : A) (k : Int) : Int := a.n + k
pp_extended_field_notation A.inc
structure B where n : Nat
def B.toA (b : B) : A := ⟨b.n⟩
variable (b : B)
#check A.inc b.toA 1
-- (B.toA b).inc 1 : Int
pp_extended_field_notation B.toA
#check A.inc b.toA 1
-- b.inc 1 : Int
#check b.inc 1
-- invalid field 'inc', the environment does not contain 'B.inc'
```
To avoid this, don't use `pp_extended_field_notation` for coercion functions
such as `B.toA`.
-/
elab "pp_extended_field_notation " f:Term.ident : command => do
let f ← liftTermElabM <| Elab.resolveGlobalConstNoOverloadWithInfo f
let .str A projName := f |
throwError "Projection name must end in a string component."
let some _ := getStructureInfo? (← getEnv) A |
throwError "{A} is not a structure"
let .str _ A' := A | throwError "{A} must end in a string component"
let toA : Name := .str .anonymous ("to" ++ A')
elabCommand <| ← `(command|
@[app_unexpander $(mkIdent f)]
aux_def $(mkIdent <| Name.str f "unexpander") : Lean.PrettyPrinter.Unexpander := fun
-- First two patterns are to avoid extra parentheses in output
| `($$_ $$(x).$(mkIdent toA))
| `($$_ $$x) => set_option hygiene false in `($$(x).$(mkIdent projName))
-- Next two are for when there are actually arguments, so parentheses might be needed
| `($$_ $$(x).$(mkIdent toA) $$args*)
| `($$_ $$x $$args*) => set_option hygiene false in `($$(x).$(mkIdent projName) $$args*)
| _ => throw ())

namespace Mathlib.ProjectionNotation

0 comments on commit c93011c

Please sign in to comment.