Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add pp.fieldNotation.generalized for generalized field notation, add @[pp_nodot] attribute #3737

Merged
merged 2 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ v4.8.0 (development in progress)
rather than `{a := x, b := y, c := z}`.
This attribute is applied to `Sigma`, `PSigma`, `PProd`, `Subtype`, `And`, and `Fin`.

* Option `pp.structureProjections` is renamed to `pp.fieldNotation`, and there is a new suboption `pp.fieldNotation.generalized`
to enable pretty printing function applications using generalized field notation.
Field notation can now be disabled function-by-function using the `@[pp_nodot]` attribute.

Breaking changes:

* Automatically generated equational theorems are now named using suffix `.eq_<idx>` instead of `._eq_<idx>`, and `.def` instead of `._unfold`. Example:
Expand Down
11 changes: 10 additions & 1 deletion src/Lean/PrettyPrinter/Delaborator/Attributes.lean
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,19 @@ namespace Lean
builtin_initialize ppUsingAnonymousConstructorAttr : TagAttribute ←
registerTagAttribute `pp_using_anonymous_constructor "mark structure to be pretty printed using `⟨a,b,c⟩` notation"

builtin_initialize ppNoDotAttr : TagAttribute ←
registerTagAttribute `pp_nodot "mark declaration to never be pretty printed using field notation"

/--
Returns whether or not the given declaration has been given the `@[pp_using_anonymous_constructor]` attribute.
Returns whether or not the given declaration has the `@[pp_using_anonymous_constructor]` attribute.
-/
def hasPPUsingAnonymousConstructorAttribute (env : Environment) (declName : Name) : Bool :=
ppUsingAnonymousConstructorAttr.hasTag env declName

/--
Returns whether or not the given declaration has the `@[pp_nodot]` attribute.
-/
def hasPPNoDotAttribute (env : Environment) (declName : Name) : Bool :=
ppNoDotAttr.hasTag env declName

end Lean
213 changes: 122 additions & 91 deletions src/Lean/PrettyPrinter/Delaborator/Builtins.lean

Large diffs are not rendered by default.

98 changes: 98 additions & 0 deletions src/Lean/PrettyPrinter/Delaborator/FieldNotation.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/-
Copyright (c) 2024 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kyle Miller
-/
prelude
import Lean.Meta.Basic
import Lean.PrettyPrinter.Delaborator.Attributes
import Lean.PrettyPrinter.Delaborator.Options
import Lean.Structure

/-!
# Functions for analyzing projections for pretty printing
-/

namespace Lean.PrettyPrinter.Delaborator
open Meta

/--
If this is a structure projection that could delaborate using dot notation,
returns the field name, the number of parameters before the structure argument, and whether this is a parent projection.
Otherwise it fails.
-/
private def projInfo (f : Expr) : MetaM (Name × Nat × Bool) := do
let .const c@(.str _ field) _ := f.consumeMData | failure
let field := Name.mkSimple field
let env ← getEnv
let some info := env.getProjectionFnInfo? c | failure
-- Don't use projection notation for instances of classes.
guard <| !info.fromClass
let some (.ctorInfo cVal) := env.find? info.ctorName | failure
let isParentProj := (isSubobjectField? env cVal.induct field).isSome
return (field, info.numParams, isParentProj)

/--
If this function application could delaborate using generalized field notation,
returns the field name and the index for the argument to be used as the object of the field notation.
Otherwise it fails.
-/
private def generalizedFieldInfo (f : Expr) (args : Array Expr) : MetaM (Name × Nat) := do
let .const name@(.str _ field) .. := f.consumeMData | failure
let field := Name.mkSimple field
let baseName := name.getPrefix
guard <| !baseName.isAnonymous
Meta.forallBoundedTelescope (← instantiateMVars <| ← inferType f) args.size fun params _ => do
for i in [0:params.size] do
let fvarId := params[i]!.fvarId!
if (← fvarId.getBinderInfo).isExplicit then
-- We only consider the first explicit argument, so fail if the parameter does not have the correct type.
guard <| (← fvarId.getType).cleanupAnnotations.isAppOf baseName
let argTy ← instantiateMVars <| ← inferType args[i]!
-- Generalized field notation allows either an an exact match, or a match after `whnfR`.
if argTy.consumeMData.isAppOf baseName then
return (field, i)
else if (← Meta.whnfR argTy).isAppOf baseName then
return (field, i)
else
failure
else
-- If not explicit, then make sure that this parameter can't be used for field notation.
guard <| ! (← fvarId.getType).cleanupAnnotations.isAppOf baseName
failure

/--
If `f` is a function that can be used for field notation,
returns the field name to use and the argument index for the object of the field notation.
-/
def fieldNotationCandidate? (f : Expr) (args : Array Expr) (useGeneralizedFieldNotation : Bool) : MetaM (Option (Name × Nat)) := do
let env ← getEnv
let .const name .. := f.consumeMData | return none
if name.getPrefix.isAnonymous then return none
-- If there is `pp_nodot` on this function, then don't use field notation for it.
if hasPPNoDotAttribute env name then
return none
-- Handle structure projections
try
let (field, numParams, _) ← projInfo f
return (field, numParams)
catch _ => pure ()
-- Handle generalized field notation
if useGeneralizedFieldNotation then
try
return ← generalizedFieldInfo f args
catch _ => pure ()
-- It's not handled by any of the above.
return none

