feat(Tactic): nonempty attribute#39782
Conversation
PR summary 77283e5266Import changes for modified filesNo significant changes to the import graph Import changes for all files
|
| /-- The configuration options for the `nonempty` attribute. -/ | ||
| structure Config where | ||
| /-- If true, register the generated `Nonempty _` declaration as an instance. -/ | ||
| inst : Bool := true |
There was a problem hiding this comment.
I also think that when using the attribute, we will always want to make this an instance. Otherwise I don't see the point of using @[nonempty].
| /-- Given an expression assumed of type `α`, returns an expression for of type `Nonempty α`. -/ | ||
| def toNonempty (e : Expr) : MetaM Expr := do | ||
| mapForallTelescope | ||
| (fun e' => do | ||
| let e ← instantiateMVars e' | ||
| let e2 ← mkAppM ``Nonempty.intro #[e] | ||
| return e2) e |
There was a problem hiding this comment.
Can you just inline this function? The instantiateMVars is redundant, because there won't be any metavariables. So it is just mapForallTelescope (mkAppM ``Nonempty.intro #[·]) e
| /-- This is an adaptation of `Lean.Elab.Util.mkUnusedBaseName` but | ||
| doing everything in `MetaM` instead of `MacroM`. |
There was a problem hiding this comment.
You can run MacroM from MetaM right? So why copy the code?
There was a problem hiding this comment.
I couldn’t get running MacroM in MetaM to work (liftMacroM wants a MonadMacroAdapter MetaM which isn’t there), which is why I had to copy things over like this.
Happy to change if you show me a nicer way to run MacroM inside MetaM (from what I understand, I’d have to build a Macro.Context by hand and run it with that, but there are a bunch of things in Macro.Context I’m not sure how to fill in).
There was a problem hiding this comment.
I also didn't see a nice way to do this
| Warning: As a result, the original doc-string of `ref` will not be visible, | ||
| and go-to-def on `ref` will not go to the definition of `ref`. | ||
| -/ | ||
| def addRelatedInst (src : Name) (ref : Syntax) |
There was a problem hiding this comment.
I think this should not be a separate function from addRelatedDecl. We should avoid code duplication when not necessary.
There was a problem hiding this comment.
So you’d say instead nonempty should be the one responsible for name generation and would add the instance itself rather than registering it in in addRelatedInst? Auto-generating the name and adding the instance with the correct priority is the only extra logic here that had to make it separate from addRelatedDecl.
Maybe the name generation can be moved inside addRelatedDecl? (i.e., we turn the name parameter in addRelatedDecl to an Option and we add a prefix parameter that gets prefixed to the name and that will serve as the prefix for the name generation).
There was a problem hiding this comment.
Indeed, could you simply add another argument (inst := false) to addRelatedDecl and have it deal with both cases?
It's indeed tricky right now to figure out how much is duplicated and what's new.
| @[nonempty] def F : Foo := .unit | ||
|
|
||
| /-- info: instNonemptyFoo : Nonempty Foo -/ | ||
| #guard_msgs in #check instNonemptyFoo |
There was a problem hiding this comment.
| @[nonempty] def F : Foo := .unit | |
| /-- info: instNonemptyFoo : Nonempty Foo -/ | |
| #guard_msgs in #check instNonemptyFoo | |
| /-- | |
| error: failed to synthesize | |
| Nonempty Foo | |
| -/ | |
| #guard_msgs (substring := true) in #synth Nonempty Foo | |
| @[nonempty] def F : Foo := .unit |
I think the tests would make more sense if they check failure before adding the instance and not check the instance's autogenerated name
| - `@[nonempty (name := foo)]` names the resulting instance `foo`. | ||
| - `@[nonempty (priority := 123)]` gives the resulting instance a priority of `123`. | ||
| - `@[nonempty -inst]` does not register the instance. |
There was a problem hiding this comment.
The implementation expects a strict ordering of arguments (priority, name, -inst) which isn't mentioned here; and even the list is in a different order.
You can achieve a better implementation by doing something like:
/-- The configuration options for the `nonempty` attribute. -/
structure Config where
/-- If true, register the generated `Nonempty _` declaration as an instance. -/
inst : Bool := true
priority: Nat := eval_prio default
name : Option String := noneinstead of defining some config-syntax manually.
| @@ -87,4 +87,86 @@ def addRelatedDecl (src tgt : Name) (ref : Syntax) | |||
| if hoverInfo then | |||
| Term.addTermInfo' ref (← mkConstWithLevelParams tgt) (isBinder := true) | |||
|
|
|||
| /-- This is an adaptation of `Lean.Elab.Util.mkUnusedBaseName` but | |||
There was a problem hiding this comment.
| /-- This is an adaptation of `Lean.Elab.Util.mkUnusedBaseName` but | |
| /-- This is an adaptation of `Lean.Elab.mkUnusedBaseName` but |
| Warning: As a result, the original doc-string of `ref` will not be visible, | ||
| and go-to-def on `ref` will not go to the definition of `ref`. | ||
| -/ | ||
| def addRelatedInst (src : Name) (ref : Syntax) |
There was a problem hiding this comment.
Indeed, could you simply add another argument (inst := false) to addRelatedDecl and have it deal with both cases?
It's indeed tricky right now to figure out how much is duplicated and what's new.
This PR adds a
nonemptyattribute. When tagging a definition of typeαwith[nonempty], an instance of typeNonempty αis automatically generated and registered using the definition.Such an attribute is useful in the following scenario: certain structures are not classes (e.g., to avoid data-carrying classes potentially creating diamonds), but mere existence of terms of such structures allows to derive instances about the parameters of the structure. Using
[nonempty]lets one auto-generate the instances derived instance (provided they correctly follow from[Nonempty _]).Such is the case for the
CategoryTheory.Functor.FullyFaithfulstructure, which allows to derive theProp-classesCategoryTheory.Functor.FullandCategoryTheory.Functor.Faithful. Currently in mathlib, most definitions of typeCategoryTheory.Functor.FullyFaithfulare immediately followed by the correspondingFullandFaithfulinstances, adding "boilerplate" and increasing the surface for potential mistakes (like forgetting one or both of the instances.)The attribute uses a variation of
addRelatedDeclcalledaddRelatedInstance, which handles automatic name generation and registration of instances from a given declaration and a "constructor" of typeExpr → List Name → MetaM (Expr × List Name).I believe abstracting
addRelatedInstmight have other possible usages: for instance, given an adjunctionadj: L ⊣ R, it would be nice to have an attribute that one can tag on such adjunctions that automatically registersL.IsLeftAdjointandR.IsRightAdjoint. Thenonemptyattribute here can’t really do this becauseRcan’t be inferred from a goal of typeL.IsLeftAdjoint, but the situation is quite similar, andaddRelatedInstcould also help writing this kind of attribute.