Zulip Chat Archive

Stream: general

Topic: Dimming implicit arguments in `#check`


Mirek Olšák (Nov 02 2025 at 14:48):

I just thought that it could improve user experience if I could quickly see the explicit arguments of a library function, so for example when typing

#check AffineSubspace

Seeing highlighted output instead of the monotone current state. What do you think? Would it be easy to implement? Would it be generally appreciated?
check-AffineSubspace-hl.png
check-AffineSubspace.png.

Ruben Van de Velde (Nov 02 2025 at 14:51):

I have the impression that this was discussed recently and it was hard, but would certainly be nice

Ruben Van de Velde (Nov 02 2025 at 14:51):

Also on hover

Markus Himmel (Nov 02 2025 at 14:52):

Here is the recent discussion. It was about the signature help, not about #check. #lean4 > rfc: show arity of current function

Mirek Olšák (Nov 02 2025 at 14:56):

Hm, it should not be that hard at least in terms of generating HTML... Only that it would miss the hover feature, so it cannot be the default.

Aaron Liu (Nov 02 2025 at 15:02):

apparently we can do diagrams in the docstrings and it shows up in the infoview #mathlib4 > readable commutative diagrams in docstrings @ 💬

Aaron Liu (Nov 02 2025 at 15:02):

so the infoview is pretty powerful

Mirek Olšák (Nov 02 2025 at 15:33):

In a sense, clickable grayed-out terms are possible

import ProofWidgets
import Qq

open ProofWidgets Jsx

open Qq in
def makeWidget : Lean.Meta.MetaM Html := do
  let term : Q(Nat) := q(2 + 2)
  return <span «class»="goal-inaccessible">
    <InteractiveCode fmt={ Lean.Widget.ppExprTagged term} /> </span>

#html makeWidget

Mirek Olšák (Nov 02 2025 at 15:34):

(only it is also italic)

Michael Rothgang (Nov 02 2025 at 16:06):

My personal preference would be to add a version of #check that only shows explicit arguments. (Probably, adding a configuration option to #check would be the way, i.e. this needs core modifications.)

Michael Rothgang (Nov 02 2025 at 16:09):

Here is how to write this as a separate command, today:

import Mathlib