/--
Returns `true` if `e` is an application that is a projection to a parent structure.
If `explicit` is `true`, then further requires that the structure have no parameters.
-/
def isParentProj (explicit : Bool) (e : Expr) : MetaM Bool := do
unless e.isApp do return false
try
let (_, numParams, isParentProj) ← projInfo e.getAppFn
return isParentProj && (!explicit || numParams == 0) && e.getAppNumArgs == numParams + 1
catch _ =>
return false
12 changes: 9 additions & 3 deletions src/Lean/PrettyPrinter/Delaborator/Options.lean
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,15 @@ register_builtin_option pp.structureInstances : Bool := {
descr := "(pretty printer) display structure instances using the '{ fieldName := fieldValue, ... }' notation, \
or using '⟨fieldValue, ... ⟩' if structure is tagged with the '@[pp_using_anonymous_constructor]' attribute"
}
register_builtin_option pp.structureProjections : Bool := {
register_builtin_option pp.fieldNotation : Bool := {
defValue := true
group := "pp"
descr := "(pretty printer) display structure projections using field notation"
descr := "(pretty printer) use field notation when pretty printing, including for structure projections, unless '@[pp_nodot]' is applied"
}
register_builtin_option pp.fieldNotation.generalized : Bool := {
defValue := false -- TODO(kmill): set to true
group := "pp"
descr := "(pretty printer) when `pp.fieldNotation` is true, enable using generalized field notation when the argument for field notation is the first explicit argument"
}
register_builtin_option pp.explicit : Bool := {
defValue := false
Expand Down Expand Up @@ -215,7 +220,8 @@ def getPPExplicit (o : Options) : Bool := o.get pp.explicit.name (getPPAll o)
def getPPNotation (o : Options) : Bool := o.get pp.notation.name (!getPPAll o)
def getPPUnicodeFun (o : Options) : Bool := o.get pp.unicode.fun.name false
def getPPMatch (o : Options) : Bool := o.get pp.match.name (!getPPAll o)
def getPPStructureProjections (o : Options) : Bool := o.get pp.structureProjections.name (!getPPAll o)
def getPPFieldNotation (o : Options) : Bool := o.get pp.fieldNotation.name (!getPPAll o)
def getPPFieldNotationGeneralized (o : Options) : Bool := o.get pp.fieldNotation.generalized.name pp.fieldNotation.generalized.defValue
def getPPStructureInstances (o : Options) : Bool := o.get pp.structureInstances.name (!getPPAll o)
def getPPStructureInstanceType (o : Options) : Bool := o.get pp.structureInstanceTypes.name (getPPAll o)
def getPPTagAppFns (o : Options) : Bool := o.get pp.tagAppFns.name (getPPAll o)
Expand Down
13 changes: 13 additions & 0 deletions src/Lean/PrettyPrinter/Delaborator/TopDownAnalyze.lean
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Lean.Util.FindMVar
import Lean.Util.FindLevelMVar
import Lean.Util.CollectLevelParams
import Lean.Util.ReplaceLevel
import Lean.PrettyPrinter.Delaborator.FieldNotation
import Lean.PrettyPrinter.Delaborator.Options
import Lean.PrettyPrinter.Delaborator.SubExpr
import Lean.Elab.Config
Expand Down Expand Up @@ -123,6 +124,7 @@ def getPPAnalysisNamedArg (o : Options) : Bool := o.get `pp.analysis.name
def getPPAnalysisLetVarType (o : Options) : Bool := o.get `pp.analysis.letVarType false
def getPPAnalysisNeedsType (o : Options) : Bool := o.get `pp.analysis.needsType false
def getPPAnalysisBlockImplicit (o : Options) : Bool := o.get `pp.analysis.blockImplicit false
def getPPAnalysisNoDot (o : Options) : Bool := o.get `pp.analysis.noDot false

namespace PrettyPrinter.Delaborator

Expand Down Expand Up @@ -401,6 +403,17 @@ mutual
-- Unify with the expected type
if (← read).knowsType then tryUnify (← inferType (mkAppN f args)) resultType

-- Prevent using dot notation if the expected of the argument can't be determined.
-- TODO: is canBottomUp sufficient for this?
if getPPFieldNotation (← getOptions) then
if let some (_, idx) ← fieldNotationCandidate? f args (getPPFieldNotationGeneralized (← getOptions)) then
if idx < args.size then
withKnowing false false do
if !(← canBottomUp args[idx]!) then
annotateBool `pp.analysis.noDot
else
annotateBool `pp.analysis.noDot

let forceRegularApp : Bool :=
(getPPAnalyzeTrustSubst (← getOptions) && isSubstLike (← getExpr))
|| (getPPAnalyzeTrustSubtypeMk (← getOptions) && (← getExpr).isAppOfArity ``Subtype.mk 4)
Expand Down
2 changes: 1 addition & 1 deletion tests/lean/439.lean.expected.out
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
fn.imp : {p : P} → Bar.fn p
@Fn.imp ((p : P) → Bar.fn p) ({p : P} → Bar.fn p) fn : {p : P} → Bar.fn p
439.lean:18:7-18:12: error: function expected at
fn.imp
term has type
Expand Down
96 changes: 0 additions & 96 deletions tests/lean/delabProjectionApp.lean

This file was deleted.

22 changes: 0 additions & 22 deletions tests/lean/delabProjectionApp.lean.expected.out

This file was deleted.

2 changes: 1 addition & 1 deletion tests/lean/run/PPTopDownAnalyze.lean
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ set_option pp.analyze.explicitHoles false in

set_option pp.analyze.trustSubtypeMk true in
#testDelab fun (n : Nat) (val : List Nat) (property : List.length val = n) => List.length { val := val, property := property : { x : List Nat // List.length x = n } }.val = n
expecting fun n val property => List.length (⟨val, property⟩ : { x : List Nat // List.length x = n }).val = n
expecting fun n val property => List.length (Subtype.val (p := fun x => List.length x = n) (⟨val, property⟩ : { x : List Nat // List.length x = n })) = n

#testDelabN Nat.brecOn
#testDelabN Nat.below
Expand Down
Loading
Loading