open Lean Elab Command PrettyPrinter Delaborator in
elab tk:"#check' " name:ident : command => runTermElabM fun _ => do
  for c in ( realizeGlobalConstWithInfos name) do
    addCompletionInfo <| .id name name.getId (danglingDot := false) {} none
    let info  getConstInfo c
    let delab : Delab := do
      delabForallParamsWithSignature fun binders type => do
        let binders := binders.filter fun binder => binder.raw.isOfKind ``Parser.Term.explicitBinder
        return ⟨← `(declSigWithId| $(mkIdent c) $binders* : $type)
    logInfoAt tk <| .ofFormatWithInfosM (PrettyPrinter.ppExprWithInfos (delab := delab) info.type)

/--
info: mfderiv_comp (x : M) (hg : MDifferentiableAt I' I'' g (f x)) (hf : MDifferentiableAt I I' f x) :
  mfderiv I I'' (g ∘ f) x = (mfderiv I' I'' g (f x)).comp (mfderiv I I' f x)
-/
#guard_msgs in
#check' mfderiv_comp

Michael Rothgang (Nov 02 2025 at 16:09):

Note that #check' mfderiv_comp does not show any of the ~dozen implicit or typeclass arguments.

Mirek Olšák (Nov 02 2025 at 16:14):

Thanks for the first outline, although I would prefer the implicit arguments dimmed, not missing...

Mirek Olšák (Nov 02 2025 at 16:23):

That probably means doing custom elementary argument decomposition side, to produce a Html instead of MessageData, so we would need to reimplement things like

  • printing different kinds of parentheses around arguments
  • skipping [anonymous] : for arguments without names
  • grouping together arguments with identical types

Besides that, it suffices to group all the Html parts into <span class="font-code">, and dimmed arguments into <span class="goal-inaccessible">. Do I understand it correctly?

Michael Rothgang (Nov 02 2025 at 16:36):

(Note: making a #check' tactic is not difficult either, I have that on a branch. Let me open a PR in any case.)

Mirek Olšák (Nov 02 2025 at 18:36):

Here we go!

import Mathlib

open Lean ProofWidgets Jsx in
def checkH (name : Lean.Name) (showAlt : Bool) : MetaM Unit := do
  let constVal  getConstVal name
  Meta.forallTelescope constVal.type <| fun args outType => do
    let mut msg : MessageData := toString name
    if !constVal.levelParams.isEmpty then
      let univStr : String := ",".intercalate (constVal.levelParams.map toString)
      let univStr := ".{" ++ univStr ++ "}"
      let alt := if showAlt then univStr else ""
      msg := msg ++ (MessageData.ofHtml <span «class»="o-50">{
        .text <| univStr
      }</span> alt)
    let decls  args.mapM Meta.getFVarLocalDecl
    let groups := decls.toList.splitBy
      (fun d1 d2 => declCore d1 == declCore d2)
    for group in groups do
      let decl : LocalDecl := group.head!
      let (p1,p2) : String × String := match decl.binderInfo with
      | .default => ("(",")")
      | .implicit => ("{","}")
      | .instImplicit => ("[","]")
      | .strictImplicit => ("⦃","⦄")
      let mut paramParts : Array (String  Expr) := #[.inl " "]
      paramParts := paramParts.push (.inl p1)
      if group.length > 1  ¬decl.binderInfo.isInstImplicit then
        for arg in group do
          let e : Expr := .fvar arg.fvarId
          paramParts := paramParts.push (.inr e)
          paramParts := paramParts.push (.inl " ")
        paramParts := paramParts.push (.inl ": ")
      paramParts := paramParts.push (.inr decl.type)
      paramParts := paramParts.push (.inl p2)
      let paramMsg : MessageData 
        if decl.binderInfo.isExplicit then
          pure <| paramParts.foldl (fun m part =>
            m ++ match part with
            | .inl s => toMessageData s
            | .inr e => toMessageData e
          ) m!""
        else
          let htmls  paramParts.mapM (fun part => match part with
          | .inl s => pure (.text s)
          | .inr e => do
            let widget  Lean.Widget.ppExprTagged e
            pure <InteractiveCode fmt={widget} />
          )
          let mut alt := ""
          if showAlt then
            let strs : Array String  paramParts.mapM (fun part => match part with
            | .inl s => pure s
            | .inr e => toString <$> Meta.ppExpr e
            )
            alt := "".intercalate strs.toList
          pure <| MessageData.ofHtml
            (.element "span" #[("class", "o-50")] htmls) alt
      msg := msg ++ paramMsg
    msg := msg ++ " : "
    msg := msg ++ outType
    logInfo msg
where
  declCore (decl : Lean.LocalDecl) :=
    (decl.type, decl.binderInfo, decl.kind)

elab "#checkh " name:ident : command =>
  Lean.Elab.Command.runTermElabM fun _ => do
    for fullName in ( Lean.Elab.realizeGlobalConstWithInfos name) do
      checkH fullName false
elab "#checkh' " name:ident : command =>
  Lean.Elab.Command.runTermElabM fun _ => do
    for fullName in ( Lean.Elab.realizeGlobalConstWithInfos name) do
      checkH fullName true

#checkh AffineSubspace
#checkh mul_assoc

It might not be as sophisticated as the real delaboration but looks usable.

Mirek Olšák (Nov 02 2025 at 19:30):

Update: Now it only passes the dimmed parts through Html, so Error Lens show the same as check'. Now I am quite happy with it. Where could this belong? ProofWidgets?
Update2: o-50 looks like better dimmer than goal-inaccessible. I update the code above.

Mirek Olšák (Nov 02 2025 at 19:40):

screenshot.png
error-lens.png

Eric Wieser (Nov 02 2025 at 20:34):

I wonder if this should be built into the term MessageData formatter, so that this also happens in error messages about mismatching types?

Mirek Olšák (Nov 02 2025 at 21:18):

Unfortunatelly, I don't think it is a desirable feature in general that the export from MessageData to String hides some parts of the output. Only here, I didn't have much better option because I needed to insert an HTML tag into it, which breaks the path to string.

Mirek Olšák (Nov 02 2025 at 21:33):

Actually, I could have used the alt-text, so it is possible to export string correctly... Hard to decide. I still think for user experience the shortened Error Lens is great, but I cannot expect this to be the default, when you also see the shortened string on terminal output upon calling lean from command line.

Mirek Olšák (Nov 02 2025 at 21:37):

If the question was about dimming implicit arguments in other MessageData, then sure, I think it would be helpful & principled (without the detour through ProofWidgets).

Mirek Olšák (Nov 03 2025 at 22:53):

I made it a PR to ProofWidgets: https://github.com/leanprover-community/ProofWidgets4/pull/142
I understand it would be better to have it more general, and in Core but let's start somewhere...


Last updated: Dec 20 2025 at 21:32 UTC